From b80f2357b9c30843f0cf462bd4a47b75f52f8b8b Mon Sep 17 00:00:00 2001 From: Mike Lissner Date: Sat, 16 Aug 2014 20:20:08 -0700 Subject: [PATCH 0001/1094] Updates docs for Python 3 In Python 3, the simpleHTTPServer has moved into the http module, so the command `python -m simpleHTTPServer no longer works. This minor change adds instructions for those of us using Python 3. --- docs/publish.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/publish.rst b/docs/publish.rst index 266009e4..fea053bf 100644 --- a/docs/publish.rst +++ b/docs/publish.rst @@ -36,12 +36,19 @@ HTML files directly:: 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:: +reliable previewing experience. + +For Python 2, run:: cd output python -m SimpleHTTPServer -Once the ``SimpleHTTPServer`` has been started, you can preview your site at +For Python 3, run:: + + cd output + python -m http.server + +Once the basic server has been started, you can preview your site at http://localhost:8000/ Deployment From 6ac06b28b1c3a9be1c947c6ae84b923ebea2ba86 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Wed, 20 Aug 2014 17:07:01 +0200 Subject: [PATCH 0002/1094] Unify bottom margins of lists and paragraphs --- pelican/tests/output/basic/theme/css/main.css | 3 ++- pelican/tests/output/custom/theme/css/main.css | 3 ++- pelican/tests/output/custom_locale/theme/css/main.css | 3 ++- pelican/themes/notmyidea/static/css/main.css | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pelican/tests/output/basic/theme/css/main.css b/pelican/tests/output/basic/theme/css/main.css index 2efb518d..9d7221a2 100644 --- a/pelican/tests/output/basic/theme/css/main.css +++ b/pelican/tests/output/basic/theme/css/main.css @@ -84,7 +84,8 @@ ol { margin: 0em 0 0 1.5em; } -li { margin-top: 0.5em;} +li { margin-top: 0.5em; + margin-bottom: 1em; } .post-info { float:right; diff --git a/pelican/tests/output/custom/theme/css/main.css b/pelican/tests/output/custom/theme/css/main.css index 2efb518d..9d7221a2 100644 --- a/pelican/tests/output/custom/theme/css/main.css +++ b/pelican/tests/output/custom/theme/css/main.css @@ -84,7 +84,8 @@ ol { margin: 0em 0 0 1.5em; } -li { margin-top: 0.5em;} +li { margin-top: 0.5em; + margin-bottom: 1em; } .post-info { float:right; diff --git a/pelican/tests/output/custom_locale/theme/css/main.css b/pelican/tests/output/custom_locale/theme/css/main.css index 2efb518d..9d7221a2 100644 --- a/pelican/tests/output/custom_locale/theme/css/main.css +++ b/pelican/tests/output/custom_locale/theme/css/main.css @@ -84,7 +84,8 @@ ol { margin: 0em 0 0 1.5em; } -li { margin-top: 0.5em;} +li { margin-top: 0.5em; + margin-bottom: 1em; } .post-info { float:right; diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css index 2efb518d..9d7221a2 100644 --- a/pelican/themes/notmyidea/static/css/main.css +++ b/pelican/themes/notmyidea/static/css/main.css @@ -84,7 +84,8 @@ ol { margin: 0em 0 0 1.5em; } -li { margin-top: 0.5em;} +li { margin-top: 0.5em; + margin-bottom: 1em; } .post-info { float:right; From 304b16a9bea976917b05a0380b9efbbb0db7d8fb Mon Sep 17 00:00:00 2001 From: Artemy Tregubenko Date: Sun, 31 Aug 2014 09:55:45 +0200 Subject: [PATCH 0003/1094] Fixes #1405: No error message for invalid syntax in period_archives.html --- pelican/generators.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index b877d43a..eb822e68 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -29,6 +29,9 @@ from pelican import signals logger = logging.getLogger(__name__) +class PelicanTemplateNotFound(Exception): + pass + @python_2_unicode_compatible class Generator(object): """Baseclass generator""" @@ -88,7 +91,7 @@ class Generator(object): try: self._templates[name] = self.env.get_template(name + '.html') except TemplateNotFound: - raise Exception('[templates] unable to load %s.html from %s' + raise PelicanTemplateNotFound('[templates] unable to load %s.html from %s' % (name, self._templates_path)) return self._templates[name] @@ -330,7 +333,7 @@ class ArticlesGenerator(CachingGenerator): """Generate per-year, per-month, and per-day archives.""" try: template = self.get_template('period_archives') - except Exception: + except PelicanTemplateNotFound: template = self.get_template('archives') period_save_as = { From c92e00f17b808e524543864653e198394c4a3395 Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Thu, 18 Sep 2014 22:31:47 +0200 Subject: [PATCH 0004/1094] Add a new signal: page_writer_finalized --- docs/plugins.rst | 2 ++ pelican/generators.py | 1 + pelican/signals.py | 1 + 3 files changed, 4 insertions(+) diff --git a/docs/plugins.rst b/docs/plugins.rst index 01e67810..57f90ba0 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -93,6 +93,8 @@ page_generator_preread page_generator invoked befor use if code needs to do something before every page is parsed. page_generator_init page_generator invoked in the PagesGenerator.__init__ page_generator_finalized page_generator invoked at the end of PagesGenerator.generate_context +page_writer_finalized page_generator, writer invoked after all pages have been written, but before the page generator + is closed. static_generator_context static_generator, metadata static_generator_preread static_generator invoked before a static file is read in StaticGenerator.generate_context; use if code needs to do something before every static file is added to the diff --git a/pelican/generators.py b/pelican/generators.py index b877d43a..c2383bc6 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -637,6 +637,7 @@ class PagesGenerator(CachingGenerator): self.context, page=page, relative_urls=self.settings['RELATIVE_URLS'], override_output=hasattr(page, 'override_save_as')) + signals.page_writer_finalized.send(self, writer=writer) class StaticGenerator(Generator): diff --git a/pelican/signals.py b/pelican/signals.py index f858c249..bd8c7954 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -25,6 +25,7 @@ article_writer_finalized = signal('article_writer_finalized') page_generator_init = signal('page_generator_init') page_generator_finalized = signal('page_generator_finalized') +page_writer_finalized = signal('page_writer_finalized') static_generator_init = signal('static_generator_init') static_generator_finalized = signal('static_generator_finalized') From 431029664835b8a63dd29a08a8c871472255bf71 Mon Sep 17 00:00:00 2001 From: Robert Utter Date: Wed, 24 Sep 2014 16:19:24 -0700 Subject: [PATCH 0005/1094] Update notmyidea's embedded Twitter icon Twitter's logo has been a bird since 2010. The old T-icon is outdated. --- .../notmyidea/static/images/icons/twitter.png | Bin 830 -> 1509 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pelican/themes/notmyidea/static/images/icons/twitter.png b/pelican/themes/notmyidea/static/images/icons/twitter.png index d0ef3cc1b36ab79ac7931c2269b70f3662950a97..057ab004b5d826e151a4f9cc6a66d88caffa8911 100644 GIT binary patch literal 1509 zcmbVMT}%{L6dqg_sA8nmMy!cU7j3A8ow>X0-^j}T%x{ ziioWWjRg{0+q5?FKw6$ECbfwv%~C-uL8X=08cY)ugvM&sqT&xSy$h`N!T8Y0%-nm= zobNl|IrrRYEXaRz;rv(TV;HtDH-|1nV^ZWvh(qs`357w!GTBuk7YSu@6(d2+&IzRu z&-F9qun;m_^|n5kg<&cmUtA)Wc=9M#@N1cfk2d5N5gNm?vO^-nRzMjqh2?y}s=4_? zScCJNRkP9HAw8lK`uLn02^Q7l7qc}LtcBBLuf?-M6e93LnZZN;t$`pFvT9~{DKw9m z2@O8uB3D>7v!Y5o1-Mg?Aa2m=GguM;+-TH-OoPz~(s3OLbOecRV+P2i3`R<4!sjjx ziY9SoR3Yu2i-lHJjZcXz+$mPICQ!UBM?lDGSD!{ zO1voZLI962GNnSLY}Fv8vnlvRkLL+-AUIbjR5BvOhy>7*gx?>@YsNb$7s979p6MMd zt`;Ft2!leU#G?HuOO1k2?VcYf;)v8x>m?p-3bU0K*h)VP$hovtgT81vo}=6*lN$h2 zCTY@dPkQu+!Ygd+Id8M z@6zpEU0-7> zCNzMljmF{fmV(vZcR=xUfA7mZZNyK?67FFgbXMcFD||oiY#Fr0HN2J6x7+WIJCmM> zt2V@*_`dEO5V$2@id`PMpS(NtP54!>I%NwWJ8NR)znkOJul$o2}{;?h=1vBHI`oJ?AxP1yXC;+srnr`yt@5ZOwPD!W_^V@RU@%6tLx)XO-t3TmGt;uakqXR^1oBArN#$w{Idt7(_nwNIiB0t_-S3do% H!@K8S?9(ld delta 807 zcmV+?1K9lK3%&-BB!2{RLP=Bz2nYy#2xN!=000SaNLh0L01FZT01FZU(%pXi0000S zbVXQnQ*UN;cVTj606}DLVr3vkX>w(EZ*psMAVX6$2C~zj0000LbVXQnLvm$dbZKvH zAXI5>WdJfTFE=kRIeNv-e)>{rfz2>o|Yj zIakM#%UhMb+@bNp@nA+}SPdDo7O9oAGH1DQ=3VCA#gT5=SO4a&tyI0gXBl?Tx)@mh*J>fn>V;M*DO$H1^SFS^3Cch<$pf*yF%!>ZA7BC6gp=e!vZ_-P71 zp2D>q_zdI2D$rmwM2@mERUiSmfZ;i1$#={-A0h(ouYX{B0RLr}3jS#Wk6U=NhWAF1 z-(8%#a2X*GQ&*%;!VOwL1{VU{ox!JTI4SU~g9-4%A-p{@ZWm~ZSQU1I$vc;^EaIey zvkVDH86Ga-c?-eBsl3NUxLu>~LP!n16=I}{&G#C|H$#?(3yDKFI$%htO#ZKU{k0-ZZa!I lYapPVx|%k Date: Wed, 24 Sep 2014 17:01:05 -0700 Subject: [PATCH 0006/1094] Update tests to match new Twitter icon --- .../basic/theme/images/icons/twitter.png | Bin 830 -> 1509 bytes .../custom/theme/images/icons/twitter.png | Bin 830 -> 1509 bytes .../theme/images/icons/twitter.png | Bin 830 -> 1509 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/pelican/tests/output/basic/theme/images/icons/twitter.png b/pelican/tests/output/basic/theme/images/icons/twitter.png index d0ef3cc1b36ab79ac7931c2269b70f3662950a97..057ab004b5d826e151a4f9cc6a66d88caffa8911 100644 GIT binary patch literal 1509 zcmbVMT}%{L6dqg_sA8nmMy!cU7j3A8ow>X0-^j}T%x{ ziioWWjRg{0+q5?FKw6$ECbfwv%~C-uL8X=08cY)ugvM&sqT&xSy$h`N!T8Y0%-nm= zobNl|IrrRYEXaRz;rv(TV;HtDH-|1nV^ZWvh(qs`357w!GTBuk7YSu@6(d2+&IzRu z&-F9qun;m_^|n5kg<&cmUtA)Wc=9M#@N1cfk2d5N5gNm?vO^-nRzMjqh2?y}s=4_? zScCJNRkP9HAw8lK`uLn02^Q7l7qc}LtcBBLuf?-M6e93LnZZN;t$`pFvT9~{DKw9m z2@O8uB3D>7v!Y5o1-Mg?Aa2m=GguM;+-TH-OoPz~(s3OLbOecRV+P2i3`R<4!sjjx ziY9SoR3Yu2i-lHJjZcXz+$mPICQ!UBM?lDGSD!{ zO1voZLI962GNnSLY}Fv8vnlvRkLL+-AUIbjR5BvOhy>7*gx?>@YsNb$7s979p6MMd zt`;Ft2!leU#G?HuOO1k2?VcYf;)v8x>m?p-3bU0K*h)VP$hovtgT81vo}=6*lN$h2 zCTY@dPkQu+!Ygd+Id8M z@6zpEU0-7> zCNzMljmF{fmV(vZcR=xUfA7mZZNyK?67FFgbXMcFD||oiY#Fr0HN2J6x7+WIJCmM> zt2V@*_`dEO5V$2@id`PMpS(NtP54!>I%NwWJ8NR)znkOJul$o2}{;?h=1vBHI`oJ?AxP1yXC;+srnr`yt@5ZOwPD!W_^V@RU@%6tLx)XO-t3TmGt;uakqXR^1oBArN#$w{Idt7(_nwNIiB0t_-S3do% H!@K8S?9(ld delta 807 zcmV+?1K9lK3%&-BB!2{RLP=Bz2nYy#2xN!=000SaNLh0L01FZT01FZU(%pXi0000S zbVXQnQ*UN;cVTj606}DLVr3vkX>w(EZ*psMAVX6$2C~zj0000LbVXQnLvm$dbZKvH zAXI5>WdJfTFE=kRIeNv-e)>{rfz2>o|Yj zIakM#%UhMb+@bNp@nA+}SPdDo7O9oAGH1DQ=3VCA#gT5=SO4a&tyI0gXBl?Tx)@mh*J>fn>V;M*DO$H1^SFS^3Cch<$pf*yF%!>ZA7BC6gp=e!vZ_-P71 zp2D>q_zdI2D$rmwM2@mERUiSmfZ;i1$#={-A0h(ouYX{B0RLr}3jS#Wk6U=NhWAF1 z-(8%#a2X*GQ&*%;!VOwL1{VU{ox!JTI4SU~g9-4%A-p{@ZWm~ZSQU1I$vc;^EaIey zvkVDH86Ga-c?-eBsl3NUxLu>~LP!n16=I}{&G#C|H$#?(3yDKFI$%htO#ZKU{k0-ZZa!I lYapPVx|%kX0-^j}T%x{ ziioWWjRg{0+q5?FKw6$ECbfwv%~C-uL8X=08cY)ugvM&sqT&xSy$h`N!T8Y0%-nm= zobNl|IrrRYEXaRz;rv(TV;HtDH-|1nV^ZWvh(qs`357w!GTBuk7YSu@6(d2+&IzRu z&-F9qun;m_^|n5kg<&cmUtA)Wc=9M#@N1cfk2d5N5gNm?vO^-nRzMjqh2?y}s=4_? zScCJNRkP9HAw8lK`uLn02^Q7l7qc}LtcBBLuf?-M6e93LnZZN;t$`pFvT9~{DKw9m z2@O8uB3D>7v!Y5o1-Mg?Aa2m=GguM;+-TH-OoPz~(s3OLbOecRV+P2i3`R<4!sjjx ziY9SoR3Yu2i-lHJjZcXz+$mPICQ!UBM?lDGSD!{ zO1voZLI962GNnSLY}Fv8vnlvRkLL+-AUIbjR5BvOhy>7*gx?>@YsNb$7s979p6MMd zt`;Ft2!leU#G?HuOO1k2?VcYf;)v8x>m?p-3bU0K*h)VP$hovtgT81vo}=6*lN$h2 zCTY@dPkQu+!Ygd+Id8M z@6zpEU0-7> zCNzMljmF{fmV(vZcR=xUfA7mZZNyK?67FFgbXMcFD||oiY#Fr0HN2J6x7+WIJCmM> zt2V@*_`dEO5V$2@id`PMpS(NtP54!>I%NwWJ8NR)znkOJul$o2}{;?h=1vBHI`oJ?AxP1yXC;+srnr`yt@5ZOwPD!W_^V@RU@%6tLx)XO-t3TmGt;uakqXR^1oBArN#$w{Idt7(_nwNIiB0t_-S3do% H!@K8S?9(ld delta 807 zcmV+?1K9lK3%&-BB!2{RLP=Bz2nYy#2xN!=000SaNLh0L01FZT01FZU(%pXi0000S zbVXQnQ*UN;cVTj606}DLVr3vkX>w(EZ*psMAVX6$2C~zj0000LbVXQnLvm$dbZKvH zAXI5>WdJfTFE=kRIeNv-e)>{rfz2>o|Yj zIakM#%UhMb+@bNp@nA+}SPdDo7O9oAGH1DQ=3VCA#gT5=SO4a&tyI0gXBl?Tx)@mh*J>fn>V;M*DO$H1^SFS^3Cch<$pf*yF%!>ZA7BC6gp=e!vZ_-P71 zp2D>q_zdI2D$rmwM2@mERUiSmfZ;i1$#={-A0h(ouYX{B0RLr}3jS#Wk6U=NhWAF1 z-(8%#a2X*GQ&*%;!VOwL1{VU{ox!JTI4SU~g9-4%A-p{@ZWm~ZSQU1I$vc;^EaIey zvkVDH86Ga-c?-eBsl3NUxLu>~LP!n16=I}{&G#C|H$#?(3yDKFI$%htO#ZKU{k0-ZZa!I lYapPVx|%kX0-^j}T%x{ ziioWWjRg{0+q5?FKw6$ECbfwv%~C-uL8X=08cY)ugvM&sqT&xSy$h`N!T8Y0%-nm= zobNl|IrrRYEXaRz;rv(TV;HtDH-|1nV^ZWvh(qs`357w!GTBuk7YSu@6(d2+&IzRu z&-F9qun;m_^|n5kg<&cmUtA)Wc=9M#@N1cfk2d5N5gNm?vO^-nRzMjqh2?y}s=4_? zScCJNRkP9HAw8lK`uLn02^Q7l7qc}LtcBBLuf?-M6e93LnZZN;t$`pFvT9~{DKw9m z2@O8uB3D>7v!Y5o1-Mg?Aa2m=GguM;+-TH-OoPz~(s3OLbOecRV+P2i3`R<4!sjjx ziY9SoR3Yu2i-lHJjZcXz+$mPICQ!UBM?lDGSD!{ zO1voZLI962GNnSLY}Fv8vnlvRkLL+-AUIbjR5BvOhy>7*gx?>@YsNb$7s979p6MMd zt`;Ft2!leU#G?HuOO1k2?VcYf;)v8x>m?p-3bU0K*h)VP$hovtgT81vo}=6*lN$h2 zCTY@dPkQu+!Ygd+Id8M z@6zpEU0-7> zCNzMljmF{fmV(vZcR=xUfA7mZZNyK?67FFgbXMcFD||oiY#Fr0HN2J6x7+WIJCmM> zt2V@*_`dEO5V$2@id`PMpS(NtP54!>I%NwWJ8NR)znkOJul$o2}{;?h=1vBHI`oJ?AxP1yXC;+srnr`yt@5ZOwPD!W_^V@RU@%6tLx)XO-t3TmGt;uakqXR^1oBArN#$w{Idt7(_nwNIiB0t_-S3do% H!@K8S?9(ld delta 807 zcmV+?1K9lK3%&-BB!2{RLP=Bz2nYy#2xN!=000SaNLh0L01FZT01FZU(%pXi0000S zbVXQnQ*UN;cVTj606}DLVr3vkX>w(EZ*psMAVX6$2C~zj0000LbVXQnLvm$dbZKvH zAXI5>WdJfTFE=kRIeNv-e)>{rfz2>o|Yj zIakM#%UhMb+@bNp@nA+}SPdDo7O9oAGH1DQ=3VCA#gT5=SO4a&tyI0gXBl?Tx)@mh*J>fn>V;M*DO$H1^SFS^3Cch<$pf*yF%!>ZA7BC6gp=e!vZ_-P71 zp2D>q_zdI2D$rmwM2@mERUiSmfZ;i1$#={-A0h(ouYX{B0RLr}3jS#Wk6U=NhWAF1 z-(8%#a2X*GQ&*%;!VOwL1{VU{ox!JTI4SU~g9-4%A-p{@ZWm~ZSQU1I$vc;^EaIey zvkVDH86Ga-c?-eBsl3NUxLu>~LP!n16=I}{&G#C|H$#?(3yDKFI$%htO#ZKU{k0-ZZa!I lYapPVx|%k Date: Sun, 28 Sep 2014 00:33:05 +0800 Subject: [PATCH 0007/1094] add objects details to theme docs --- docs/themes.rst | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/docs/themes.rst b/docs/themes.rst index 4be9a8e5..16453881 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -316,6 +316,108 @@ period A tuple of the form (`year`, `month`, `day`) that You can see an example of how to use `period` in the ``simple`` theme's period_archives.html +Objects +======= + +Detail objects attributes that are available and useful in templates. Not all +attributes are listed here, this is a selection of attributes considered useful +in a template. + +.. _object-article: + +Article +------- + +The string representation of an Article is the `source_path` attribute. + +=================== =================================================== +Attribute Description +=================== =================================================== +author The :ref:`Author ` of + this article. +authors A list of :ref:`Authors ` + of this article. +category The :ref:`Category ` + of this article. +content The rendered content of the article. +date Datetime object representing the article date. +date_format Either default date format or locale date format. +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`. +metadata Article header metadata `dict`. +save_as Location to save the article page. +slug Page slug. +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 ` + objects. +template Template name to use for rendering. +title Title of the article. +translations List of translations + :ref:`Article ` objects. +url URL to the article page. +=================== =================================================== + +.. _object-author_cat_tag: + +Author / Category / Tag +----------------------- + +The string representation of those objects is the `name` attribute. + +=================== =================================================== +Attribute Description +=================== =================================================== +name Name of this object [1]_. +page_name Author page name. +save_as Location to save the author page. +slug Page slug. +url URL to the author page. +=================== =================================================== + +.. [1] for Author object, coming from `:authors:` or `AUTHOR`. + +.. _object-page: + +Page +---- + +The string representation of a Page is the `source_path` attribute. + +=================== =================================================== +Attribute Description +=================== =================================================== +author The :ref:`Author ` of + this page. +content The rendered content of the page. +date Datetime object representing the page date. +date_format Either default date format or locale date format. +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`. +metadata Page header metadata `dict`. +save_as Location to save the page. +slug Page slug. +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 ` + objects. +template Template name to use for rendering. +title Title of the page. +translations List of translations + :ref:`Article ` objects. +url URL to the page. +=================== =================================================== + Feeds ===== From 99e734cb71f26d2a3cafa71a6f6373fbb0c8fda5 Mon Sep 17 00:00:00 2001 From: Jeff Dairiki Date: Thu, 16 Oct 2014 06:56:43 -0700 Subject: [PATCH 0008/1094] Add tests for abbr_role. Refs #949 --- pelican/tests/test_rstdirectives.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 pelican/tests/test_rstdirectives.py diff --git a/pelican/tests/test_rstdirectives.py b/pelican/tests/test_rstdirectives.py new file mode 100644 index 00000000..ae863b30 --- /dev/null +++ b/pelican/tests/test_rstdirectives.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, print_function + +from mock import Mock +from pelican.tests.support import unittest + +class Test_abbr_role(unittest.TestCase): + def call_it(self, text): + from pelican.rstdirectives import abbr_role + rawtext = text + lineno = 42 + inliner = Mock(name='inliner') + nodes, system_messages = abbr_role( + 'abbr', rawtext, text, lineno, inliner) + self.assertEqual(system_messages, []) + self.assertEqual(len(nodes), 1) + return nodes[0] + + def test(self): + node = self.call_it("Abbr (Abbreviation)") + self.assertEqual(node.astext(), "Abbr") + self.assertEqual(node['explanation'], "Abbreviation") + + def test_newlines_in_explanation(self): + node = self.call_it("CUL (See you\nlater)") + self.assertEqual(node.astext(), "CUL") + self.assertEqual(node['explanation'], "See you\nlater") + + def test_newlines_in_abbr(self): + node = self.call_it("US of\nA \n (USA)") + self.assertEqual(node.astext(), "US of\nA") + self.assertEqual(node['explanation'], "USA") From 10c0002af1ac347d378cba6393120c4fec41c19c Mon Sep 17 00:00:00 2001 From: Jeff Dairiki Date: Thu, 16 Oct 2014 06:59:32 -0700 Subject: [PATCH 0009/1094] Fix bug with custom reST :abbr: role. Fixes #949 This fixes things so that newlines are allowed within the explanation of an :abbr: role in reST mark-up. --- pelican/rstdirectives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index 1bf6971c..1c25cc42 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -70,7 +70,7 @@ directives.register_directive('code-block', Pygments) directives.register_directive('sourcecode', Pygments) -_abbr_re = re.compile('\((.*)\)$') +_abbr_re = re.compile('\((.*)\)$', re.DOTALL) class abbreviation(nodes.Inline, nodes.TextElement): From d503ea436c1733c2fc596e5003bac2cb74d576c9 Mon Sep 17 00:00:00 2001 From: Forest Date: Fri, 31 Oct 2014 17:21:15 -0700 Subject: [PATCH 0010/1094] Let documents {attach} static files. Fixes #1019. Until now, making static files end up in the same output directory as an article that links to them has been difficult, especially when the article's output path is generated based on metadata. This changeset introduces the {attach} link syntax, which works like the {filename} syntax, but also overrides the static file's output path with the directory of the linking document. It also clarifies and expands the documentation on linking to internal content. --- docs/content.rst | 176 +++++++++++++++++++++++++++------ pelican/__init__.py | 4 +- pelican/contents.py | 78 ++++++++++++++- pelican/tests/test_contents.py | 147 ++++++++++++++++++++++++++- pelican/tests/test_pelican.py | 8 +- 5 files changed, 372 insertions(+), 41 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index 5a1f3ad0..570a32d1 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -151,24 +151,25 @@ 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* -hierarchy. This makes it easier to link from the current post to other posts -and images that may be sitting alongside the current post (instead of having -to determine where those resources will be placed after site generation). +hierarchy. This makes it easier to link from the current post to other content +that may be sitting alongside that post (instead of having to determine where +the other content will be placed after site generation). To link to internal content (files in the ``content`` directory), use the -following syntax: ``{filename}path/to/file``:: +following syntax for the link target: ``{filename}path/to/file`` +For example, a Pelican project might be structured like this:: website/ ├── content - │   ├── article1.rst - │   ├── cat/ - │   │   └── article2.md + │   ├── category/ + │   │   └── article1.rst + │   ├── article2.md │ └── pages │      └── about.md └── pelican.conf.py -In this example, ``article1.rst`` could look like:: +In this example, ``article1.rst`` could look like this:: The first article ################# @@ -177,8 +178,8 @@ In this example, ``article1.rst`` could look like:: See below intra-site link examples in reStructuredText format. - `a link relative to content root <{filename}/cat/article2.rst>`_ - `a link relative to current file <{filename}cat/article2.rst>`_ + `a link relative to the current file <{filename}../article2.md>`_ + `a link relative to the content root <{filename}/article2.md>`_ and ``article2.md``:: @@ -187,43 +188,154 @@ and ``article2.md``:: See below intra-site link examples in Markdown format. - [a link relative to content root]({filename}/article1.md) - [a link relative to current file]({filename}../article1.md) + [a link relative to the current file]({filename}category/article1.rst) + [a link relative to the content root]({filename}/category/article1.rst) -Embedding non-article or non-page content is slightly different in that the -directories need to be specified in ``pelicanconf.py`` file. The ``images`` -directory is configured for this by default but others will need to be added -manually:: +Linking to static files +----------------------- + +Linking to non-article or non-page content uses the same ``{filename}`` syntax +as described above. It is important to remember that those files will not be +copied to the output directory unless the source directories containing them +are included in the ``STATIC_PATHS`` setting of the project's ``pelicanconf.py`` +file. Pelican's default configuration includes the ``images`` directory for +this, but others must be added manually. Forgetting to do so will result in +broken links. + +For example, a project's content directory might be structured like this:: content ├── images │   └── han.jpg - └── misc -    └── image-test.md + ├── pdfs + │   └── menu.pdf + └── pages +    └── test.md -And ``image-test.md`` would include:: +``test.md`` would include:: ![Alt Text]({filename}/images/han.jpg) + [Our Menu]({filename}/pdfs/menu.pdf) -Any content can be linked in this way. What happens is that the ``images`` -directory gets copied to ``output/`` during site generation because Pelican -includes ``images`` in the ``STATIC_PATHS`` setting's list by default. If -you want to have another directory, say ``pdfs``, copied from your content to -your output during site generation, you would need to add the following to -your settings file:: +``pelicanconf.py`` would include:: STATIC_PATHS = ['images', 'pdfs'] -After the above line has been added, subsequent site generation should copy the -``content/pdfs/`` directory to ``output/pdfs/``. +Site generation would then copy ``han.jpg`` to ``output/images/han.jpg``, +``menu.pdf`` to ``output/pdfs/menu.pdf``, and write the appropriate links +in ``test.md``. -You can also link to categories or tags, using the ``{tag}tagname`` and +Mixed content in the same directory +----------------------------------- + +Starting with Pelican 3.5, static files can safely share a source directory with +page source files, without exposing the page sources in the generated site. +Any such directory must be added to both ``STATIC_PATHS`` and ``PAGE_PATHS`` +(or ``STATIC_PATHS`` and ``ARTICLE_PATHS``). Pelican will identify and process +the page source files normally, and copy the remaining files as if they lived +in a separate directory reserved for static files. + +Note: Placing static and content source files together in the same source +directory does not guarantee that they will end up in the same place in the +generated site. The easiest way to do this is by using the ``{attach}`` link +syntax (described below). Alternatively, the ``STATIC_SAVE_AS``, +``PAGE_SAVE_AS``, and ``ARTICLE_SAVE_AS`` settings (and the corresponding +``*_URL`` settings) can be configured to place files of different types +together, just as they could in earlier versions of Pelican. + +Attaching static files +---------------------- + +Starting with Pelican 3.5, static files can be "attached" to a page or article +using this syntax for the link target: ``{attach}path/to/file`` This works +like the ``{filename}`` syntax, but also relocates the static file into the +linking document's output directory. If the static file originates from a +subdirectory beneath the linking document's source, that relationship will be +preserved on output. Otherwise, it will become a sibling of the linking +document. + +This only works for linking to static files, and only when they originate from +a directory included in the ``STATIC_PATHS`` setting. + +For example, a project's content directory might be structured like this:: + + content + ├── blog + │   ├── icons + │   │   └── icon.png + │   ├── photo.jpg + │   └── testpost.md + └── downloads + └── archive.zip + +``pelicanconf.py`` would include:: + + PATH = 'content' + STATIC_PATHS = ['blog', 'downloads'] + ARTICLE_PATHS = ['blog'] + ARTICLE_SAVE_AS = '{date:%Y}/{slug}.html' + ARTICLE_URL = '{date:%Y}/{slug}.html' + +``testpost.md`` would include:: + + Title: Test Post + Category: test + Date: 2014-10-31 + + ![Icon]({attach}icons/icon.png) + ![Photo]({attach}photo.jpg) + [Downloadable File]({attach}/downloads/archive.zip) + +Site generation would then produce an output directory structured like this:: + + output + └── 2014 + ├── archive.zip + ├── icons + │   └── icon.png + ├── photo.jpg + └── test-post.html + +Notice that all the files linked using ``{attach}`` ended up in or beneath +the article's output directory. + +If a static file is linked multiple times, the relocating feature of +``{attach}`` will only work in the first of those links to be processed. +After the first link, Pelican will treat ``{attach}`` like ``{filename}``. +This avoids breaking the already-processed links. + +**Be careful when linking to a file from multiple documents:** +Since the first link to a file finalizes its location and Pelican does +not define the order in which documents are processed, using ``{attach}`` on a +file linked by multiple documents can cause its location to change from one +site build to the next. (Whether this happens in practice will depend on the +operating system, file system, version of Pelican, and documents being added, +modified, or removed from the project.) Any external sites linking to the +file's old location might then find their links broken. **It is therefore +advisable to use {attach} only if you use it in all links to a file, and only +if the linking documents share a single directory.** Under these conditions, +the file's output location will not change in future builds. In cases where +these precautions are not possible, consider using ``{filename}`` links instead +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 +------------------------------ + +You can link to tags and categories using the ``{tag}tagname`` and ``{category}foobar`` syntax. -For backward compatibility, Pelican also supports bars (``||``) in addition to -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. +Deprecated internal link syntax +------------------------------- + +To remain compatible with earlier versions, Pelican still supports vertical bars +(``||``) in addition to curly braces (``{}``) for internal links. 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. Support for the old syntax may eventually be +removed. + Importing an existing site ========================== diff --git a/pelican/__init__.py b/pelican/__init__.py index 076375ba..d0056ded 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -208,7 +208,9 @@ class Pelican(object): logger.debug('Found generator: %s', v) generators.append(v) - # StaticGenerator runs last so it can see which files the others handle + # StaticGenerator must run last, so it can identify files that + # were skipped by the other generators, and so static files can + # have their output paths overridden by the {attach} link syntax. generators.append(StaticGenerator) return generators diff --git a/pelican/contents.py b/pelican/contents.py index 01f51651..beff2106 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -219,7 +219,7 @@ class Content(object): origin = m.group('path') # XXX Put this in a different location. - if what == 'filename': + if what in {'filename', 'attach'}: if path.startswith('/'): path = path[1:] else: @@ -234,9 +234,16 @@ class Content(object): if unquoted_path in self._context['filenames']: path = unquoted_path - if self._context['filenames'].get(path): - origin = '/'.join((siteurl, - self._context['filenames'][path].url)) + linked_content = self._context['filenames'].get(path) + if linked_content: + if what == 'attach': + if isinstance(linked_content, Static): + linked_content.attach_to(self) + else: + 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)) origin = origin.replace('\\', '/') # for Windows paths. else: logger.warning( @@ -359,6 +366,10 @@ class Quote(Page): @python_2_unicode_compatible class Static(Page): + def __init__(self, *args, **kwargs): + super(Static, self).__init__(*args, **kwargs) + self._output_location_referenced = False + @deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0)) def filepath(): return None @@ -371,6 +382,65 @@ class Static(Page): def dst(): return None + @property + def url(self): + # Note when url has been referenced, so we can avoid overriding it. + self._output_location_referenced = True + return super(Static, self).url + + @property + def save_as(self): + # Note when save_as has been referenced, so we can avoid overriding it. + self._output_location_referenced = True + return super(Static, self).save_as + + 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. + 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): + tail_path = os.path.basename(tail_path) + new_save_as = os.path.join( + os.path.dirname(content.save_as), tail_path) + + # We do not build our new url by joining tail_path with the linking + # document's url, because we cannot know just by looking at the latter + # whether it points to the document itself or to its parent directory. + # (An url like 'some/content' might mean a directory named 'some' + # with a file named 'content', or it might mean a directory named + # '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.", + content.get_relative_source_path(), + self.get_relative_source_path(), reason, + extra={'limit_msg': "More {attach} warnings silenced."}) + + # We never override an override, because we don't want to interfere + # with user-defined overrides that might be in EXTRA_PATH_METADATA. + if hasattr(self, 'override_save_as') or hasattr(self, 'override_url'): + if new_save_as != self.save_as or new_url != self.url: + _log_reason("its output location was already overridden") + return + + # We never change an output path that has already been referenced, + # because we don't want to break links that depend on that path. + if self._output_location_referenced: + if new_save_as != self.save_as or new_url != self.url: + _log_reason("another link already referenced its location") + return + + self.override_save_as = new_save_as + self.override_url = new_url + def is_valid_content(content, f): try: diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index e64b3804..01ee9ca2 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -4,12 +4,13 @@ from __future__ import unicode_literals, absolute_import import six from sys import platform import locale +import os.path from pelican.tests.support import unittest, get_settings -from pelican.contents import Page, Article, URLWrapper +from pelican.contents import Page, Article, Static, URLWrapper from pelican.settings import DEFAULT_CONFIG -from pelican.utils import truncate_html_words, SafeDatetime +from pelican.utils import path_to_url, truncate_html_words, SafeDatetime from pelican.signals import content_object_init from jinja2.utils import generate_lorem_ipsum @@ -401,6 +402,148 @@ class TestArticle(TestPage): self.assertEqual(article.save_as, 'obrien/csharp-stuff/fnord/index.html') +class TestStatic(unittest.TestCase): + + def setUp(self): + + self.settings = get_settings( + STATIC_SAVE_AS='{path}', + STATIC_URL='{path}', + PAGE_SAVE_AS=os.path.join('outpages', '{slug}.html'), + PAGE_URL='outpages/{slug}.html') + self.context = self.settings.copy() + + self.static = Static(content=None, metadata={}, settings=self.settings, + source_path=os.path.join('dir', 'foo.jpg'), context=self.context) + + self.context['filenames'] = {self.static.source_path: self.static} + + def tearDown(self): + pass + + 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, + source_path=os.path.join('dir', 'fakepage.md')) + self.static.attach_to(page) + + expected_save_as = os.path.join('outpages', 'foo.jpg') + self.assertEqual(self.static.save_as, expected_save_as) + self.assertEqual(self.static.url, path_to_url(expected_save_as)) + + def test_attach_to_parent_dir(self): + """attach_to() preserves dirs inside the linking document dir. + """ + page = Page(content="fake page", metadata={'title': 'fakepage'}, + settings=self.settings, source_path='fakepage.md') + self.static.attach_to(page) + + expected_save_as = os.path.join('outpages', 'dir', 'foo.jpg') + self.assertEqual(self.static.save_as, expected_save_as) + self.assertEqual(self.static.url, path_to_url(expected_save_as)) + + def test_attach_to_other_dir(self): + """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')) + self.static.attach_to(page) + + expected_save_as = os.path.join('outpages', 'foo.jpg') + self.assertEqual(self.static.save_as, expected_save_as) + self.assertEqual(self.static.url, path_to_url(expected_save_as)) + + def test_attach_to_ignores_subsequent_calls(self): + """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')) + + self.static.attach_to(page) + + otherdir_settings = self.settings.copy() + 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, + source_path=os.path.join('dir', 'otherpage.md')) + + self.static.attach_to(otherdir_page) + + otherdir_save_as = os.path.join('otherpages', 'foo.jpg') + self.assertNotEqual(self.static.save_as, otherdir_save_as) + self.assertNotEqual(self.static.url, path_to_url(otherdir_save_as)) + + def test_attach_to_does_nothing_after_save_as_referenced(self): + """attach_to() does nothing if the save_as was already referenced. + (For example, by a {filename} link an a document processed earlier.) + """ + original_save_as = self.static.save_as + + page = Page(content="fake page", + metadata={'title': 'fakepage'}, settings=self.settings, + source_path=os.path.join('dir', 'fakepage.md')) + self.static.attach_to(page) + + self.assertEqual(self.static.save_as, original_save_as) + self.assertEqual(self.static.url, path_to_url(original_save_as)) + + def test_attach_to_does_nothing_after_url_referenced(self): + """attach_to() does nothing if the url was already referenced. + (For example, by a {filename} link an a document processed earlier.) + """ + original_url = self.static.url + + page = Page(content="fake page", + metadata={'title': 'fakepage'}, settings=self.settings, + source_path=os.path.join('dir', 'fakepage.md')) + self.static.attach_to(page) + + self.assertEqual(self.static.save_as, self.static.source_path) + self.assertEqual(self.static.url, original_url) + + def test_attach_to_does_not_override_an_override(self): + """attach_to() does not override paths that were overridden elsewhere. + (For example, by the user with EXTRA_PATH_METADATA) + """ + 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", + metadata={'title': 'fakepage'}, settings=self.settings, + source_path=os.path.join('dir', 'fakepage.md')) + + customstatic.attach_to(page) + + self.assertEqual(customstatic.save_as, 'customfoo.jpg') + self.assertEqual(customstatic.url, 'customfoo.jpg') + + def test_attach_link_syntax(self): + """{attach} link syntax triggers output path override & 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, + "{attach} link syntax did not trigger URL replacement.") + + expected_save_as = os.path.join('outpages', 'foo.jpg') + self.assertEqual(self.static.save_as, expected_save_as) + self.assertEqual(self.static.url, path_to_url(expected_save_as)) + + class TestURLWrapper(unittest.TestCase): def test_comparisons(self): # URLWrappers are sorted by name diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 83988d62..190d5e06 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function +import collections import os import sys from tempfile import mkdtemp @@ -77,14 +78,17 @@ class TestPelican(LoggedTestCase): assert not err, err def test_order_of_generators(self): - # StaticGenerator must run last, so it can find files that were - # skipped by the other generators. + # StaticGenerator must run last, so it can identify files that + # were skipped by the other generators, and so static files can + # have their output paths overridden by the {attach} link syntax. pelican = Pelican(settings=read_settings(path=None)) generator_classes = pelican.get_generator_classes() self.assertTrue(generator_classes[-1] is StaticGenerator, "StaticGenerator must be the last generator, but it isn't!") + self.assertIsInstance(generator_classes, collections.Sequence, + "get_generator_classes() must return a Sequence to preserve order") def test_basic_generation_works(self): # when running pelican without settings, it should pick up the default From 306807596346bb42ed72ff9e712f3f6f66751d52 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 4 Nov 2014 19:35:12 -0800 Subject: [PATCH 0011/1094] Update changelog --- docs/changelog.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index d8c33cb5..52367cec 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,34 @@ Release history ############### +3.5.0 (2014-11-04) +================== + +* Introduce ``ARTICLE_ORDER_BY`` and ``PAGE_ORDER_BY`` settings to control the + order of articles and pages. +* Include time zone information in dates rendered in templates. +* Expose the reader name in the metadata for articles and pages. +* Add the ability to store static files along with content in the same + directory as articles and pages using ``{attach}`` in the path. +* Prevent Pelican from raising an exception when there are duplicate pieces of + metadata in a Markdown file. +* Introduce the ``TYPOGRIFY_IGNORE_TAGS`` setting to add HTML tags to be ignored + by Typogrify. +* Add the ability to use ``-`` in date formats to strip leading zeros. For + example, ``%-d/%-m/%y`` will now result in the date ``9/8/12``. +* Ensure feed generation is correctly disabled during quickstart configuration. +* Fix ``PAGE_EXCLUDES`` and ``ARTICLE_EXCLUDES`` from incorrectly matching + sub-directories. +* Introduce ``STATIC_EXCLUDE`` setting to add static file excludes. +* Fix an issue when using ``PAGINATION_PATTERNS`` while ``RELATIVE_URLS`` + is enabled. +* Fix feed generation causing links to use the wrong language for month + names when using other locales. +* Fix an issue where the authors list in the simple template wasn't correctly + formatted. +* Fix an issue when parsing non-string URLs from settings. +* Improve consistency of debug and warning messages. + 3.4.0 (2014-07-01) ================== From febd28a74e8ead5adf7b1148236576723f22ba35 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 4 Nov 2014 19:43:14 -0800 Subject: [PATCH 0012/1094] Increment version to 3.5 --- pelican/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index d0056ded..967360c9 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.5.dev" +__version__ = "3.5.0" DEFAULT_CONFIG_NAME = 'pelicanconf.py' diff --git a/setup.py b/setup.py index c1af72bb..8e4eff34 100755 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ CHANGELOG = open('docs/changelog.rst').read() setup( name="pelican", - version="3.4.0", + version="3.5.0", url='http://getpelican.com/', author='Alexis Metaireau', author_email='authors@getpelican.com', From 4654a4efe40fb23e709005044410eb65a25ae63a Mon Sep 17 00:00:00 2001 From: Jiangge Zhang Date: Mon, 10 Nov 2014 11:40:26 +0800 Subject: [PATCH 0013/1094] fix up the datetime comparison error caused by timezone. "can't compare offset-naive and offset-aware datetimes". --- pelican/contents.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index beff2106..c3d1230b 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -11,6 +11,7 @@ import os import re import sys +import pytz from pelican import signals from pelican.settings import DEFAULT_CONFIG @@ -132,8 +133,12 @@ class Content(object): # manage status if not hasattr(self, 'status'): self.status = settings['DEFAULT_STATUS'] - if not settings['WITH_FUTURE_DATES']: - if hasattr(self, 'date') and self.date > SafeDatetime.now(): + if not settings['WITH_FUTURE_DATES'] and hasattr(self, 'date'): + if self.date.tzinfo is None: + now = SafeDatetime.now() + else: + now = SafeDatetime.utcnow().replace(tzinfo=pytz.utc) + if self.date > now: self.status = 'draft' # store the summary metadata if it is set From 90edf14beb3a44cd814a7f8aad1202e18890e18d Mon Sep 17 00:00:00 2001 From: Sethathi Morokole Date: Tue, 11 Nov 2014 15:26:13 +0200 Subject: [PATCH 0014/1094] unit tests for settings.py --- pelican/tests/test_settings.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 260eff05..9dff1626 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -112,3 +112,22 @@ class TestSettingsConfiguration(unittest.TestCase): configure_settings(self.settings) self.assertEqual(locale.getlocale(), locale.getdefaultlocale()) + + def test_invalid_settings_throw_exception(self): + # Test that the path name is valid + + # test that 'PATH' is set + settings = { + } + + self.assertRaises(Exception, configure_settings, settings) + + # Test that 'PATH' is valid + settings['PATH'] = '' + self.assertRaises(Exception, configure_settings, settings) + + # Test nonexistent THEME + settings['PATH'] = os.curdir + settings['THEME'] = 'foo' + + self.assertRaises(Exception, configure_settings, settings) From 88d19d47b5c1ed74d4a64c396b2e452a1f884161 Mon Sep 17 00:00:00 2001 From: Kernc Date: Mon, 17 Nov 2014 06:54:22 +0100 Subject: [PATCH 0015/1094] Replace underscores in dates with spaces before parsing --- pelican/readers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/readers.py b/pelican/readers.py index 85147e3e..36653306 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -30,7 +30,7 @@ from pelican.utils import get_date, pelican_open, FileStampDataCacher, SafeDatet METADATA_PROCESSORS = { 'tags': lambda x, y: [Tag(tag, y) for tag in x.split(',')], - 'date': lambda x, y: get_date(x), + 'date': lambda x, y: get_date(x.replace('_', ' ')), 'modified': lambda x, y: get_date(x), 'status': lambda x, y: x.strip(), 'category': Category, From 33994f002430f390f4f9939563a4b50be7e076e0 Mon Sep 17 00:00:00 2001 From: OGINO Masanori Date: Tue, 18 Nov 2014 19:46:25 +0900 Subject: [PATCH 0016/1094] Add missing names in a list of the feed variables. Signed-off-by: OGINO Masanori --- docs/themes.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/themes.rst b/docs/themes.rst index c9fc2b37..198f8334 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -341,6 +341,8 @@ Here is a complete list of the feed variables:: FEED_ALL_RSS CATEGORY_FEED_ATOM CATEGORY_FEED_RSS + AUTHOR_FEED_ATOM + AUTHOR_FEED_RSS TAG_FEED_ATOM TAG_FEED_RSS TRANSLATION_FEED_ATOM From b3b2e845337c0df900235bf573566855562fe055 Mon Sep 17 00:00:00 2001 From: Sebastian Gumprich Date: Tue, 18 Nov 2014 13:37:27 +0000 Subject: [PATCH 0017/1094] Added documentation for default draft-status of articles --- docs/content.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/content.rst b/docs/content.rst index fa65ac5d..4de480ba 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -498,6 +498,13 @@ 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. +If your articles should be automatically published as a draft (to not accidentally +publish an article before it is finished) include the status in the ``DEFAULT_METADATA``:: + + DEFAULT_METADATA = { + 'status': 'draft', + } + .. _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 From 5006dc95d2c86044a5519d0e2e5eb09ad0516c72 Mon Sep 17 00:00:00 2001 From: George Angelopoulos Date: Tue, 25 Nov 2014 17:38:37 +0200 Subject: [PATCH 0018/1094] docs: fix table formating in importer.rst --- docs/importer.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/importer.rst b/docs/importer.rst index 309ca144..aa3fa935 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -47,10 +47,11 @@ Usage Positional arguments -------------------- - - input The input file to read - api_token [Posterous only] api_token can be obtained from http://posterous.com/api/ - api_key [Tumblr only] api_key can be obtained from http://www.tumblr.com/oauth/apps + ============= ============================================================================ + ``input`` The input file to read + ``api_token`` (Posterous only) api_token can be obtained from http://posterous.com/api/ + ``api_key`` (Tumblr only) api_key can be obtained from http://www.tumblr.com/oauth/apps + ============= ============================================================================ Optional arguments ------------------ From 8cc2f99ec413002d10f154304a002040722ea3cc Mon Sep 17 00:00:00 2001 From: "George V. Reilly" Date: Fri, 28 Nov 2014 15:41:10 -0800 Subject: [PATCH 0019/1094] Fabfile improvements - Remove gratuitous Unixisms so that fabfile will work on Windows - Docstrings for tasks so `fab --list` is more useful. - Add `gh_pages` task for publishing to GitHub Pages using [ghp-import](https://github.com/davisp/ghp-import) --- pelican/tools/templates/fabfile.py.in | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/pelican/tools/templates/fabfile.py.in b/pelican/tools/templates/fabfile.py.in index e693bb48..a8ab586b 100644 --- a/pelican/tools/templates/fabfile.py.in +++ b/pelican/tools/templates/fabfile.py.in @@ -1,6 +1,7 @@ from fabric.api import * import fabric.contrib.project as project import os +import shutil import sys import SimpleHTTPServer import SocketServer @@ -18,26 +19,35 @@ env.cloudfiles_username = '$cloudfiles_username' env.cloudfiles_api_key = '$cloudfiles_api_key' env.cloudfiles_container = '$cloudfiles_container' +# Github Pages configuration +env.github_pages_branch = "gh-pages" + +# Port for `serve` +PORT = 8000 def clean(): + """Remove generated files""" if os.path.isdir(DEPLOY_PATH): - local('rm -rf {deploy_path}'.format(**env)) - local('mkdir {deploy_path}'.format(**env)) + shutil.rmtree(DEPLOY_PATH) + os.makedirs(DEPLOY_PATH) def build(): + """Build local version of site""" local('pelican -s pelicanconf.py') def rebuild(): + """`clean` then `build`""" clean() build() def regenerate(): + """Automatically regenerate site upon file modification""" local('pelican -r -s pelicanconf.py') def serve(): + """Serve site at http://localhost:8000/""" os.chdir(env.deploy_path) - PORT = 8000 class AddressReuseTCPServer(SocketServer.TCPServer): allow_reuse_address = True @@ -47,22 +57,26 @@ def serve(): server.serve_forever() def reserve(): + """`build`, then `serve`""" build() serve() def preview(): + """Build production version of site""" local('pelican -s publishconf.py') def cf_upload(): + """Publish to Rackspace Cloud Files""" rebuild() - local('cd {deploy_path} && ' - 'swift -v -A https://auth.api.rackspacecloud.com/v1.0 ' - '-U {cloudfiles_username} ' - '-K {cloudfiles_api_key} ' - 'upload -c {cloudfiles_container} .'.format(**env)) + with lcd(DEPLOY_PATH): + local('swift -v -A https://auth.api.rackspacecloud.com/v1.0 ' + '-U {cloudfiles_username} ' + '-K {cloudfiles_api_key} ' + 'upload -c {cloudfiles_container} .'.format(**env)) @hosts(production) def publish(): + """Publish to production via rsync""" local('pelican -s publishconf.py') project.rsync_project( remote_dir=dest_path, @@ -71,3 +85,9 @@ def publish(): delete=True, extra_opts='-c', ) + +def gh_pages(): + """Publish to GitHub Pages""" + rebuild() + local("ghp-import -b {github_pages_branch} {deploy_path}".format(**env)) + local("git push origin {github_pages_branch}".format(**env)) From bbbb3247eaa1104750dd3b196deeb3df2ce14266 Mon Sep 17 00:00:00 2001 From: Midhul Varma Date: Wed, 24 Dec 2014 01:29:32 +0530 Subject: [PATCH 0020/1094] Add timezone prompt to quickstart. Refs 1337. --- pelican/tools/pelican_quickstart.py | 23 ++++++++++++++++++++++- pelican/tools/templates/pelicanconf.py.in | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index b24a508c..74633630 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -9,6 +9,7 @@ import string import argparse import sys import codecs +import pytz from pelican import __version__ @@ -39,9 +40,13 @@ CONF = { 'github_pages_branch': _GITHUB_PAGES_BRANCHES['project'], 'default_pagination': 10, 'siteurl': '', - 'lang': 'en' + 'lang': 'en', + 'timezone': 'Europe/Paris' } +#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) @@ -155,6 +160,20 @@ def ask(question, answer=str_compat, default=None, l=None): raise NotImplemented('Argument `answer` must be str_compat, bool, or integer') +def ask_timezone(question, default, tzurl): + """Prompt for time zone and validate input""" + lower_tz = map(str.lower, pytz.all_timezones) + while True: + r = ask(question, str_compat, default) + r = r.strip().replace(' ', '_').lower() + if r in lower_tz: + r = pytz.all_timezones[lower_tz.index(r)] + break + else: + print('Please enter a valid time zone:\n (check [{0}])'.format(tzurl)) + return r + + def main(): parser = argparse.ArgumentParser( description="A kickstarter for Pelican", @@ -203,6 +222,8 @@ needed by Pelican. else: CONF['default_pagination'] = False + 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) diff --git a/pelican/tools/templates/pelicanconf.py.in b/pelican/tools/templates/pelicanconf.py.in index e9570c4a..05d1a323 100644 --- a/pelican/tools/templates/pelicanconf.py.in +++ b/pelican/tools/templates/pelicanconf.py.in @@ -8,7 +8,7 @@ SITEURL = '' PATH = 'content' -TIMEZONE = 'Europe/Paris' +TIMEZONE = $timezone DEFAULT_LANG = $lang From 49668f711a451e6fa2caa67a938d5e4b12172a51 Mon Sep 17 00:00:00 2001 From: Elana Hashman Date: Fri, 2 Jan 2015 00:03:18 -0700 Subject: [PATCH 0021/1094] Generate {tag}-style links on pages correctly. Fixes #1513 --- pelican/contents.py | 4 +- .../page_with_category_and_tag_links.md | 7 ++++ pelican/tests/test_contents.py | 38 ++++++++++++++++--- pelican/tests/test_generators.py | 23 +++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 pelican/tests/TestPages/page_with_category_and_tag_links.md diff --git a/pelican/contents.py b/pelican/contents.py index c3d1230b..69c01438 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -257,9 +257,9 @@ class Content(object): 'limit_msg': ("Other resources were not found " "and their urls not replaced")}) elif what == 'category': - origin = Category(path, self.settings).url + origin = '/'.join((siteurl, Category(path, self.settings).url)) elif what == 'tag': - origin = Tag(path, self.settings).url + origin = '/'.join((siteurl, Tag(path, self.settings).url)) # keep all other parts, such as query, fragment, etc. parts = list(value) diff --git a/pelican/tests/TestPages/page_with_category_and_tag_links.md b/pelican/tests/TestPages/page_with_category_and_tag_links.md new file mode 100644 index 00000000..6806a570 --- /dev/null +++ b/pelican/tests/TestPages/page_with_category_and_tag_links.md @@ -0,0 +1,7 @@ +Title: Page with a bunch of links + +My links: + +[Link 1]({tag}マック) + +[Link 2]({category}Yeah) diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 01ee9ca2..de297985 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -212,17 +212,20 @@ class TestPage(unittest.TestCase): 'link') page = Page(**args) content = page.get_content('http://notmyidea.org') - self.assertEqual(content, ('A simple test, with a ' - 'link')) + self.assertEqual( + content, + ('A simple test, with a ' + 'link')) # Category args['content'] = ('A simple test, with a ' 'link') page = Page(**args) content = page.get_content('http://notmyidea.org') - self.assertEqual(content, - ('A simple test, with a ' - 'link')) + self.assertEqual( + content, + ('A simple test, with a ' + 'link')) def test_intrasite_link(self): # type does not take unicode in PY2 and bytes in PY3, which in @@ -543,6 +546,31 @@ class TestStatic(unittest.TestCase): self.assertEqual(self.static.save_as, expected_save_as) self.assertEqual(self.static.url, path_to_url(expected_save_as)) + def test_tag_link_syntax(self): + "{tag} 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_category_link_syntax(self): + "{category} 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) + class TestURLWrapper(unittest.TestCase): def test_comparisons(self): diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 4be1b35e..c3e36bc1 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -428,6 +428,7 @@ class TestPageGenerator(unittest.TestCase): ['This is a markdown test page', 'published', 'page'], ['This is a test page with a preset template', 'published', 'custom'], + ['Page with a bunch of links', 'published', 'page'], ['A Page (Test) for sorting', 'published', 'page'], ] hidden_pages_expected = [ @@ -517,6 +518,7 @@ class TestPageGenerator(unittest.TestCase): ['This is a test page', 'published', 'page'], ['This is a markdown test page', 'published', 'page'], ['A Page (Test) for sorting', 'published', 'page'], + ['Page with a bunch of links', 'published', 'page'], ['This is a test page with a preset template', 'published', 'custom'], ] @@ -530,6 +532,7 @@ class TestPageGenerator(unittest.TestCase): # sort by title pages_expected_sorted_by_title = [ ['A Page (Test) for sorting', 'published', 'page'], + ['Page with a bunch of links', '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', @@ -543,6 +546,26 @@ class TestPageGenerator(unittest.TestCase): pages = self.distill_pages(generator.pages) self.assertEqual(pages_expected_sorted_by_title, pages) + def test_tag_and_category_links_on_generated_pages(self): + """ + Test to ensure links of the form {tag}tagname and {category}catname + are generated correctly on pages + """ + settings = get_settings(filenames={}) + settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR + settings['CACHE_PATH'] = self.temp_cache + settings['DEFAULT_DATE'] = (1970, 1, 1) + + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CUR_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + pages_by_title = {p.title: p.content for p in generator.pages} + + test_content = pages_by_title['Page with a bunch of links'] + self.assertIn('', test_content) + self.assertIn('', test_content) + class TestTemplatePagesGenerator(unittest.TestCase): From 0360ce1865bfe7fb2046b8a998017e06d2f65508 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Fri, 2 Jan 2015 01:11:51 -0800 Subject: [PATCH 0022/1094] Use Shields.io as source for Travis badge Using Shields.io as the source for both the Travis and Coveralls badges keeps styling consistent between the two. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 58896f1d..564cc77c 100644 --- a/README.rst +++ b/README.rst @@ -55,7 +55,7 @@ See our `contribution submission and feedback guidelines `_. .. _`Pelican documentation`: http://docs.getpelican.com/ .. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html -.. |build-status| image:: https://travis-ci.org/getpelican/pelican.svg?branch=master +.. |build-status| image:: https://img.shields.io/travis/getpelican/pelican/master.svg :target: https://travis-ci.org/getpelican/pelican :alt: Travis CI: continuous integration status .. |coverage-status| image:: https://img.shields.io/coveralls/getpelican/pelican.svg From 3763f96011b617f5b33dc375fe81d3344fb8d8c7 Mon Sep 17 00:00:00 2001 From: winlu Date: Mon, 5 Jan 2015 11:50:48 +0100 Subject: [PATCH 0023/1094] add comment to 'preview your site' section mentioning the http server module for python3 is different then python --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 4fe75d98..122d65b5 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -64,7 +64,7 @@ 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 + python -m SimpleHTTPServer # -m http.server if you use python3 Preview your site by navigating to http://localhost:8000/ in your browser. From 5432dc04a64c9282ef35fd6017047f5215a00dfd Mon Sep 17 00:00:00 2001 From: winlu Date: Mon, 5 Jan 2015 12:07:01 +0100 Subject: [PATCH 0024/1094] fix double definition of simple theme link to different targets --- docs/themes.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/themes.rst b/docs/themes.rst index 198f8334..25926893 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -325,6 +325,7 @@ period A tuple of the form (`year`, `month`, `day`) that =================== =================================================== You can see an example of how to use `period` in the `"simple" theme +period_archives.html template `_. From 30c4d0eaf38f13f442946be29243f881e0c5e1a7 Mon Sep 17 00:00:00 2001 From: winlu Date: Mon, 5 Jan 2015 12:22:00 +0100 Subject: [PATCH 0025/1094] fix whitespace --- docs/content.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index 4de480ba..12bd887f 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -502,9 +502,9 @@ If your articles should be automatically published as a draft (to not accidental publish an article before it is finished) include the status in the ``DEFAULT_METADATA``:: DEFAULT_METADATA = { - 'status': 'draft', - } - + 'status': 'draft', + } + .. _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 From 49481f60a54eed144b2d09d510e203f03fcdc2d4 Mon Sep 17 00:00:00 2001 From: winlu Date: Mon, 5 Jan 2015 12:36:35 +0100 Subject: [PATCH 0026/1094] fix headers, this fixes issues with docs/contributing.rst throwing errors because of the different content structure --- CONTRIBUTING.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 85d30c97..862cf7a7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,5 +1,5 @@ Filing issues -------------- +============= * Before you file an issue, try `asking for help`_ first. * If determined to file an issue, first check for `existing issues`_, including @@ -9,7 +9,7 @@ Filing issues .. _`existing issues`: https://github.com/getpelican/pelican/issues How to get help ---------------- +=============== Before you ask for help, please make sure you do the following: @@ -66,7 +66,7 @@ Once the above preparation is ready, you can contact people willing to help via 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 @@ -81,7 +81,7 @@ The #pelican IRC channel 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 @@ -89,7 +89,7 @@ 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 -.................... +-------------------- * `Create a new git branch`_ specific to your change (as opposed to making your commits in the master branch). @@ -116,7 +116,7 @@ Using Git and GitHub turn your GitHub issue into a pull request containing your code. Contribution quality standards -.............................. +------------------------------ * Adhere to `PEP8 coding standards`_ whenever possible. This can be eased via the `pep8 `_ or `flake8 From 3d8ceb1c67e577225cf739b31a326d827e06d9b9 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Sat, 10 Jan 2015 15:40:54 -0800 Subject: [PATCH 0027/1094] Adding ability to listen on addresses other than localhost. This is helpful for mobile testing of Pelican sites by allowing broadcasting on the local network. Using port 80 requires running as root on most machines. --- pelican/server.py | 9 ++--- pelican/tools/templates/Makefile.in | 51 +++++++++++++++++------------ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/pelican/server.py b/pelican/server.py index d4518acd..cfe3d2b0 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -12,7 +12,8 @@ try: except ImportError: import socketserver # NOQA -PORT = len(sys.argv) == 2 and int(sys.argv[1]) or 8000 +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'] @@ -38,13 +39,13 @@ Handler = ComplexHTTPRequestHandler socketserver.TCPServer.allow_reuse_address = True try: - httpd = socketserver.TCPServer(("", PORT), Handler) + httpd = socketserver.TCPServer((SERVER, PORT), Handler) except OSError as e: - logging.error("Could not listen on port %s", PORT) + logging.error("Could not listen on port %s, server %s", PORT, SERVER) sys.exit(getattr(e, 'exitcode', 1)) -logging.info("Serving at port %s", PORT) +logging.info("Serving at port %s, server %s", PORT, SERVER) try: httpd.serve_forever() except KeyboardInterrupt as e: diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 8534595e..18722175 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -32,26 +32,27 @@ ifeq ($(DEBUG), 1) endif help: - @echo 'Makefile for a pelican Web site ' - @echo ' ' - @echo 'Usage: ' - @echo ' make html (re)generate the web site ' - @echo ' make clean remove the generated files ' - @echo ' make regenerate regenerate files upon modification ' - @echo ' make publish generate using production settings ' - @echo ' make serve [PORT=8000] serve site at http://localhost:8000' - @echo ' make devserver [PORT=8000] start/restart develop_server.sh ' - @echo ' make stopserver stop local server ' - @echo ' make ssh_upload upload the web site via SSH ' - @echo ' make rsync_upload upload the web site via rsync+ssh ' - @echo ' make dropbox_upload upload the web site via Dropbox ' - @echo ' make ftp_upload upload the web site via FTP ' - @echo ' make s3_upload upload the web site via S3 ' - @echo ' make cf_upload upload the web site via Cloud Files' - @echo ' make github upload the web site via gh-pages ' - @echo ' ' - @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html' - @echo ' ' + @echo 'Makefile for a pelican Web site ' + @echo ' ' + @echo 'Usage: ' + @echo ' make html (re)generate the web site ' + @echo ' make clean remove the generated files ' + @echo ' make regenerate regenerate files upon modification ' + @echo ' make publish generate using production settings ' + @echo ' make serve [PORT=8000] serve site at http://localhost:8000' + @echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 ' + @echo ' make devserver [PORT=8000] start/restart develop_server.sh ' + @echo ' make stopserver stop local server ' + @echo ' make ssh_upload upload the web site via SSH ' + @echo ' make rsync_upload upload the web site via rsync+ssh ' + @echo ' make dropbox_upload upload the web site via Dropbox ' + @echo ' make ftp_upload upload the web site via FTP ' + @echo ' make s3_upload upload the web site via S3 ' + @echo ' make cf_upload upload the web site via Cloud Files' + @echo ' make github upload the web site via gh-pages ' + @echo ' ' + @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html ' + @echo ' ' html: $$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS) @@ -69,6 +70,14 @@ else cd $$(OUTPUTDIR) && $(PY) -m pelican.server endif +serve-global: +ifdef SERVER + cd $$(OUTPUTDIR) && $(PY) -m pelican.server 80 $$(SERVER) +else + cd $$(OUTPUTDIR) && $(PY) -m pelican.server 80 0.0.0.0 +endif + + devserver: ifdef PORT $$(BASEDIR)/develop_server.sh restart $$(PORT) @@ -106,4 +115,4 @@ github: publish 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 +.PHONY: html help clean regenerate serve serve-global devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github From cff5f3e77485c86ad97c2e8cca8d5f1e8c3f9751 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 11 Jan 2015 18:54:15 -0800 Subject: [PATCH 0028/1094] Add custom 404 page to Tips section of docs --- docs/tips.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/tips.rst b/docs/tips.rst index eb400124..fdd57398 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -3,6 +3,32 @@ Tips Here are some tips about Pelican that you might find useful. +Custom 404 Pages +================ + +When a browser requests a resource that the web server cannot find, the web +server usually displays a generic "File not found" (404) error page that can be +stark and unsightly. One way to provide an error page that matches the theme +of your site is to create a custom 404 page, such as this Markdown-formatted +example:: + + Title: Not Found + Status: hidden + Save_as: 404.html + + The requested item could not be located. Perhaps you might want to check + the [Archives](/archives.html)? + +The next step is to configure your web server to display this custom page +instead of its default 404 page. For Nginx, add the following to your +configuration file's ``location`` block:: + + error_page 404 /404.html; + +For Apache:: + + ErrorDocument 404 /404.html + Publishing to GitHub ==================== From 38c307eefdd32e77162775b52cca7f577e86177c Mon Sep 17 00:00:00 2001 From: Shrayas Date: Sat, 10 Jan 2015 00:31:01 +0530 Subject: [PATCH 0029/1094] Add link to GitHub docs on custom 404 pages --- docs/tips.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/tips.rst b/docs/tips.rst index fdd57398..69e1b9ba 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -88,6 +88,12 @@ by the ``ghp-import`` command) to the ``elemoine.github.io`` repository's To publish your Pelican site as User Pages, feel free to adjust the ``github`` target of the Makefile. +Custom 404 Pages +---------------- + +GitHub Pages will display the custom 404 page described above, as noted in the +relevant `GitHub docs `_. + Extra Tips ---------- @@ -125,3 +131,4 @@ Moreover, markup languages like reST and Markdown have plugins that let you embed videos in the markup. You can use `reST video directive `_ for reST or `mdx_video plugin `_ for Markdown. + From bf7966183a1931e1a906525566b75f93723d1419 Mon Sep 17 00:00:00 2001 From: Shrayas Date: Mon, 12 Jan 2015 09:14:09 +0530 Subject: [PATCH 0030/1094] Changing dates in copyright information --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index d4efcb38..4ec0f832 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.extlinks' source_suffix = '.rst' master_doc = 'index' project = 'Pelican' -copyright = '2014, Alexis Metaireau and contributors' +copyright = '2015, Alexis Metaireau and contributors' exclude_patterns = ['_build'] release = __version__ version = '.'.join(release.split('.')[:1]) From 79548c3bf90b5a5ea2de3f6e1ca7c61169eb484c Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Mon, 15 Dec 2014 11:05:02 -0800 Subject: [PATCH 0031/1094] Minor changes to server.py - Log original path name rather than last path name - Use for-else construct to avoid use of flag variable - Make log messages consistent --- pelican/server.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/pelican/server.py b/pelican/server.py index cfe3d2b0..0a8dc1b6 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -19,21 +19,25 @@ SUFFIXES = ['', '.html', '/index.html'] class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): def do_GET(self): - # we are trying to detect the file by having a fallback mechanism - found = False + # Try to detect file by applying various suffixes for suffix in SUFFIXES: - if not hasattr(self,'original_path'): + if not hasattr(self, 'original_path'): self.original_path = self.path + self.path = self.original_path + suffix path = self.translate_path(self.path) + if os.path.exists(path): srvmod.SimpleHTTPRequestHandler.do_GET(self) - logging.info("Found: %s" % self.path) - found = True + logging.info("Found `%s`." % self.path) break - logging.info("Tried to find file %s, but it doesn't exist. ", self.path) - if not found: - logging.warning("Unable to find file %s or variations.", self.path) + + logging.info("Tried to find `%s`, but it doesn't exist.", + self.path) + else: + # Fallback if there were no matches + logging.warning("Unable to find `%s` or variations.", + self.original_path) Handler = ComplexHTTPRequestHandler @@ -41,13 +45,13 @@ 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) + 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) +logging.info("Serving at port %s, server %s.", PORT, SERVER) try: httpd.serve_forever() except KeyboardInterrupt as e: - logging.info("Shutting down server") + logging.info("Shutting down server.") httpd.socket.close() From a62f9ab23ca5b113bc9546aac52e39de55844e04 Mon Sep 17 00:00:00 2001 From: winlu Date: Sat, 17 Jan 2015 16:37:11 +0100 Subject: [PATCH 0032/1094] add argparse argument for relative urls --- pelican/__init__.py | 6 ++++++ pelican/tools/templates/Makefile.in | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/pelican/__init__.py b/pelican/__init__.py index 967360c9..8d774ffe 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -281,6 +281,11 @@ def parse_arguments(): help='Relaunch pelican each time a modification occurs' ' on the content files.') + parser.add_argument('--relative-urls', dest='relative_paths', + action='store_true', + help='Use relative urls in output, ' + 'useful for site development') + parser.add_argument('--cache-path', dest='cache_path', help=('Directory in which to store cache files. ' 'If not specified, defaults to "cache".')) @@ -314,6 +319,7 @@ 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 config['DEBUG'] = args.verbosity == logging.DEBUG # argparse returns bytes in Py2. There is no definite answer as to which diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 18722175..5e3635c3 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -31,6 +31,11 @@ ifeq ($(DEBUG), 1) PELICANOPTS += -D endif +RELATIVE ?= 0 +ifeq ($(RELATIVE), 1) + PELICANOPTS += --relative-urls +endif + help: @echo 'Makefile for a pelican Web site ' @echo ' ' @@ -52,6 +57,7 @@ help: @echo ' make github upload the web site via gh-pages ' @echo ' ' @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html ' + @echo 'Set the RELATIVE variable to 1 to enable relative urls ' @echo ' ' html: From 8aa9a9329edc90933b38de6a4fd4686199bbc090 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Sat, 17 Jan 2015 16:19:37 -0800 Subject: [PATCH 0033/1094] Clarify that 'fs' date setting is a string --- docs/content.rst | 2 +- docs/settings.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index 12bd887f..b4292e69 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -91,7 +91,7 @@ via the ``keywords`` metadata, as is standard in HTML. The two can be used interchangeably. Note that, aside from the title, none of this article metadata is mandatory: -if the date is not specified and ``DEFAULT_DATE`` is set to ``fs``, Pelican +if the date is not specified and ``DEFAULT_DATE`` is set to ``'fs'``, Pelican will rely on the file's "mtime" timestamp, and the category can be determined by the directory in which the file resides. For example, a file located at ``python/foobar/myfoobar.rst`` will have a category of ``foobar``. If you would diff --git a/docs/settings.rst b/docs/settings.rst index 02f4359f..a0a41c29 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -58,7 +58,7 @@ Setting name (followed by default value, if any) template. Templates may or not honor this setting. ``DEFAULT_DATE = None`` The default date you want to use. - If ``fs``, Pelican will use the file system + If ``'fs'``, Pelican will use the file system timestamp information (mtime) if it can't get date information from the metadata. If set to a tuple object, the default datetime object will instead From 88ad46fd4dc0001fe51472a23b26b0e951424ca0 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Sat, 17 Jan 2015 16:20:39 -0800 Subject: [PATCH 0034/1094] Change documented type of various settings - DEFUALT_METADATA, tuple -> dictionary - OUTPUT_RETENTION, tuple -> list - JINJA_FILTERS, list -> dictionary - DIRECT_TEMPLATES, tuple -> list --- docs/settings.rst | 23 +++++++++++------------ pelican/settings.py | 8 ++++---- pelican/tests/default_conf.py | 2 +- samples/pelican.conf.py | 2 +- samples/pelican.conf_FR.py | 2 +- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index a0a41c29..d7ba85cd 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -64,12 +64,11 @@ Setting name (followed by default value, if any) If set to a tuple object, the default datetime object will instead be generated by passing the tuple to the ``datetime.datetime`` constructor. -``DEFAULT_METADATA = ()`` The default metadata you want to use for all articles - and pages. +``DEFAULT_METADATA = {}`` The default metadata you want to use for all articles and pages. ``DOCUTILS_SETTINGS = {}`` Extra configuration settings for the docutils publisher - (applicable only to reStructuredText). See `Docutils + (applicable only to reStructuredText). See `Docutils Configuration`_ settings for more details. - + ``FILENAME_METADATA =`` ``'(?P\d{4}-\d{2}-\d{2}).*'`` The regexp that will be used to extract any metadata from the filename. All named groups that are matched will be set in the metadata object. @@ -90,11 +89,11 @@ Setting name (followed by default value, if any) generating new files. This can be useful in preventing older, unnecessary files from persisting in your output. However, **this is a destructive setting and should be handled with extreme care.** -``OUTPUT_RETENTION = ()`` A tuple of filenames that should be retained and not deleted from the +``OUTPUT_RETENTION = []`` A list of filenames that should be retained and not deleted from the output directory. One use case would be the preservation of version - control data. For example: ``(".hg", ".git", ".bzr")`` + control data. For example: ``[".hg", ".git", ".bzr"]`` ``JINJA_EXTENSIONS = []`` A list of any Jinja2 extensions you want to use. -``JINJA_FILTERS = {}`` A list of custom Jinja2 filters you want to use. +``JINJA_FILTERS = {}`` A dictionary of custom Jinja2 filters you want to use. The dictionary should map the filtername to the filter function. For example: ``{'urlencode': urlencode_filter}`` See `Jinja custom filters documentation`_. @@ -171,12 +170,12 @@ Setting name (followed by default value, if any) ``TYPOGRIFY_IGNORE_TAGS = []`` A list of tags for Typogrify to ignore. By default Typogrify will ignore ``pre`` and ``code`` tags. This requires that Typogrify version 2.0.4 or later is installed -``DIRECT_TEMPLATES =`` ``('index', 'categories', 'authors', 'archives')`` List of templates that are used directly to render +``DIRECT_TEMPLATES =`` ``['index', 'categories', 'authors', 'archives']`` List of templates that are used directly to render content. Typically direct templates are used to generate index pages for collections of content (e.g., tags and category index pages). If the tag and category collections - are not needed, set ``DIRECT_TEMPLATES = ('index', 'archives')`` -``PAGINATED_DIRECT_TEMPLATES = ('index',)`` Provides the direct templates that should be paginated. + are not needed, set ``DIRECT_TEMPLATES = ['index', 'archives']`` +``PAGINATED_DIRECT_TEMPLATES = ['index']`` Provides the direct templates that should be paginated. ``SUMMARY_MAX_LENGTH = 50`` When creating a short summary of an article, this will be the default length (measured in words) of the text created. This only applies if your content does not otherwise @@ -352,7 +351,7 @@ posts for the month at ``posts/2011/Aug/index.html``. arrive at an appropriate archive of posts, without having to specify a page name. -``DIRECT_TEMPLATES``, which are ``('index', 'tags', 'categories', 'archives')`` +``DIRECT_TEMPLATES``, which are ``['index', 'tags', 'categories', 'archives']`` by default, work a bit differently than noted above. Only the ``_SAVE_AS`` settings are available, but it is available for any direct template. @@ -394,7 +393,7 @@ Date format and locale If no ``DATE_FORMATS`` are set, Pelican will fall back to ``DEFAULT_DATE_FORMAT``. If you need to maintain multiple languages with different date formats, you can set the ``DATE_FORMATS`` dictionary using the -language name (``lang`` metadata in your post content) as the key. +language name (``lang`` metadata in your post content) as the key. In addition to the standard C89 strftime format codes that are listed in `Python strftime documentation`_, you can use ``-`` character between ``%`` and diff --git a/pelican/settings.py b/pelican/settings.py index 794733d7..a3d791b0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -61,7 +61,7 @@ DEFAULT_CONFIG = { 'NEWEST_FIRST_ARCHIVES': True, 'REVERSE_CATEGORY_ORDER': False, 'DELETE_OUTPUT_DIRECTORY': False, - 'OUTPUT_RETENTION': (), + 'OUTPUT_RETENTION': [], 'ARTICLE_URL': '{slug}.html', 'ARTICLE_SAVE_AS': '{slug}.html', 'ARTICLE_ORDER_BY': 'slug', @@ -97,9 +97,9 @@ DEFAULT_CONFIG = { 'DEFAULT_LANG': 'en', 'TAG_CLOUD_STEPS': 4, 'TAG_CLOUD_MAX_ITEMS': 100, - 'DIRECT_TEMPLATES': ('index', 'tags', 'categories', 'authors', 'archives'), + 'DIRECT_TEMPLATES': ['index', 'tags', 'categories', 'authors', 'archives'], 'EXTRA_TEMPLATES_PATHS': [], - 'PAGINATED_DIRECT_TEMPLATES': ('index', ), + 'PAGINATED_DIRECT_TEMPLATES': ['index'], 'PELICAN_CLASS': 'pelican.Pelican', 'DEFAULT_DATE_FORMAT': '%a %d %B %Y', 'DATE_FORMATS': {}, @@ -110,7 +110,7 @@ DEFAULT_CONFIG = { 'LOCALE': [''], # defaults to user locale 'DEFAULT_PAGINATION': False, 'DEFAULT_ORPHANS': 0, - 'DEFAULT_METADATA': (), + 'DEFAULT_METADATA': {}, 'FILENAME_METADATA': '(?P\d{4}-\d{2}-\d{2}).*', 'PATH_METADATA': '', 'EXTRA_PATH_METADATA': {}, diff --git a/pelican/tests/default_conf.py b/pelican/tests/default_conf.py index 62594894..b4f532d4 100644 --- a/pelican/tests/default_conf.py +++ b/pelican/tests/default_conf.py @@ -26,7 +26,7 @@ SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), ('github', 'http://github.com/ametaireau'),) # global metadata to all the contents -DEFAULT_METADATA = (('yeah', 'it is'),) +DEFAULT_METADATA = {'yeah': 'it is'} # path-specific metadata EXTRA_PATH_METADATA = { diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py index d6a87923..98ae078a 100755 --- a/samples/pelican.conf.py +++ b/samples/pelican.conf.py @@ -32,7 +32,7 @@ SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), ('github', 'http://github.com/ametaireau'),) # global metadata to all the contents -DEFAULT_METADATA = (('yeah', 'it is'),) +DEFAULT_METADATA = {'yeah': 'it is'} # path-specific metadata EXTRA_PATH_METADATA = { diff --git a/samples/pelican.conf_FR.py b/samples/pelican.conf_FR.py index 0c4ec60d..54b348c4 100644 --- a/samples/pelican.conf_FR.py +++ b/samples/pelican.conf_FR.py @@ -36,7 +36,7 @@ SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), ('github', 'http://github.com/ametaireau'),) # global metadata to all the contents -DEFAULT_METADATA = (('yeah', 'it is'),) +DEFAULT_METADATA = {'yeah': 'it is'} # path-specific metadata EXTRA_PATH_METADATA = { From 737db1d641c6d17cdf2700912c413eb2b6dd4ba6 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Wed, 21 Jan 2015 18:06:04 -0500 Subject: [PATCH 0035/1094] Fix static path watchers There was an issue with static path watchers, where they were watching wrong paths. They need to be prefixed with the 'content' path. So, they were not working at all. It was also possible to overwrite default watchers like 'content', 'settings' and 'theme' by mistake if any of them were present in `STATIC_PATHS`. This is fixed by adding a prefix to static watchers. And static watchers were "too static", meaning, they stayed the same even if `STATIC_PATHS` was changed in the settings during autoreload. Now static watchers reflect those changes (i.e. new paths are added to watch list, and removed ones are no longer watched). --- pelican/__init__.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 967360c9..53025e71 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -359,8 +359,13 @@ def main(): pelican.ignore_files), 'settings': file_watcher(args.settings)} - for static_path in settings.get("STATIC_PATHS", []): - watchers[static_path] = folder_watcher(static_path, [''], pelican.ignore_files) + old_static = settings.get("STATIC_PATHS", []) + for static_path in old_static: + # use a prefix to avoid possible overriding of standard watchers above + watchers['[static]%s' % static_path] = folder_watcher( + os.path.join(pelican.path, static_path), + [''], + pelican.ignore_files) try: if args.autoreload: @@ -386,6 +391,29 @@ def main(): original_load_cache = settings['LOAD_CONTENT_CACHE'] _ignore_cache(pelican) + # Adjust static watchers if there are any changes + new_static = settings.get("STATIC_PATHS", []) + + # Added static paths + # Add new watchers and set them as modified + for static_path in set(new_static).difference(old_static): + static_key = '[static]%s' % static_path + watchers[static_key] = folder_watcher( + os.path.join(pelican.path, static_path), + [''], + pelican.ignore_files) + modified[static_key] = next(watchers[static_key]) + + # Removed static paths + # Remove watchers and modified values + for static_path in set(old_static).difference(new_static): + static_key = '[static]%s' % static_path + watchers.pop(static_key) + modified.pop(static_key) + + # Replace old_static with the new one + old_static = new_static + if any(modified.values()): print('\n-> Modified: {}. re-generating...'.format( ', '.join(k for k, v in modified.items() if v))) From 4c25610cd88d3c2936fec22baa7f37ead7660606 Mon Sep 17 00:00:00 2001 From: "George V. Reilly" Date: Fri, 2 Jan 2015 23:45:44 -0800 Subject: [PATCH 0036/1094] Fix Pelican rendering and unit tests on Windows. * Fix {filename} links on Windows. Otherwise '{filename}/foo/bar.jpg' doesn't work * Clean up relative Posix path handling in contents. * Use Posix paths in readers * Environment for Popen must be strs, not unicodes. * Ignore Git CRLF warnings. * Replace CRLFs with LFs in inputs on Windows. * Fix importer tests * Fix test_contents * Fix one last backslash in paginated output * Skip the remaining failing locale tests on Windows. * Document the use of forward slashes on Windows. * Add some Fabric and ghp-import notes --- docs/content.rst | 3 +++ docs/publish.rst | 13 +++++++++++++ docs/tips.rst | 16 ++++++++++++++-- pelican/contents.py | 20 +++++++++++--------- pelican/paginator.py | 2 +- pelican/readers.py | 4 ++-- pelican/settings.py | 25 +++++++++++++------------ pelican/tests/test_contents.py | 4 ++-- pelican/tests/test_importer.py | 9 ++++----- pelican/tests/test_pelican.py | 28 ++++++++++++++-------------- pelican/tests/test_settings.py | 3 +++ pelican/tests/test_utils.py | 4 +++- pelican/tools/pelican_import.py | 3 ++- pelican/utils.py | 22 +++++++++++++++++++--- 14 files changed, 104 insertions(+), 52 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index 4de480ba..84bfcbe7 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -157,6 +157,9 @@ the other content will be placed after site generation). To link to internal content (files in the ``content`` directory), use the following syntax for the link target: ``{filename}path/to/file`` +Note: forward slashes, ``/``, +are the required path separator in the ``{filename}`` directive +on all operating systems, including Windows. For example, a Pelican project might be structured like this:: diff --git a/docs/publish.rst b/docs/publish.rst index fea053bf..70d93e59 100644 --- a/docs/publish.rst +++ b/docs/publish.rst @@ -100,6 +100,18 @@ separately. Use the following command to install Fabric, prefixing with pip install Fabric +.. note:: Installing PyCrypto on Windows + + Fabric depends upon PyCrypto_, which is tricky to install + if your system doesn't have a C compiler. + For Windows users, before installing Fabric, use + ``easy_install http://www.voidspace.org.uk/downloads/pycrypto26/pycrypto-2.6.win32-py2.7.exe`` + per this `StackOverflow suggestion `_ + You're more likely to have success + with the Win32 versions of Python 2.7 and PyCrypto, + than with the Win64—\ + even if your operating system is a 64-bit version of Windows. + 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 @@ -179,3 +191,4 @@ executables, such as ``python3``, you can set the ``PY`` and ``PELICAN`` environment variables, respectively, to override the default executable names.) .. _Fabric: http://fabfile.org/ +.. _PyCrypto: http://pycrypto.org diff --git a/docs/tips.rst b/docs/tips.rst index eb400124..9db08900 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -36,9 +36,15 @@ already exist). The ``git push origin gh-pages`` command updates the remote ``gh-pages`` branch, effectively publishing the Pelican site. .. note:: + The ``github`` target of the Makefile (and the ``gh_pages`` task of the Fabfile) + created by the ``pelican-quickstart`` command + publishes the Pelican site as Project Pages, as described above. - The ``github`` target of the Makefile created by the ``pelican-quickstart`` - command publishes the Pelican site as Project Pages, as described above. +.. note:: ghp-import on Windows + + Until `ghp-import Pull Request #25 `_ + is accepted, you will need to install a custom build of ghp-import: + ``pip install https://github.com/chevah/ghp-import/archive/win-support.zip`` User Pages ---------- @@ -86,6 +92,12 @@ output directory. For example:: STATIC_PATHS = ['images', 'extra/CNAME'] EXTRA_PATH_METADATA = {'extra/CNAME': {'path': 'CNAME'},} +Note: use forward slashes, ``/``, even on Windows. + +.. hint:: + You can also use the ``EXTRA_PATH_METADATA`` mechanism + to place a ``favicon.ico`` or ``robots.txt`` at the root of any site. + How to add YouTube or Vimeo Videos ================================== diff --git a/pelican/contents.py b/pelican/contents.py index 69c01438..074c28be 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -17,7 +17,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, set_date_tzinfo, SafeDatetime) + path_to_url, posixize_path, 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 @@ -337,17 +337,19 @@ class Content(object): if source_path is None: return None - return os.path.relpath( - os.path.abspath(os.path.join(self.settings['PATH'], source_path)), - os.path.abspath(self.settings['PATH']) - ) + return posixize_path( + os.path.relpath( + os.path.abspath(os.path.join(self.settings['PATH'], source_path)), + os.path.abspath(self.settings['PATH']) + )) @property def relative_dir(self): - return os.path.dirname(os.path.relpath( - os.path.abspath(self.source_path), - os.path.abspath(self.settings['PATH'])) - ) + return posixize_path( + os.path.dirname( + os.path.relpath( + os.path.abspath(self.source_path), + os.path.abspath(self.settings['PATH'])))) class Page(Content): diff --git a/pelican/paginator.py b/pelican/paginator.py index 3f5cce47..0189ec91 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -136,7 +136,7 @@ class Page(object): # URL or SAVE_AS is a string, format it with a controlled context context = { - 'name': self.name, + 'name': self.name.replace(os.sep, '/'), 'object_list': self.object_list, 'number': self.number, 'paginator': self.paginator, diff --git a/pelican/readers.py b/pelican/readers.py index 85147e3e..4de28793 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -25,7 +25,7 @@ from six.moves.html_parser import HTMLParser from pelican import signals from pelican.contents import Page, Category, Tag, Author -from pelican.utils import get_date, pelican_open, FileStampDataCacher, SafeDatetime +from pelican.utils import get_date, pelican_open, FileStampDataCacher, SafeDatetime, posixize_path METADATA_PROCESSORS = { @@ -424,7 +424,7 @@ class Readers(FileStampDataCacher): """Return a content object parsed with the given format.""" path = os.path.abspath(os.path.join(base_path, path)) - source_path = os.path.relpath(path, base_path) + source_path = posixize_path(os.path.relpath(path, base_path)) logger.debug('Read file %s -> %s', source_path, content_class.__name__) diff --git a/pelican/settings.py b/pelican/settings.py index 794733d7..6f9d8d8f 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -18,6 +18,7 @@ except ImportError: load_source = imp.load_source from os.path import isabs +from pelican.utils import posix_join from pelican.log import LimitFilter @@ -41,11 +42,11 @@ DEFAULT_CONFIG = { 'STATIC_EXCLUDE_SOURCES': True, 'THEME_STATIC_DIR': 'theme', 'THEME_STATIC_PATHS': ['static', ], - 'FEED_ALL_ATOM': os.path.join('feeds', 'all.atom.xml'), - 'CATEGORY_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'), - 'AUTHOR_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'), - 'AUTHOR_FEED_RSS': os.path.join('feeds', '%s.rss.xml'), - 'TRANSLATION_FEED_ATOM': os.path.join('feeds', 'all-%s.atom.xml'), + 'FEED_ALL_ATOM': posix_join('feeds', 'all.atom.xml'), + 'CATEGORY_FEED_ATOM': posix_join('feeds', '%s.atom.xml'), + 'AUTHOR_FEED_ATOM': posix_join('feeds', '%s.atom.xml'), + 'AUTHOR_FEED_RSS': posix_join('feeds', '%s.rss.xml'), + 'TRANSLATION_FEED_ATOM': posix_join('feeds', 'all-%s.atom.xml'), 'FEED_MAX_ITEMS': '', 'SITEURL': '', 'SITENAME': 'A Pelican Blog', @@ -68,25 +69,25 @@ DEFAULT_CONFIG = { 'ARTICLE_LANG_URL': '{slug}-{lang}.html', 'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html', 'DRAFT_URL': 'drafts/{slug}.html', - 'DRAFT_SAVE_AS': os.path.join('drafts', '{slug}.html'), + 'DRAFT_SAVE_AS': posix_join('drafts', '{slug}.html'), 'DRAFT_LANG_URL': 'drafts/{slug}-{lang}.html', - 'DRAFT_LANG_SAVE_AS': os.path.join('drafts', '{slug}-{lang}.html'), + 'DRAFT_LANG_SAVE_AS': posix_join('drafts', '{slug}-{lang}.html'), 'PAGE_URL': 'pages/{slug}.html', - 'PAGE_SAVE_AS': os.path.join('pages', '{slug}.html'), + 'PAGE_SAVE_AS': posix_join('pages', '{slug}.html'), 'PAGE_ORDER_BY': 'basename', 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html', - 'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'), + 'PAGE_LANG_SAVE_AS': posix_join('pages', '{slug}-{lang}.html'), 'STATIC_URL': '{path}', 'STATIC_SAVE_AS': '{path}', 'PDF_GENERATOR': False, 'PDF_STYLE_PATH': '', 'PDF_STYLE': 'twelvepoint', 'CATEGORY_URL': 'category/{slug}.html', - 'CATEGORY_SAVE_AS': os.path.join('category', '{slug}.html'), + 'CATEGORY_SAVE_AS': posix_join('category', '{slug}.html'), 'TAG_URL': 'tag/{slug}.html', - 'TAG_SAVE_AS': os.path.join('tag', '{slug}.html'), + 'TAG_SAVE_AS': posix_join('tag', '{slug}.html'), 'AUTHOR_URL': 'author/{slug}.html', - 'AUTHOR_SAVE_AS': os.path.join('author', '{slug}.html'), + 'AUTHOR_SAVE_AS': posix_join('author', '{slug}.html'), 'PAGINATION_PATTERNS': [ (0, '{name}{number}{extension}', '{name}{number}{extension}'), ], diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index de297985..4b692e29 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -10,7 +10,7 @@ from pelican.tests.support import unittest, get_settings from pelican.contents import Page, Article, Static, URLWrapper from pelican.settings import DEFAULT_CONFIG -from pelican.utils import path_to_url, truncate_html_words, SafeDatetime +from pelican.utils import path_to_url, truncate_html_words, SafeDatetime, posix_join from pelican.signals import content_object_init from jinja2.utils import generate_lorem_ipsum @@ -417,7 +417,7 @@ class TestStatic(unittest.TestCase): self.context = self.settings.copy() self.static = Static(content=None, metadata={}, settings=self.settings, - source_path=os.path.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} diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 65193bf5..8c6e3ae6 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -9,7 +9,7 @@ from pelican.tools.pelican_import import wp2fields, fields2pelican, decode_wp_co from pelican.tests.support import (unittest, temporary_folder, mute, skipIfNoExecutable) -from pelican.utils import slugify +from pelican.utils import slugify, path_to_file_url CUR_DIR = os.path.abspath(os.path.dirname(__file__)) WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml') @@ -293,12 +293,11 @@ class TestWordpressXMLAttachements(unittest.TestCase): def test_download_attachments(self): real_file = os.path.join(CUR_DIR, 'content/article.rst') - good_url = 'file://' + real_file + good_url = path_to_file_url(real_file) bad_url = 'http://localhost:1/not_a_file.txt' silent_da = mute()(download_attachments) with temporary_folder() as temp: - #locations = download_attachments(temp, [good_url, bad_url]) locations = list(silent_da(temp, [good_url, bad_url])) - self.assertTrue(len(locations) == 1) + self.assertEqual(1, len(locations)) directory = locations[0] - self.assertTrue(directory.endswith('content/article.rst')) + self.assertTrue(directory.endswith(os.path.join('content', 'article.rst')), directory) diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 190d5e06..62322355 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -58,22 +58,22 @@ class TestPelican(LoggedTestCase): locale.setlocale(locale.LC_ALL, self.old_locale) super(TestPelican, self).tearDown() - def assertFilesEqual(self, diff): - msg = ("some generated files differ from the expected functional " - "tests output.\n" - "This is probably because the HTML generated files " - "changed. If these changes are normal, please refer " - "to docs/contribute.rst to update the expected " - "output of the functional tests.") - - self.assertEqual(diff['left_only'], [], msg=msg) - self.assertEqual(diff['right_only'], [], msg=msg) - self.assertEqual(diff['diff_files'], [], msg=msg) - def assertDirsEqual(self, left_path, right_path): out, err = subprocess.Popen( - ['git', 'diff', '--no-ext-diff', '--exit-code', '-w', left_path, right_path], env={'PAGER': ''}, - stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + ['git', 'diff', '--no-ext-diff', '--exit-code', '-w', left_path, right_path], + env={b'PAGER': b''}, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ).communicate() + def ignorable_git_crlf_errors(line): + # Work around for running tests on Windows + for msg in [ + "LF will be replaced by CRLF", + "The file will have its original line endings"]: + if msg in line: + return True + return False + if err: + err = '\n'.join([l for l in err.decode('utf8').splitlines() + if not ignorable_git_crlf_errors(l)]) assert not out, out assert not err, err diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 260eff05..2c5c1541 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -3,6 +3,7 @@ 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 pelican.settings import (read_settings, configure_settings, @@ -107,6 +108,8 @@ class TestSettingsConfiguration(unittest.TestCase): # locale is not specified in the settings #reset locale to python default + if platform == 'win32': + return unittest.skip("Doesn't work on Windows") locale.setlocale(locale.LC_ALL, str('C')) self.assertEqual(self.settings['LOCALE'], DEFAULT_CONFIG['LOCALE']) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 7c9e6e5a..18a0f4ca 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -301,7 +301,7 @@ class TestUtils(LoggedTestCase): old_locale = locale.setlocale(locale.LC_TIME) if platform == 'win32': - locale.setlocale(locale.LC_TIME, str('Turkish')) + return unittest.skip("Doesn't work on Windows") else: locale.setlocale(locale.LC_TIME, str('tr_TR.UTF-8')) @@ -471,6 +471,8 @@ class TestDateFormatter(unittest.TestCase): locale_available('French'), 'French locale needed') def test_french_strftime(self): + if platform == 'win32': + return unittest.skip("Doesn't work on Windows") # This test tries to reproduce an issue that occurred with python3.3 under macos10 only locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8')) date = utils.SafeDatetime(2014,8,14) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index d0531c42..c7b16e92 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -588,7 +588,8 @@ def download_attachments(output_path, urls): filename = path.pop(-1) localpath = '' for item in path: - localpath = os.path.join(localpath, item) + if sys.platform != 'win32' or ':' not in item: + localpath = os.path.join(localpath, item) full_path = os.path.join(output_path, localpath) if not os.path.exists(full_path): os.makedirs(full_path) diff --git a/pelican/utils.py b/pelican/utils.py index f216b027..caac8e61 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -11,6 +11,7 @@ import os import pytz import re import shutil +import sys import traceback import pickle import hashlib @@ -23,6 +24,7 @@ from functools import partial from itertools import groupby from jinja2 import Markup from operator import attrgetter +from posixpath import join as posix_join logger = logging.getLogger(__name__) @@ -230,13 +232,15 @@ def get_date(string): @contextmanager -def pelican_open(filename): +def pelican_open(filename, mode='rb', strip_crs=(sys.platform == 'win32')): """Open a file and return its content""" - with codecs.open(filename, encoding='utf-8') as infile: + with codecs.open(filename, mode, encoding='utf-8') as infile: content = infile.read() if content[0] == codecs.BOM_UTF8.decode('utf8'): content = content[1:] + if strip_crs: + content = content.replace('\r\n', '\n') yield content @@ -370,6 +374,13 @@ def path_to_url(path): return '/'.join(split_all(path)) +def posixize_path(rel_path): + """Use '/' as path separator, so that source references, + like '{filename}/foo/bar.jpg' or 'extras/favicon.ico', + will work on Windows as well as on Mac and Linux.""" + return rel_path.replace(os.sep, '/') + + def truncate_html_words(s, num, end_text='...'): """Truncates HTML to a certain number of words. @@ -750,4 +761,9 @@ def is_selected_for_writing(settings, path): return path in settings['WRITE_SELECTED'] else: return True - + + +def path_to_file_url(path): + '''Convert file-system path to file:// URL''' + return six.moves.urllib_parse.urljoin( + "file://", six.moves.urllib.request.pathname2url(path)) From f7a5d06c6d821e9ab70e5b1bc49f1de7b8899fed Mon Sep 17 00:00:00 2001 From: Louis Tiao Date: Tue, 27 Jan 2015 18:42:34 +0800 Subject: [PATCH 0037/1094] Removed `PDF_GENERATOR=False` It might give the impression that the PDF generation is still part of the core. --- samples/pelican.conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py index d6a87923..bb499464 100755 --- a/samples/pelican.conf.py +++ b/samples/pelican.conf.py @@ -11,7 +11,6 @@ RELATIVE_URLS = True GITHUB_URL = 'http://github.com/ametaireau/' DISQUS_SITENAME = "blog-notmyidea" -PDF_GENERATOR = False REVERSE_CATEGORY_ORDER = True LOCALE = "C" DEFAULT_PAGINATION = 4 From ca2a426a837ce55d844f7deae2196846cb00ef0b Mon Sep 17 00:00:00 2001 From: Louis Tiao Date: Tue, 27 Jan 2015 18:45:10 +0800 Subject: [PATCH 0038/1094] Removed PDF Generation settings Since PDF Generation is no longer a core feature, these settings are irrelevant. If the `pdf` plugin from pelican-plugins is used, it disregards the `PDF_GENERATOR` setting anyways. --- pelican/settings.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pelican/settings.py b/pelican/settings.py index 794733d7..e924eedd 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -78,9 +78,6 @@ DEFAULT_CONFIG = { 'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'), 'STATIC_URL': '{path}', 'STATIC_SAVE_AS': '{path}', - 'PDF_GENERATOR': False, - 'PDF_STYLE_PATH': '', - 'PDF_STYLE': 'twelvepoint', 'CATEGORY_URL': 'category/{slug}.html', 'CATEGORY_SAVE_AS': os.path.join('category', '{slug}.html'), 'TAG_URL': 'tag/{slug}.html', From 4e2e00b450fcdea256d1e2ecd6868fa7295de49f Mon Sep 17 00:00:00 2001 From: Annika Backstrom Date: Sun, 1 Feb 2015 16:42:51 -0500 Subject: [PATCH 0039/1094] Docs: Note how posts are published when 'draft' is default --- docs/content.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/content.rst b/docs/content.rst index 12bd887f..1ff8ff9c 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -505,6 +505,9 @@ publish an article before it is finished) include the status in the ``DEFAULT_ME 'status': 'draft', } +To publish a post when the default status is ``draft``, update the post's +metadata to include ``Status: published``. + .. _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 From 77231e97c05a65cf2c2c408008b51b822cb679af Mon Sep 17 00:00:00 2001 From: Arul Date: Sun, 1 Feb 2015 14:01:35 +0530 Subject: [PATCH 0040/1094] wordpress importer support for draft article --- pelican/tests/test_importer.py | 4 ++-- pelican/tools/pelican_import.py | 34 +++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 65193bf5..22a7cf7f 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -254,13 +254,13 @@ class TestBuildHeader(unittest.TestCase): def test_galleries_added_to_header(self): header = build_header('test', None, None, None, None, - None, ['output/test1', 'output/test2']) + 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, - ['output/test1', 'output/test2']) + attachments=['output/test1', 'output/test2']) self.assertEqual(header, 'Title: test\n' + 'Attachments: output/test1, ' + 'output/test2\n\n') diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index d0531c42..5cbef7ae 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -126,7 +126,7 @@ def wp2fields(xml, wp_custpost=False): items = get_items(xml) for item in items: - if item.find('status').string == "publish": + if item.find('status').string in ["publish", "draft"]: try: # Use HTMLParser due to issues with BeautifulSoup 3 @@ -149,6 +149,8 @@ def wp2fields(xml, wp_custpost=False): # caturl = [cat['nicename'] for cat in item.find(domain='category')] 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 kind = 'article' post_type = item.find('post_type').string @@ -165,7 +167,7 @@ def wp2fields(xml, wp_custpost=False): pass else: kind = post_type - yield (title, content, filename, date, author, categories, tags, + yield (title, content, filename, date, author, categories, tags, status, kind, "wp-html") def dc2fields(file): @@ -296,9 +298,10 @@ def dc2fields(file): post_format = "html" kind = 'article' # TODO: Recognise pages + status = 'published' # TODO: Find a way for draft posts yield (post_title, content, slugify(post_title), post_creadt, author, - categories, tags, kind, post_format) + categories, tags, status, kind, post_format) def posterous2fields(api_token, email, password): @@ -347,9 +350,10 @@ def posterous2fields(api_token, email, password): date_object -= delta 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, kind, "html") + post.get('user').get('display_name'), [], tags, status, kind, "html") def tumblr2fields(api_key, blogname): @@ -426,8 +430,10 @@ def tumblr2fields(api_key, blogname): content = content.rstrip() + '\n' kind = 'article' + status = 'published' # TODO: Find a way for draft posts + yield (title, content, slug, date, post.get('blog_name'), [type], - tags, kind, format) + tags, status, kind, format) offset += len(posts) posts = get_tumblr_posts(api_key, blogname, offset) @@ -444,10 +450,10 @@ def feed2fields(file): slug = slugify(entry.title) kind = 'article' - yield (entry.title, entry.description, slug, date, author, [], tags, + yield (entry.title, entry.description, slug, date, author, [], tags, None, kind, "html") -def build_header(title, date, author, categories, tags, slug, attachments=None): +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""" @@ -462,13 +468,15 @@ def build_header(title, date, author, categories, tags, slug, attachments=None): header += ':tags: %s\n' % ', '.join(tags) if slug: header += ':slug: %s\n' % slug + if status: + header += ':status: %s\n' % status if attachments: header += ':attachments: %s\n' % ', '.join(attachments) header += '\n' return header -def build_markdown_header(title, date, author, categories, tags, slug, - 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: @@ -481,6 +489,8 @@ def build_markdown_header(title, date, author, categories, tags, slug, header += 'Tags: %s\n' % ', '.join(tags) if slug: header += 'Slug: %s\n' % slug + if status: + header += 'Status: %s\n' % status if attachments: header += 'Attachments: %s\n' % ', '.join(attachments) header += '\n' @@ -606,7 +616,7 @@ 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): - for (title, content, filename, date, author, categories, tags, + for (title, content, filename, date, author, categories, tags, status, kind, in_markup) in fields: if filter_author and filter_author != author: continue @@ -624,11 +634,11 @@ 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, attached_files) + tags, slug, status, attached_files) else: out_markup = "rst" header = build_header(title, date, author, categories, - tags, slug, attached_files) + tags, slug, status, attached_files) out_filename = get_out_filename(output_path, filename, ext, kind, dirpage, dircat, categories, wp_custpost) From f56b699d5b0551c686b1c6aa2df1d59364068306 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 6 Feb 2015 14:30:02 -0800 Subject: [PATCH 0041/1094] Fix quotes in faq --- docs/faq.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index c749c262..86f12462 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -144,8 +144,8 @@ your home page. The following Markdown example could be stored in Thank you for visiting. Welcome! If the original blog index is still wanted, it can then be saved in a -different location by setting ``INDEX_SAVE_AS = 'blog_index.html`` for -the ``''index'`` direct template. +different location by setting ``INDEX_SAVE_AS = 'blog_index.html'`` for +the ``'index'`` direct template. What if I want to disable feed generation? ========================================== From 265f68a879b7d2b1cc5d611f7998ace07ee4d608 Mon Sep 17 00:00:00 2001 From: Kernc Date: Tue, 3 Feb 2015 04:06:22 +0100 Subject: [PATCH 0042/1094] Add signal: all_generators_finalized (getpelican/pelican-plugins#314) Some plugins have used `content_object_init` signal and read `summary` or `content` properties of the content object. This resulted in internal (`{filename}`) links being unresolved. When used, this signal should hopefully mitigate that. See also: * https://github.com/getpelican/pelican-plugins/issues/314 * https://github.com/getpelican/pelican-plugins/pull/410 --- docs/plugins.rst | 12 ++++++++++++ pelican/__init__.py | 2 ++ pelican/signals.py | 1 + 3 files changed, 15 insertions(+) diff --git a/docs/plugins.rst b/docs/plugins.rst index 15fcf109..5135e315 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -85,6 +85,7 @@ finalized pelican object invoked after - minifying js/css assets. - notify/ping search engines with an updated sitemap. generator_init generator invoked in the Generator.__init__ +all_generators_finalized generators invoked after all the generators are executed and before writing output readers_init readers invoked in the Readers.__init__ article_generator_context article_generator, metadata article_generator_preread article_generator invoked before a article is read in ArticlesGenerator.generate_context; @@ -138,6 +139,15 @@ request if you need them! def register(): signals.content_object_init.connect(test, sender=contents.Article) +.. warning:: + + Avoid ``content_object_init`` signal if you intend to read ``summary`` + or ``content`` properties of the content object. That combination can + result in unresolved links when :ref:`ref-linking-to-internal-content` + (see `pelican-plugins bug #314`_). Use ``_summary`` and ``_content`` + properties instead, or, alternatively, run your plugin at a later + stage (e.g. ``all_generators_finalized``). + .. note:: After Pelican 3.2, signal names were standardized. Older plugins @@ -222,3 +232,5 @@ Adding a new generator is also really easy. You might want to have a look at return MyGenerator signals.get_generators.connect(get_generators) + +.. _pelican-plugins bug #314: https://github.com/getpelican/pelican-plugins/issues/314 diff --git a/pelican/__init__.py b/pelican/__init__.py index 53025e71..3039d35c 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -168,6 +168,8 @@ class Pelican(object): if hasattr(p, 'generate_context'): p.generate_context() + signals.all_generators_finalized.send(generators) + writer = self.get_writer() for p in generators: diff --git a/pelican/signals.py b/pelican/signals.py index f858c249..a4d99688 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -6,6 +6,7 @@ from blinker import signal initialized = signal('pelican_initialized') get_generators = signal('get_generators') +all_generators_finalized = signal('all_generators_finalized') get_writer = signal('get_writer') finalized = signal('pelican_finalized') From 8218923625160c20416e92e256e52b3560b3929b Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 9 Feb 2015 08:22:30 -0800 Subject: [PATCH 0043/1094] Add pandoc package to Travis CI configuration --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 54dcf0ea..a052252b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ python: - "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 From 954c85a59343213195e537262d68009d649e8e70 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Mon, 9 Feb 2015 17:11:45 -0800 Subject: [PATCH 0044/1094] Make PagesGenerator status check case-insensitive Fixes #1620. --- pelican/generators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 5122fa6d..97e453b4 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -641,9 +641,9 @@ class PagesGenerator(CachingGenerator): self.add_source_path(page) - if page.status == "published": + if page.status.lower() == "published": all_pages.append(page) - elif page.status == "hidden": + elif page.status.lower() == "hidden": hidden_pages.append(page) else: logger.error("Unknown status '%s' for file %s, skipping it.", From 7fc6aab99a011489b181e526872fb75ea98c2c06 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 10 Feb 2015 20:59:57 -0500 Subject: [PATCH 0045/1094] Fix for tests that were broken in #1607 --- pelican/tests/test_importer.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 22a7cf7f..04bfbbd4 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -5,6 +5,7 @@ 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) @@ -41,7 +42,7 @@ class TestWordpressXmlImporter(unittest.TestCase): def test_ignore_empty_posts(self): self.assertTrue(self.posts) - for title, content, fname, date, author, categ, tags, kind, format in self.posts: + for title, content, fname, date, author, categ, tags, status, kind, format in self.posts: self.assertTrue(title.strip()) def test_recognise_page_kind(self): @@ -49,7 +50,7 @@ class TestWordpressXmlImporter(unittest.TestCase): 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, kind, format in self.posts: + 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)) @@ -85,7 +86,7 @@ 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, kind, format in self.posts: + for title, content, fname, date, author, categ, tags, status, kind, format in self.posts: if kind == 'page' or kind == 'article': pass else: @@ -95,7 +96,7 @@ class TestWordpressXmlImporter(unittest.TestCase): def test_recognise_custom_post_type(self): self.assertTrue(self.custposts) cust_data = [] - for title, content, fname, date, author, categ, tags, kind, format in self.custposts: + for title, content, fname, date, author, categ, tags, status, kind, format in self.custposts: if kind == 'article' or kind == 'page': pass else: @@ -110,7 +111,7 @@ class TestWordpressXmlImporter(unittest.TestCase): test_posts = [] for post in self.custposts: # check post kind - if post[7] == 'article' or post[7] == 'page': + if post[8] == 'article' or post[8] == 'page': pass else: test_posts.append(post) @@ -119,7 +120,7 @@ class TestWordpressXmlImporter(unittest.TestCase): index = 0 for post in test_posts: name = post[2] - kind = post[7] + kind = post[8] name += '.md' filename = os.path.join(kind, name) out_name = fnames[index] @@ -131,7 +132,7 @@ class TestWordpressXmlImporter(unittest.TestCase): test_posts = [] for post in self.custposts: # check post kind - if post[7] == 'article' or post[7] == 'page': + if post[8] == 'article' or post[8] == 'page': pass else: test_posts.append(post) @@ -141,7 +142,7 @@ class TestWordpressXmlImporter(unittest.TestCase): index = 0 for post in test_posts: name = post[2] - kind = post[7] + kind = post[8] category = slugify(post[5][0]) name += '.md' filename = os.path.join(kind, category, name) @@ -155,7 +156,7 @@ class TestWordpressXmlImporter(unittest.TestCase): test_posts = [] for post in self.custposts: # check post kind - if post[7] == 'page': + if post[8] == 'page': test_posts.append(post) with temporary_folder() as temp: fnames = list(silent_f2p(test_posts, 'markdown', temp, @@ -171,7 +172,7 @@ class TestWordpressXmlImporter(unittest.TestCase): def test_can_toggle_raw_html_code_parsing(self): def r(f): - with open(f) as infile: + with open(f, encoding='utf-8') as infile: return infile.read() silent_f2p = mute(True)(fields2pelican) @@ -213,7 +214,7 @@ class TestWordpressXmlImporter(unittest.TestCase): def test_preserve_verbatim_formatting(self): def r(f): - with open(f) as infile: + 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) @@ -228,7 +229,7 @@ class TestWordpressXmlImporter(unittest.TestCase): def test_code_in_list(self): def r(f): - with open(f) as infile: + 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) From 0949fa62ec5b8d0b9c59037cf93df733b062575e Mon Sep 17 00:00:00 2001 From: John Mastro Date: Thu, 12 Feb 2015 16:24:59 -0800 Subject: [PATCH 0046/1094] Tell smartypants to also process " entities This is necessary because Docutils has already replaced double quotes with " HTML entities by the time the typogrify filter is applied. --- pelican/readers.py | 7 +++++++ pelican/tests/test_readers.py | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index 85147e3e..1fe9a148 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -467,6 +467,13 @@ class Readers(FileStampDataCacher): # eventually filter the content with typogrify if asked so if self.settings['TYPOGRIFY']: from typogrify.filters import typogrify + import smartypants + + # Tell `smartypants` to also replace " HTML entities with + # smart quotes. This is necessary because Docutils has already + # replaced double quotes with said entities by the time we run + # this filter. + smartypants.Attr.default |= smartypants.Attr.w def typogrify_wrapper(text): """Ensures ignore_tags feature is backward compatible""" diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index ffff3478..cb657673 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -121,7 +121,7 @@ class RstReaderTest(ReaderTest): page = self.read_file(path='article.rst', TYPOGRIFY=True) expected = ( '

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

\n' + 'With some stuff to “typogrify”…

\n' '

Now with added support for TLA.

\n') @@ -146,8 +146,8 @@ class RstReaderTest(ReaderTest): TYPOGRIFY=True) expected = ('

Multi-line metadata should be' ' supported\nas well as inline' - ' markup and stuff to "typogrify' - '"…

\n') + ' markup and stuff to “typogrify' + '”…

\n') self.assertEqual(page.metadata['summary'], expected) except ImportError: From fa269d7c6f14ddd63cb8efecb5f15676b432fdbe Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 17 Feb 2015 20:18:12 -0500 Subject: [PATCH 0047/1094] Fix tests that were skipped in #1581 --- pelican/tests/test_pelican.py | 2 +- pelican/tests/test_settings.py | 3 +-- pelican/tests/test_utils.py | 37 +++++++++++++++++++++------------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 62322355..29378062 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -61,7 +61,7 @@ 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={b'PAGER': b''}, stdout=subprocess.PIPE, stderr=subprocess.PIPE + env={str('PAGER'): str('')}, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate() def ignorable_git_crlf_errors(line): # Work around for running tests on Windows diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 2c5c1541..9b961386 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -103,13 +103,12 @@ class TestSettingsConfiguration(unittest.TestCase): configure_settings(settings) self.assertEqual(settings['FEED_DOMAIN'], 'http://feeds.example.com') + @unittest.skipIf(platform == 'win32', "Doesn't work on Windows") def test_default_encoding(self): # test that the default locale is set if # locale is not specified in the settings #reset locale to python default - if platform == 'win32': - return unittest.skip("Doesn't work on Windows") locale.setlocale(locale.LC_ALL, str('C')) self.assertEqual(self.settings['LOCALE'], DEFAULT_CONFIG['LOCALE']) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 18a0f4ca..f793da13 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -298,12 +298,12 @@ class TestUtils(LoggedTestCase): 'Turkish locale needed') def test_strftime_locale_dependent_turkish(self): # store current locale - old_locale = locale.setlocale(locale.LC_TIME) + old_locale = locale.setlocale(locale.LC_ALL) if platform == 'win32': - return unittest.skip("Doesn't work on Windows") + locale.setlocale(locale.LC_ALL, str('Turkish')) else: - locale.setlocale(locale.LC_TIME, str('tr_TR.UTF-8')) + locale.setlocale(locale.LC_ALL, str('tr_TR.UTF-8')) d = utils.SafeDatetime(2012, 8, 29) @@ -321,7 +321,7 @@ class TestUtils(LoggedTestCase): '2012 yılında %üretim artışı') # restore locale back - locale.setlocale(locale.LC_TIME, old_locale) + locale.setlocale(locale.LC_ALL, old_locale) # test the output of utils.strftime in a different locale @@ -331,12 +331,12 @@ class TestUtils(LoggedTestCase): 'French locale needed') def test_strftime_locale_dependent_french(self): # store current locale - old_locale = locale.setlocale(locale.LC_TIME) + old_locale = locale.setlocale(locale.LC_ALL) if platform == 'win32': - locale.setlocale(locale.LC_TIME, str('French')) + locale.setlocale(locale.LC_ALL, str('French')) else: - locale.setlocale(locale.LC_TIME, str('fr_FR.UTF-8')) + locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8')) d = utils.SafeDatetime(2012, 8, 29) @@ -355,7 +355,7 @@ class TestUtils(LoggedTestCase): '%écrits en 2012') # restore locale back - locale.setlocale(locale.LC_TIME, old_locale) + locale.setlocale(locale.LC_ALL, old_locale) class TestCopy(unittest.TestCase): @@ -471,10 +471,11 @@ class TestDateFormatter(unittest.TestCase): locale_available('French'), 'French locale needed') def test_french_strftime(self): - if platform == 'win32': - return unittest.skip("Doesn't work on Windows") # This test tries to reproduce an issue that occurred with python3.3 under macos10 only - locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8')) + 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() ) @@ -492,9 +493,13 @@ class TestDateFormatter(unittest.TestCase): locale_available('French'), 'French locale needed') def test_french_locale(self): + if platform == 'win32': + locale_string = 'French' + else: + locale_string = 'fr_FR.UTF-8' settings = read_settings( - override={'LOCALE': locale.normalize('fr_FR.UTF-8'), - 'TEMPLATE_PAGES': {'template/source.html': + override = {'LOCALE': locale_string, + 'TEMPLATE_PAGES': {'template/source.html': 'generated/file.html'}}) generator = TemplatePagesGenerator( @@ -521,8 +526,12 @@ class TestDateFormatter(unittest.TestCase): locale_available('Turkish'), 'Turkish locale needed') def test_turkish_locale(self): + if platform == 'win32': + locale_string = 'Turkish' + else: + locale_string = 'tr_TR.UTF-8' settings = read_settings( - override = {'LOCALE': locale.normalize('tr_TR.UTF-8'), + override = {'LOCALE': locale_string, 'TEMPLATE_PAGES': {'template/source.html': 'generated/file.html'}}) From 95860c6b1bcf359005b6f7c08749214950f57a4f Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Mon, 3 Nov 2014 20:30:54 -0800 Subject: [PATCH 0048/1094] Remove unused modules/variables from tests --- pelican/tests/test_generators.py | 1 - pelican/tests/test_paginator.py | 1 - pelican/tests/test_pelican.py | 2 -- pelican/tests/test_utils.py | 2 +- 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index c3e36bc1..cdc98dfd 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -7,7 +7,6 @@ try: from unittest.mock import MagicMock except ImportError: from mock import MagicMock -from operator import itemgetter from shutil import rmtree from tempfile import mkdtemp diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index 108dc791..5494fda8 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import -import six import locale from pelican.tests.support import unittest, get_settings diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 29378062..c6332487 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -123,8 +123,6 @@ class TestPelican(LoggedTestCase): locale_available('French'), 'French locale needed') def test_custom_locale_generation_works(self): '''Test that generation with fr_FR.UTF-8 locale works''' - old_locale = locale.setlocale(locale.LC_TIME) - if sys.platform == 'win32': our_locale = str('French') else: diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index f793da13..c6550459 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -5,7 +5,7 @@ import shutil import os import time import locale -from sys import platform, version_info +from sys import platform from tempfile import mkdtemp import pytz From 6549f51591ddac1d1ee1ccbed65817d468767448 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Mon, 3 Nov 2014 21:00:09 -0800 Subject: [PATCH 0049/1094] Add tests for Pelican and pelican_import tool Added tests to ensure that: - THEME and deprecated *_DIR settings result in the expected configurations - Post headers are formatted correctly in both Markdown and reStructuredText - Files specified in IGNORE_FILES setting are properly ignored - Generator.get_files()'s `paths` argument is backwards-compatible with strings --- pelican/tests/test_generators.py | 20 +++++++++++++++ pelican/tests/test_importer.py | 36 +++++++++++++++++++++++++++ pelican/tests/test_settings.py | 42 ++++++++++++++++++++++++++------ 3 files changed, 90 insertions(+), 8 deletions(-) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index cdc98dfd..9f38c002 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -34,12 +34,17 @@ class TestGenerator(unittest.TestCase): def test_include_path(self): + self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'} + filename = os.path.join(CUR_DIR, 'content', 'article.rst') include_path = self.generator._include_path self.assertTrue(include_path(filename)) self.assertTrue(include_path(filename, extensions=('rst',))) self.assertFalse(include_path(filename, extensions=('md',))) + ignored_file = os.path.join(CUR_DIR, 'content', 'ignored1.rst') + self.assertFalse(include_path(ignored_file)) + def test_get_files_exclude(self): """Test that Generator.get_files() properly excludes directories. """ @@ -55,6 +60,13 @@ class TestGenerator(unittest.TestCase): 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, + "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, @@ -317,6 +329,14 @@ class TestArticlesGenerator(unittest.TestCase): settings, blog=True, dates=dates) + def test_nonexistent_template(self): + """Attempt to load a non-existent template""" + settings = get_settings(filenames={}) + generator = ArticlesGenerator( + context=settings, settings=settings, + path=None, theme=settings['THEME'], output_path=None) + self.assertRaises(Exception, generator.get_template, "not_a_template") + def test_generate_authors(self): """Check authors generation.""" authors = [author.name for author, _ in self.generator.authors] diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index df2ad6cd..c108bc52 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -245,6 +245,41 @@ class TestBuildHeader(unittest.TestCase): header = build_header('test', None, None, None, None, None) self.assertEqual(header, 'test\n####\n\n') + def test_build_header_with_fields(self): + header_data = [ + 'Test Post', + '2014-11-04', + 'Alexis Métaireau', + ['Programming'], + ['Pelican', 'Python'], + 'test-post', + ] + + expected_docutils = '\n'.join([ + 'Test Post', + '#########', + ':date: 2014-11-04', + ':author: Alexis Métaireau', + ':category: Programming', + ':tags: Pelican, Python', + ':slug: test-post', + '\n', + ]) + + expected_md = '\n'.join([ + 'Title: Test Post', + 'Date: 2014-11-04', + 'Author: Alexis Métaireau', + 'Category: Programming', + 'Tags: Pelican, Python', + 'Slug: test-post', + '\n', + ]) + + 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) @@ -265,6 +300,7 @@ class TestBuildHeader(unittest.TestCase): self.assertEqual(header, 'Title: test\n' + 'Attachments: output/test1, ' + 'output/test2\n\n') + @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') class TestWordpressXMLAttachements(unittest.TestCase): def setUp(self): diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 9b961386..db9fee7b 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -41,7 +41,7 @@ class TestSettingsConfiguration(unittest.TestCase): self.assertNotIn('foobar', self.settings) def test_read_empty_settings(self): - # Providing no file should return the default values. + # Ensure an empty settings file results in default settings. settings = read_settings(None) expected = copy.deepcopy(DEFAULT_CONFIG) # Added by configure settings @@ -67,8 +67,8 @@ class TestSettingsConfiguration(unittest.TestCase): settings['SITENAME'] = 'Not a Pelican Blog' self.assertNotEqual(settings['SITENAME'], DEFAULT_CONFIG['SITENAME']) - def test_path_settings_safety(self): - """Don't let people setting the static path listings to strs""" + 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 @@ -84,8 +84,7 @@ class TestSettingsConfiguration(unittest.TestCase): DEFAULT_CONFIG['THEME_STATIC_PATHS']) def test_configure_settings(self): - #Manipulations to settings should be applied correctly. - + # Manipulations to settings should be applied correctly. settings = { 'SITEURL': 'http://blog.notmyidea.org/', 'LOCALE': '', @@ -93,6 +92,7 @@ class TestSettingsConfiguration(unittest.TestCase): 'THEME': DEFAULT_THEME, } configure_settings(settings) + # SITEURL should not have a trailing slash self.assertEqual(settings['SITEURL'], 'http://blog.notmyidea.org') @@ -103,12 +103,38 @@ class TestSettingsConfiguration(unittest.TestCase): configure_settings(settings) self.assertEqual(settings['FEED_DOMAIN'], 'http://feeds.example.com') + def test_theme_settings_exceptions(self): + settings = self.settings + + # Check that theme lookup in "pelican/themes" functions as expected + settings['THEME'] = os.path.split(settings['THEME'])[1] + configure_settings(settings) + self.assertEqual(settings['THEME'], DEFAULT_THEME) + + # Check that non-existent theme raises exception + settings['THEME'] = 'foo' + self.assertRaises(Exception, configure_settings, settings) + + def test_deprecated_dir_setting(self): + settings = self.settings + + settings['ARTICLE_DIR'] = 'foo' + settings['PAGE_DIR'] = 'bar' + + configure_settings(settings) + + self.assertEqual(settings['ARTICLE_PATHS'], ['foo']) + self.assertEqual(settings['PAGE_PATHS'], ['bar']) + + with self.assertRaises(KeyError): + settings['ARTICLE_DIR'] + settings['PAGE_DIR'] + @unittest.skipIf(platform == 'win32', "Doesn't work on Windows") def test_default_encoding(self): - # test that the default locale is set if - # locale is not specified in the settings + # Test that the default locale is set if not specified in settings - #reset locale to python default + # Reset locale to Python's default locale locale.setlocale(locale.LC_ALL, str('C')) self.assertEqual(self.settings['LOCALE'], DEFAULT_CONFIG['LOCALE']) From d4ba7b729197cfd71ea2fc7137a13481b9bd3839 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 18 Feb 2015 09:41:27 -0800 Subject: [PATCH 0050/1094] Correct Dotclear sample file link in importer docs Fixes #1570 --- docs/importer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/importer.rst b/docs/importer.rst index aa3fa935..83f4e112 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -112,4 +112,4 @@ Tests To test the module, one can use sample files: - for WordPress: http://wpcandy.com/made/the-sample-post-collection -- for Dotclear: http://themes.dotaddict.org/files/public/downloads/lorem-backup.txt +- for Dotclear: http://media.dotaddict.org/tda/downloads/lorem-backup.txt From 784d07e9402ebc735c7c7baddaf0dd57cc279505 Mon Sep 17 00:00:00 2001 From: Forest Date: Thu, 19 Feb 2015 12:19:49 -0800 Subject: [PATCH 0051/1094] ArticlesGenerator: set blog=True consistently. Fixes #1631. --- pelican/generators.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 97e453b4..f0a6d264 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -350,7 +350,8 @@ class ArticlesGenerator(CachingGenerator): signals.article_generator_write_article.send(self, content=article) write(article.save_as, self.get_template(article.template), self.context, article=article, category=article.category, - override_output=hasattr(article, 'override_save_as')) + override_output=hasattr(article, 'override_save_as'), + blog=True) def generate_period_archives(self, write): """Generate per-year, per-month, and per-day archives.""" @@ -432,7 +433,7 @@ class ArticlesGenerator(CachingGenerator): dates = [article for article in self.dates if article in articles] write(tag.save_as, tag_template, self.context, tag=tag, articles=articles, dates=dates, - paginated={'articles': articles, 'dates': dates}, + paginated={'articles': articles, 'dates': dates}, blog=True, page_name=tag.page_name, all_articles=self.articles) def generate_categories(self, write): @@ -443,7 +444,7 @@ class ArticlesGenerator(CachingGenerator): dates = [article for article in self.dates if article in articles] write(cat.save_as, category_template, self.context, category=cat, articles=articles, dates=dates, - paginated={'articles': articles, 'dates': dates}, + paginated={'articles': articles, 'dates': dates}, blog=True, page_name=cat.page_name, all_articles=self.articles) def generate_authors(self, write): @@ -454,7 +455,7 @@ class ArticlesGenerator(CachingGenerator): dates = [article for article in self.dates if article in articles] write(aut.save_as, author_template, self.context, author=aut, articles=articles, dates=dates, - paginated={'articles': articles, 'dates': dates}, + paginated={'articles': articles, 'dates': dates}, blog=True, page_name=aut.page_name, all_articles=self.articles) def generate_drafts(self, write): @@ -463,7 +464,7 @@ class ArticlesGenerator(CachingGenerator): write(draft.save_as, self.get_template(draft.template), self.context, article=draft, category=draft.category, override_output=hasattr(draft, 'override_save_as'), - all_articles=self.articles) + blog=True, all_articles=self.articles) def generate_pages(self, writer): """Generate the pages on the disk""" From bc3a0e8c592e70db432ee987e9b42f069aee6ba8 Mon Sep 17 00:00:00 2001 From: SkyLothar Date: Sun, 22 Feb 2015 16:27:18 +0800 Subject: [PATCH 0052/1094] remove useless if condition in index template --- pelican/themes/notmyidea/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/themes/notmyidea/templates/index.html b/pelican/themes/notmyidea/templates/index.html index c8982476..3eac8a3a 100644 --- a/pelican/themes/notmyidea/templates/index.html +++ b/pelican/themes/notmyidea/templates/index.html @@ -23,7 +23,7 @@ {% endif %} {# other items #} {% else %} - {% if loop.first and articles_page.has_previous %} + {% if loop.first %}
    {% endif %} From 79aaedc68195ed5d82635e3b4d510aaf0a065a8d Mon Sep 17 00:00:00 2001 From: pkirk Date: Mon, 23 Feb 2015 20:13:33 +0100 Subject: [PATCH 0053/1094] rsync -avc, not --avc --- docs/publish.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/publish.rst b/docs/publish.rst index 70d93e59..8c7d4c19 100644 --- a/docs/publish.rst +++ b/docs/publish.rst @@ -65,7 +65,7 @@ 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/ + 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 From d0afaa5fbe8d22f5d7955e0a2e5dd4387c28de4a Mon Sep 17 00:00:00 2001 From: Patrick Fournier Date: Tue, 10 Feb 2015 14:49:29 -0500 Subject: [PATCH 0054/1094] Format custom metadata fields listed in the FORMATTED_FIELDS setting. Adding FORMATTED_FIELDS to the default settings with ['summary'] as the default value. --- docs/settings.rst | 1 + pelican/readers.py | 10 +++++++--- pelican/settings.py | 1 + ...article_with_markdown_and_summary_metadata_multi.md | 3 +++ pelican/tests/content/article_with_metadata.rst | 3 +++ pelican/tests/default_conf.py | 3 +++ 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index d7ba85cd..9c2f13ae 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -214,6 +214,7 @@ Setting name (followed by default value, if any) in this list are written. Paths should be either absolute or relative to the current Pelican working directory. For possible use cases see :ref:`writing_only_selected_content`. +``FORMATTED_FIELDS = ['summary']`` A list of metadata fields to be parsed and translated to HTML. =============================================================================== ===================================================================== .. [#] Default is the system locale. diff --git a/pelican/readers.py b/pelican/readers.py index 365080d6..731fb5da 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -136,13 +136,15 @@ class RstReader(BaseReader): def _parse_metadata(self, document): """Return the dict containing document metadata""" + formatted_fields = self.settings['FORMATTED_FIELDS'] + output = {} for docinfo in document.traverse(docutils.nodes.docinfo): for element in docinfo.children: if element.tagname == 'field': # custom fields (e.g. summary) name_elem, body_elem = element.children name = name_elem.astext() - if name == 'summary': + if name in formatted_fields: value = render_node_to_html(document, body_elem) else: value = body_elem.astext() @@ -205,10 +207,12 @@ class MarkdownReader(BaseReader): def _parse_metadata(self, meta): """Return the dict containing document metadata""" + formatted_fields = self.settings['FORMATTED_FIELDS'] + output = {} for name, value in meta.items(): name = name.lower() - if name == "summary": + if name in formatted_fields: # handle summary metadata as markdown # summary metadata is special case and join all list values summary_values = "\n".join(value) @@ -333,7 +337,7 @@ class HTMLReader(BaseReader): if name is None: attr_serialized = ', '.join(['{}="{}"'.format(k, v) for k, v in attrs]) logger.warning("Meta tag in file %s does not have a 'name' " - "attribute, skipping. Attributes: %s", + "attribute, skipping. Attributes: %s", self._filename, attr_serialized) return name = name.lower() diff --git a/pelican/settings.py b/pelican/settings.py index e33d6a66..0d69c08e 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -133,6 +133,7 @@ DEFAULT_CONFIG = { 'LOAD_CONTENT_CACHE': True, 'AUTORELOAD_IGNORE_CACHE': False, 'WRITE_SELECTED': [], + 'FORMATTED_FIELDS': ['summary'], } PYGMENTS_RST_OPTIONS = None diff --git a/pelican/tests/content/article_with_markdown_and_summary_metadata_multi.md b/pelican/tests/content/article_with_markdown_and_summary_metadata_multi.md index b6ef666c..c5f62a0a 100644 --- a/pelican/tests/content/article_with_markdown_and_summary_metadata_multi.md +++ b/pelican/tests/content/article_with_markdown_and_summary_metadata_multi.md @@ -3,5 +3,8 @@ Date: 2012-10-31 Summary: A multi-line summary should be supported as well as **inline markup**. +custom_formatted_field: + Multi-line metadata should also be supported + as well as *inline markup* and stuff to "typogrify"... This is some content. diff --git a/pelican/tests/content/article_with_metadata.rst b/pelican/tests/content/article_with_metadata.rst index 9b65a4b0..5f3d77bc 100644 --- a/pelican/tests/content/article_with_metadata.rst +++ b/pelican/tests/content/article_with_metadata.rst @@ -11,3 +11,6 @@ This is a super article ! Multi-line metadata should be supported as well as **inline markup** and stuff to "typogrify"... :custom_field: http://notmyidea.org +:custom_formatted_field: + Multi-line metadata should also be supported + as well as *inline markup* and stuff to "typogrify"... diff --git a/pelican/tests/default_conf.py b/pelican/tests/default_conf.py index b4f532d4..f38ef804 100644 --- a/pelican/tests/default_conf.py +++ b/pelican/tests/default_conf.py @@ -39,6 +39,9 @@ STATIC_PATHS = [ '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" + From d53c166f954f5b3b5352f71164545d40c19c4af2 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 25 Feb 2015 09:42:33 -0800 Subject: [PATCH 0055/1094] Clarify docs for FORMATTED_FIELDS setting --- docs/settings.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index 9c2f13ae..11444d2e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -214,7 +214,8 @@ Setting name (followed by default value, if any) in this list are written. Paths should be either absolute or relative to the current Pelican working directory. For possible use cases see :ref:`writing_only_selected_content`. -``FORMATTED_FIELDS = ['summary']`` A list of metadata fields to be parsed and translated to HTML. +``FORMATTED_FIELDS = ['summary']`` A list of metadata fields containing reST/Markdown content to be + parsed and translated to HTML. =============================================================================== ===================================================================== .. [#] Default is the system locale. From 555c5539924ebe1efd64252221389c646dcaa998 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Wed, 25 Feb 2015 21:37:36 -0800 Subject: [PATCH 0056/1094] Remove PDF-related elements from notmyidea PDF generation was removed from Pelican's core in #1010. --- pelican/themes/notmyidea/templates/article_infos.html | 2 +- pelican/themes/notmyidea/templates/page.html | 2 -- pelican/themes/notmyidea/templates/taglist.html | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pelican/themes/notmyidea/templates/article_infos.html b/pelican/themes/notmyidea/templates/article_infos.html index 718967e0..172ad85d 100644 --- a/pelican/themes/notmyidea/templates/article_infos.html +++ b/pelican/themes/notmyidea/templates/article_infos.html @@ -16,7 +16,7 @@ {% endfor %} {% 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 0057/1094] 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 0058/1094] 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 0059/1094] 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 0060/1094] 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 0061/1094] 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 0062/1094] 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 0063/1094] 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 0064/1094] 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 0065/1094] 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 0066/1094] 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 0067/1094] 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 0068/1094] 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 0069/1094] 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 0070/1094] 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 0071/1094] 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 0072/1094] 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 0073/1094] 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 0074/1094] 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 0075/1094] 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 0076/1094] 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 0077/1094] 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 0078/1094] 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 0079/1094] 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 0080/1094] 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 0081/1094] 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 0082/1094] 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 0083/1094] 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 0084/1094] 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 0085/1094] 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 0086/1094] 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 0087/1094] 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 0088/1094] 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 0089/1094] 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 0090/1094] 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