From 6662949d227dfd1e7942d33e6ccfc6e97c5f68d5 Mon Sep 17 00:00:00 2001 From: Jacob Lyles Date: Sun, 9 Feb 2014 00:36:52 -0800 Subject: [PATCH 01/82] allows pelican-themes symlink command to use relative paths not beginning with a dot --- pelican/tests/TestTheme/theme/a.html | 1 + pelican/tests/test_pelican_themes.py | 24 ++++++++++++++++++++++++ pelican/tools/pelican_themes.py | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 pelican/tests/TestTheme/theme/a.html create mode 100644 pelican/tests/test_pelican_themes.py diff --git a/pelican/tests/TestTheme/theme/a.html b/pelican/tests/TestTheme/theme/a.html new file mode 100644 index 00000000..90bfcb51 --- /dev/null +++ b/pelican/tests/TestTheme/theme/a.html @@ -0,0 +1 @@ +this is a test diff --git a/pelican/tests/test_pelican_themes.py b/pelican/tests/test_pelican_themes.py new file mode 100644 index 00000000..474a8ca7 --- /dev/null +++ b/pelican/tests/test_pelican_themes.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, print_function + +import os + +from pelican.tools.pelican_themes import symlink, _THEMES_PATH +from pelican.tests.support import LoggedTestCase + +CURRENT_DIR_REL = os.path.dirname(__file__) +THEME_TARGET_PATH = os.path.join(_THEMES_PATH, "theme") + +class TestPelicanThemes(LoggedTestCase): + # testing for pelican-themes commands. + + def setUp(self): + super(TestPelicanThemes, self).setUp() + + def tearDown(self): + super(TestPelicanThemes, self).tearDown() + os.remove(THEME_TARGET_PATH) + + def test_symlink_relative(self): + symlink(os.path.join(CURRENT_DIR_REL, "TestTheme/theme/")) + self.assertTrue(os.path.exists(THEME_TARGET_PATH)) diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index 8d71535d..e2feee29 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -214,7 +214,7 @@ def symlink(path, v=False): if v: print("Linking `{p}' to `{t}' ...".format(p=path, t=theme_path)) try: - os.symlink(path, theme_path) + os.symlink(os.path.abspath(path), theme_path) except Exception as e: err("Cannot link `{p}' to `{t}':\n{e}".format(p=path, t=theme_path, e=str(e))) From 5928603720d2c50372532e7ba31e7c2e84eb817b Mon Sep 17 00:00:00 2001 From: Mathieu Leplatre Date: Thu, 9 Jan 2014 15:19:31 +0100 Subject: [PATCH 02/82] Fail and exit on restructuredtext syntax error --- pelican/readers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index d070a19c..198b5a5f 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -155,7 +155,8 @@ class RstReader(BaseReader): def _get_publisher(self, source_path): extra_params = {'initial_header_level': '2', 'syntax_highlight': 'short', - 'input_encoding': 'utf-8'} + 'input_encoding': 'utf-8', + 'exit_status_level': 2} user_params = self.settings.get('DOCUTILS_SETTINGS') if user_params: extra_params.update(user_params) @@ -166,7 +167,7 @@ class RstReader(BaseReader): pub.writer.translator_class = PelicanHTMLTranslator pub.process_programmatic_settings(None, extra_params, None) pub.set_source(source_path=source_path) - pub.publish() + pub.publish(enable_exit_status=True) return pub def read(self, source_path): From c0ba93da9eee32555395282f823454481a01c826 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 9 Feb 2014 08:45:06 -0800 Subject: [PATCH 03/82] Revert test-failing change from #1114 --- pelican/tools/pelican_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 940849d8..b04906c0 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -112,7 +112,7 @@ def get_items(xml): sys.exit(error) with open(xml, encoding='utf-8') as infile: xmlfile = infile.read() - soup = BeautifulSoup(xmlfile, "lxml") + soup = BeautifulSoup(xmlfile, "xml") items = soup.rss.channel.findAll('item') return items From fae5648ce68cd5d4bdf91bdb32ad0d7634c7172e Mon Sep 17 00:00:00 2001 From: Colin Dunklau Date: Fri, 19 Apr 2013 22:15:00 -0500 Subject: [PATCH 04/82] Updated github mark for notmyidea The smallest mark GitHub provides in the bundle is 32x32. Since they explicitly say don't resize the mark, I asked them to provide a 16x16 so we could use it. I received the 16x16 in this commit from GitHub support. However, they suggested we use the 32x32 and use CSS rules to adjust the size: "...we use the 32px for both and employ css background and background- size rules to handle retina displays. I recommend that technique, since retina users will see a mess of pixels for icons that small." This sounds like reasonable advice. Should we consider migrating the notmyidea social icons to 32x32, with appropriate CSS to adjust the sizes? --- .../notmyidea/static/images/icons/github.png | Bin 346 -> 1510 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pelican/themes/notmyidea/static/images/icons/github.png b/pelican/themes/notmyidea/static/images/icons/github.png index 6c52b486c5e392261c24d53fe2609e54d4f07c5d..ea7ac8eeff880bb7743fdafdc8165e693becae09 100644 GIT binary patch literal 1510 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#y zH8OIA>2=9ZF3nBND}m`vLFjeFsTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6saIL(9V zO~LIJ3!HlOfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;u(#Yd@MB%_X10f+AmzBus34G zi-ih{zAcc`z0yCqN?>JJ;>z83fm%0p?vFIP9pL@(;O&{U^83Hv%a1eJYP?fFk1L})-v0#OZMp3?qvI|5qEoE#*xyy#;TY1^ghsRb^SMWzUL$xh6^$J zuTBaw2ze}x*_k%g>$8J-muOj6S8M&!yDwwdPN)UDT`%~K&e^tF^IwXX$ zIxA~5E9pnO`E&I>z~ZrAq(_V(T0W-G~ZaDCnW`HXIW*$eKa=$h1}fLuWsN2YYWExxyD?iIw1{hJ@4Y+ep!lP^ zmTsBJ8Nb$8I>`uat7x{Lzd(A%TFK)dl|G2?y8F&+&XFwV?1;CL=f0}lShxCB7jr>j zd*jb(nKLxbrEO5zT(E7U^z?##pKI%N5AQBI#w$|UvE4eT;#{f74ois}20VMM!fGci zec2+dfv#9~av5+_i`= zIkfH74W+|BS!H7-YNdPK7JCS7)cq~9V^{0XcUAMY&njowlz#BE*sfjeu^CV0@5D2* ZF`VUQTbsLP)>Kf*>*?y}vd$@?2>?HFLGb_p delta 319 zcmV-F0l@y|3)%vZB!3BTNLh0L01FZT01FZU(%pXi0003DNkl>~E?j1_KiT;z^&w|@$z*CsGUh074yEL{XM zZ7cf&`*=*r72Z+j+2B5y<+hzU#(AFv+!m8y#^HcNtmEqdUmXGsPJ&&BYk)<>PJbac z!3$@wEwC5mE`k2GKxqL+9X1|{k^fkbPG1EuO5&~{J{6@4w%#7&G9!Ay3~z Date: Fri, 19 Apr 2013 22:43:46 -0500 Subject: [PATCH 05/82] Updated tests --- .../output/basic/theme/images/icons/github.png | Bin 346 -> 1510 bytes .../custom/theme/images/icons/github.png | Bin 346 -> 1510 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/pelican/tests/output/basic/theme/images/icons/github.png b/pelican/tests/output/basic/theme/images/icons/github.png index 6c52b486c5e392261c24d53fe2609e54d4f07c5d..ea7ac8eeff880bb7743fdafdc8165e693becae09 100644 GIT binary patch literal 1510 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#y zH8OIA>2=9ZF3nBND}m`vLFjeFsTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6saIL(9V zO~LIJ3!HlOfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;u(#Yd@MB%_X10f+AmzBus34G zi-ih{zAcc`z0yCqN?>JJ;>z83fm%0p?vFIP9pL@(;O&{U^83Hv%a1eJYP?fFk1L})-v0#OZMp3?qvI|5qEoE#*xyy#;TY1^ghsRb^SMWzUL$xh6^$J zuTBaw2ze}x*_k%g>$8J-muOj6S8M&!yDwwdPN)UDT`%~K&e^tF^IwXX$ zIxA~5E9pnO`E&I>z~ZrAq(_V(T0W-G~ZaDCnW`HXIW*$eKa=$h1}fLuWsN2YYWExxyD?iIw1{hJ@4Y+ep!lP^ zmTsBJ8Nb$8I>`uat7x{Lzd(A%TFK)dl|G2?y8F&+&XFwV?1;CL=f0}lShxCB7jr>j zd*jb(nKLxbrEO5zT(E7U^z?##pKI%N5AQBI#w$|UvE4eT;#{f74ois}20VMM!fGci zec2+dfv#9~av5+_i`= zIkfH74W+|BS!H7-YNdPK7JCS7)cq~9V^{0XcUAMY&njowlz#BE*sfjeu^CV0@5D2* ZF`VUQTbsLP)>Kf*>*?y}vd$@?2>?HFLGb_p delta 319 zcmV-F0l@y|3)%vZB!3BTNLh0L01FZT01FZU(%pXi0003DNkl>~E?j1_KiT;z^&w|@$z*CsGUh074yEL{XM zZ7cf&`*=*r72Z+j+2B5y<+hzU#(AFv+!m8y#^HcNtmEqdUmXGsPJ&&BYk)<>PJbac z!3$@wEwC5mE`k2GKxqL+9X1|{k^fkbPG1EuO5&~{J{6@4w%#7&G9!Ay3~zc-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#y zH8OIA>2=9ZF3nBND}m`vLFjeFsTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6saIL(9V zO~LIJ3!HlOfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;u(#Yd@MB%_X10f+AmzBus34G zi-ih{zAcc`z0yCqN?>JJ;>z83fm%0p?vFIP9pL@(;O&{U^83Hv%a1eJYP?fFk1L})-v0#OZMp3?qvI|5qEoE#*xyy#;TY1^ghsRb^SMWzUL$xh6^$J zuTBaw2ze}x*_k%g>$8J-muOj6S8M&!yDwwdPN)UDT`%~K&e^tF^IwXX$ zIxA~5E9pnO`E&I>z~ZrAq(_V(T0W-G~ZaDCnW`HXIW*$eKa=$h1}fLuWsN2YYWExxyD?iIw1{hJ@4Y+ep!lP^ zmTsBJ8Nb$8I>`uat7x{Lzd(A%TFK)dl|G2?y8F&+&XFwV?1;CL=f0}lShxCB7jr>j zd*jb(nKLxbrEO5zT(E7U^z?##pKI%N5AQBI#w$|UvE4eT;#{f74ois}20VMM!fGci zec2+dfv#9~av5+_i`= zIkfH74W+|BS!H7-YNdPK7JCS7)cq~9V^{0XcUAMY&njowlz#BE*sfjeu^CV0@5D2* ZF`VUQTbsLP)>Kf*>*?y}vd$@?2>?HFLGb_p delta 319 zcmV-F0l@y|3)%vZB!3BTNLh0L01FZT01FZU(%pXi0003DNkl>~E?j1_KiT;z^&w|@$z*CsGUh074yEL{XM zZ7cf&`*=*r72Z+j+2B5y<+hzU#(AFv+!m8y#^HcNtmEqdUmXGsPJ&&BYk)<>PJbac z!3$@wEwC5mE`k2GKxqL+9X1|{k^fkbPG1EuO5&~{J{6@4w%#7&G9!Ay3~z Date: Sun, 9 Feb 2014 21:17:39 +0100 Subject: [PATCH 06/82] 32px Github logo and CSS shinker --- pelican/tests/output/basic/theme/css/main.css | 5 ++++- .../basic/theme/images/icons/github.png | Bin 1510 -> 1714 bytes .../tests/output/custom/theme/css/main.css | 5 ++++- .../custom/theme/images/icons/github.png | Bin 1510 -> 1714 bytes pelican/themes/notmyidea/static/css/main.css | 5 ++++- .../notmyidea/static/images/icons/github.png | Bin 1510 -> 1714 bytes 6 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pelican/tests/output/basic/theme/css/main.css b/pelican/tests/output/basic/theme/css/main.css index fa0bcf1c..01ee17d8 100644 --- a/pelican/tests/output/basic/theme/css/main.css +++ b/pelican/tests/output/basic/theme/css/main.css @@ -314,7 +314,10 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;} .social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');} .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');} .social a[href*='github.com'], - .social a[href*='git.io'] {background-image: url('../images/icons/github.png');} + .social a[href*='git.io'] { + background-image: url('../images/icons/github.png'); + background-size: 50%; + } .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} .social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');} .social a[href*='groups.google.com'] {background-image: url('../images/icons/google-groups.png');} diff --git a/pelican/tests/output/basic/theme/images/icons/github.png b/pelican/tests/output/basic/theme/images/icons/github.png index ea7ac8eeff880bb7743fdafdc8165e693becae09..8b25551a97921681334176ee143b41510a117d86 100644 GIT binary patch delta 1038 zcmaFHy@^+`Gr-TCmrII^fq{Y7)59eQNGpIa2OE$quB!U7QBj>q!PV5z+``ew(bCe< zz|hdu$kNf!)XCYRHnq)=QEl$vIhGP#gR z7-pm;F4dC_n4Ripx3KMGU|?49ba4!+xOHTDZbomS#PRc$KE}5VZaQlBbp$D=*?m0d z>2Yi2S@us8D}OGyqVbV&rEhPmh-Unh{AZ?1d0LkmX#X}iGHqjyT7GqA+_N9CrE_hn z?|lC_=W*n_U*&U)_y1Y{zt;Bq>C$#*yVFk&olx$;v-PzLWNjF?AAI_5 zUitgKAKGdy;u>!9o^qV#zt{c%YXOJiSxL?VA1lrzZn)SV;O*I9oS^Px_giei=5^N% z*aM$@4w(!0lADxuB`iTXLHI>k?ryOH z_lt{472hh?DqC&4byzYqe!o5A=J=M%j2W(+D*d+B$yybDfBnxtpM3HRTmD_`dB0j` zs4W$AA&nI(PT(B{G^o%!zl_2H9G znLo&4U}|~1Rn@3aP+&999rpugAJK0IMwi{sR)r(dQe9pCVENy)8q z?|2p~#LR75wu0k?p-GK&WAKOF3-yj~jBQg^P)J%O!nn8XSIssL6^-+!rsik75@pPt zR=ApJ&febDXE(}B4q6p3PxQc>*2SkT-BDqxm(uB-^!+NE!SAT2MpN4ixSrlFY1}x? z@A<*9Z9(C)jHi3Qwus1nX{6xk|H;gz^X97{o$o&!H(c4;s@Ev(aA{Q<3$LV8EZ4Ks zbNR0|T+}p~*OVL`bim|{v4NZL$=C-i_r7-9GV5p6P5;2bksW=>BjEbe&94`18-)S67@*l$@BN*SqdN`->~`hSJF~4cY z)%MSB<YT;;w~gIpDejMYx5Q_mRLsHS*zq* z6AYLc^$!#s+xO|S!ORZJbjyQW3O}bTW2j8eF?cHO*QJu=X1e0a%=D-yjk_XukJ2@KCHco>}6Gg*LHoWax8 K&t;ucLK6VJzqx4u delta 832 zcmdnQ`;1$$Gr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qoO*Kg0qvGk*kxNrK6#l zsiC2(k(0TRk&BC=rIDkdv#XJj>*Ne38JHqRoQjq(8E8UHGRLJzNujtTC^gM0WpW{t zFw95`T&gDEaktajPfzZ1xogk@;tQCi$w}e7lZy{X7@Jj|(p3 z{dM}Xp`(4T$H8W$kdN;LmK?QTxJY4d#EKUS6&8J4Ag6n!e{z+;%CN+hyYB+EZtC10 zX?8on`{TjeGi&Adf4`R>XR^t;BzF6$yq{g?eqPl%t(IW4@b#=^w)J0^?9s2?$@W_# z?)JWnBc*qZRWI-9eW2Ou`fuud&q+267h?2ZofKpc@>m+PGi|EZX9x2x(Xy_t*7~J) zU&gSVPz!duUg*zxyKc_1%sI>cs(Q_INC;(hR@P`%(vNoY=?+u=KG*G&a%jY_ea zFukmJHzjDF*XEM-HBIO0+zbE2iMI3v|B*Oqs_CV+E&qQKYxMrjJ1-- zKPr6?-*xw$*PJ6+&e;)fCC`0TyRmNdt1jk(!uH0W(=umhoJ-rFvbkW}M(ODV`##s! z>mJ@+bc|P|vSYh-P{p}YksX#2Hw<|8T7}h4s#yB6MV>`G@zbQbPo_MFD-P&fKe;dJ zt*L`Q|Kz_4JhSJnJ=guHLu}QV-G@FdwCA~N5npm>+p8N&hkvrl#!A#m_qr|i5Zb8w zTV}_u)}QaH=53!<&af%{;Ayd4yV_$jp32{eXJlhI%gwencg?J+z)Zp5>FVdQ&MBb@ E0Q-+*u>b%7 diff --git a/pelican/tests/output/custom/theme/css/main.css b/pelican/tests/output/custom/theme/css/main.css index fa0bcf1c..01ee17d8 100644 --- a/pelican/tests/output/custom/theme/css/main.css +++ b/pelican/tests/output/custom/theme/css/main.css @@ -314,7 +314,10 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;} .social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');} .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');} .social a[href*='github.com'], - .social a[href*='git.io'] {background-image: url('../images/icons/github.png');} + .social a[href*='git.io'] { + background-image: url('../images/icons/github.png'); + background-size: 50%; + } .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} .social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');} .social a[href*='groups.google.com'] {background-image: url('../images/icons/google-groups.png');} diff --git a/pelican/tests/output/custom/theme/images/icons/github.png b/pelican/tests/output/custom/theme/images/icons/github.png index ea7ac8eeff880bb7743fdafdc8165e693becae09..8b25551a97921681334176ee143b41510a117d86 100644 GIT binary patch delta 1038 zcmaFHy@^+`Gr-TCmrII^fq{Y7)59eQNGpIa2OE$quB!U7QBj>q!PV5z+``ew(bCe< zz|hdu$kNf!)XCYRHnq)=QEl$vIhGP#gR z7-pm;F4dC_n4Ripx3KMGU|?49ba4!+xOHTDZbomS#PRc$KE}5VZaQlBbp$D=*?m0d z>2Yi2S@us8D}OGyqVbV&rEhPmh-Unh{AZ?1d0LkmX#X}iGHqjyT7GqA+_N9CrE_hn z?|lC_=W*n_U*&U)_y1Y{zt;Bq>C$#*yVFk&olx$;v-PzLWNjF?AAI_5 zUitgKAKGdy;u>!9o^qV#zt{c%YXOJiSxL?VA1lrzZn)SV;O*I9oS^Px_giei=5^N% z*aM$@4w(!0lADxuB`iTXLHI>k?ryOH z_lt{472hh?DqC&4byzYqe!o5A=J=M%j2W(+D*d+B$yybDfBnxtpM3HRTmD_`dB0j` zs4W$AA&nI(PT(B{G^o%!zl_2H9G znLo&4U}|~1Rn@3aP+&999rpugAJK0IMwi{sR)r(dQe9pCVENy)8q z?|2p~#LR75wu0k?p-GK&WAKOF3-yj~jBQg^P)J%O!nn8XSIssL6^-+!rsik75@pPt zR=ApJ&febDXE(}B4q6p3PxQc>*2SkT-BDqxm(uB-^!+NE!SAT2MpN4ixSrlFY1}x? z@A<*9Z9(C)jHi3Qwus1nX{6xk|H;gz^X97{o$o&!H(c4;s@Ev(aA{Q<3$LV8EZ4Ks zbNR0|T+}p~*OVL`bim|{v4NZL$=C-i_r7-9GV5p6P5;2bksW=>BjEbe&94`18-)S67@*l$@BN*SqdN`->~`hSJF~4cY z)%MSB<YT;;w~gIpDejMYx5Q_mRLsHS*zq* z6AYLc^$!#s+xO|S!ORZJbjyQW3O}bTW2j8eF?cHO*QJu=X1e0a%=D-yjk_XukJ2@KCHco>}6Gg*LHoWax8 K&t;ucLK6VJzqx4u delta 832 zcmdnQ`;1$$Gr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qoO*Kg0qvGk*kxNrK6#l zsiC2(k(0TRk&BC=rIDkdv#XJj>*Ne38JHqRoQjq(8E8UHGRLJzNujtTC^gM0WpW{t zFw95`T&gDEaktajPfzZ1xogk@;tQCi$w}e7lZy{X7@Jj|(p3 z{dM}Xp`(4T$H8W$kdN;LmK?QTxJY4d#EKUS6&8J4Ag6n!e{z+;%CN+hyYB+EZtC10 zX?8on`{TjeGi&Adf4`R>XR^t;BzF6$yq{g?eqPl%t(IW4@b#=^w)J0^?9s2?$@W_# z?)JWnBc*qZRWI-9eW2Ou`fuud&q+267h?2ZofKpc@>m+PGi|EZX9x2x(Xy_t*7~J) zU&gSVPz!duUg*zxyKc_1%sI>cs(Q_INC;(hR@P`%(vNoY=?+u=KG*G&a%jY_ea zFukmJHzjDF*XEM-HBIO0+zbE2iMI3v|B*Oqs_CV+E&qQKYxMrjJ1-- zKPr6?-*xw$*PJ6+&e;)fCC`0TyRmNdt1jk(!uH0W(=umhoJ-rFvbkW}M(ODV`##s! z>mJ@+bc|P|vSYh-P{p}YksX#2Hw<|8T7}h4s#yB6MV>`G@zbQbPo_MFD-P&fKe;dJ zt*L`Q|Kz_4JhSJnJ=guHLu}QV-G@FdwCA~N5npm>+p8N&hkvrl#!A#m_qr|i5Zb8w zTV}_u)}QaH=53!<&af%{;Ayd4yV_$jp32{eXJlhI%gwencg?J+z)Zp5>FVdQ&MBb@ E0Q-+*u>b%7 diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css index fa0bcf1c..01ee17d8 100644 --- a/pelican/themes/notmyidea/static/css/main.css +++ b/pelican/themes/notmyidea/static/css/main.css @@ -314,7 +314,10 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;} .social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');} .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');} .social a[href*='github.com'], - .social a[href*='git.io'] {background-image: url('../images/icons/github.png');} + .social a[href*='git.io'] { + background-image: url('../images/icons/github.png'); + background-size: 50%; + } .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} .social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');} .social a[href*='groups.google.com'] {background-image: url('../images/icons/google-groups.png');} diff --git a/pelican/themes/notmyidea/static/images/icons/github.png b/pelican/themes/notmyidea/static/images/icons/github.png index ea7ac8eeff880bb7743fdafdc8165e693becae09..8b25551a97921681334176ee143b41510a117d86 100644 GIT binary patch delta 1038 zcmaFHy@^+`Gr-TCmrII^fq{Y7)59eQNGpIa2OE$quB!U7QBj>q!PV5z+``ew(bCe< zz|hdu$kNf!)XCYRHnq)=QEl$vIhGP#gR z7-pm;F4dC_n4Ripx3KMGU|?49ba4!+xOHTDZbomS#PRc$KE}5VZaQlBbp$D=*?m0d z>2Yi2S@us8D}OGyqVbV&rEhPmh-Unh{AZ?1d0LkmX#X}iGHqjyT7GqA+_N9CrE_hn z?|lC_=W*n_U*&U)_y1Y{zt;Bq>C$#*yVFk&olx$;v-PzLWNjF?AAI_5 zUitgKAKGdy;u>!9o^qV#zt{c%YXOJiSxL?VA1lrzZn)SV;O*I9oS^Px_giei=5^N% z*aM$@4w(!0lADxuB`iTXLHI>k?ryOH z_lt{472hh?DqC&4byzYqe!o5A=J=M%j2W(+D*d+B$yybDfBnxtpM3HRTmD_`dB0j` zs4W$AA&nI(PT(B{G^o%!zl_2H9G znLo&4U}|~1Rn@3aP+&999rpugAJK0IMwi{sR)r(dQe9pCVENy)8q z?|2p~#LR75wu0k?p-GK&WAKOF3-yj~jBQg^P)J%O!nn8XSIssL6^-+!rsik75@pPt zR=ApJ&febDXE(}B4q6p3PxQc>*2SkT-BDqxm(uB-^!+NE!SAT2MpN4ixSrlFY1}x? z@A<*9Z9(C)jHi3Qwus1nX{6xk|H;gz^X97{o$o&!H(c4;s@Ev(aA{Q<3$LV8EZ4Ks zbNR0|T+}p~*OVL`bim|{v4NZL$=C-i_r7-9GV5p6P5;2bksW=>BjEbe&94`18-)S67@*l$@BN*SqdN`->~`hSJF~4cY z)%MSB<YT;;w~gIpDejMYx5Q_mRLsHS*zq* z6AYLc^$!#s+xO|S!ORZJbjyQW3O}bTW2j8eF?cHO*QJu=X1e0a%=D-yjk_XukJ2@KCHco>}6Gg*LHoWax8 K&t;ucLK6VJzqx4u delta 832 zcmdnQ`;1$$Gr-TCmrII^fq{Y7)59eQNDF{42OE%-|NK93qoO*Kg0qvGk*kxNrK6#l zsiC2(k(0TRk&BC=rIDkdv#XJj>*Ne38JHqRoQjq(8E8UHGRLJzNujtTC^gM0WpW{t zFw95`T&gDEaktajPfzZ1xogk@;tQCi$w}e7lZy{X7@Jj|(p3 z{dM}Xp`(4T$H8W$kdN;LmK?QTxJY4d#EKUS6&8J4Ag6n!e{z+;%CN+hyYB+EZtC10 zX?8on`{TjeGi&Adf4`R>XR^t;BzF6$yq{g?eqPl%t(IW4@b#=^w)J0^?9s2?$@W_# z?)JWnBc*qZRWI-9eW2Ou`fuud&q+267h?2ZofKpc@>m+PGi|EZX9x2x(Xy_t*7~J) zU&gSVPz!duUg*zxyKc_1%sI>cs(Q_INC;(hR@P`%(vNoY=?+u=KG*G&a%jY_ea zFukmJHzjDF*XEM-HBIO0+zbE2iMI3v|B*Oqs_CV+E&qQKYxMrjJ1-- zKPr6?-*xw$*PJ6+&e;)fCC`0TyRmNdt1jk(!uH0W(=umhoJ-rFvbkW}M(ODV`##s! z>mJ@+bc|P|vSYh-P{p}YksX#2Hw<|8T7}h4s#yB6MV>`G@zbQbPo_MFD-P&fKe;dJ zt*L`Q|Kz_4JhSJnJ=guHLu}QV-G@FdwCA~N5npm>+p8N&hkvrl#!A#m_qr|i5Zb8w zTV}_u)}QaH=53!<&af%{;Ayd4yV_$jp32{eXJlhI%gwencg?J+z)Zp5>FVdQ&MBb@ E0Q-+*u>b%7 From 597f1d95a63c0f870310c6ab95fa3eed54f06e13 Mon Sep 17 00:00:00 2001 From: Colin Dunklau Date: Sun, 9 Feb 2014 21:20:02 +0100 Subject: [PATCH 07/82] Use absolute size for github mark --- pelican/tests/output/basic/theme/css/main.css | 2 +- pelican/tests/output/custom/theme/css/main.css | 2 +- pelican/themes/notmyidea/static/css/main.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pelican/tests/output/basic/theme/css/main.css b/pelican/tests/output/basic/theme/css/main.css index 01ee17d8..2efb518d 100644 --- a/pelican/tests/output/basic/theme/css/main.css +++ b/pelican/tests/output/basic/theme/css/main.css @@ -316,7 +316,7 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;} .social a[href*='github.com'], .social a[href*='git.io'] { background-image: url('../images/icons/github.png'); - background-size: 50%; + background-size: 16px 16px; } .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} .social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');} diff --git a/pelican/tests/output/custom/theme/css/main.css b/pelican/tests/output/custom/theme/css/main.css index 01ee17d8..2efb518d 100644 --- a/pelican/tests/output/custom/theme/css/main.css +++ b/pelican/tests/output/custom/theme/css/main.css @@ -316,7 +316,7 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;} .social a[href*='github.com'], .social a[href*='git.io'] { background-image: url('../images/icons/github.png'); - background-size: 50%; + background-size: 16px 16px; } .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} .social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');} diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css index 01ee17d8..2efb518d 100644 --- a/pelican/themes/notmyidea/static/css/main.css +++ b/pelican/themes/notmyidea/static/css/main.css @@ -316,7 +316,7 @@ img.left, figure.left {float: left; margin: 0 2em 2em 0;} .social a[href*='github.com'], .social a[href*='git.io'] { background-image: url('../images/icons/github.png'); - background-size: 50%; + background-size: 16px 16px; } .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} .social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');} From 5bc3a7d0d84e588656fb9276a2ff5c79a7dcd9e2 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 12 Feb 2014 10:54:56 -0800 Subject: [PATCH 08/82] Revert to newly-revived Typogrify project Since the Typogrify project will be actively maintained going forward, there is no need for the fork. --- dev_requirements.txt | 2 +- docs/changelog.rst | 2 -- docs/getting_started.rst | 2 +- docs/settings.rst | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index b013a00d..c90ac630 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -5,7 +5,7 @@ mock Markdown BeautifulSoup4 lxml -typogrify-web +typogrify # To perform release bumpr==0.2.0 diff --git a/docs/changelog.rst b/docs/changelog.rst index 1e064e5f..5c8db17f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,8 +4,6 @@ Release history Next release ============ -* Replace the neglected ``typogrify`` package with the actively-maintained - ``typogrify-web`` package. * Added the `:modified:` metadata field to complement `:date:`. Used to specify the last date and time an article was updated independently from the date and time it was published. * Produce inline links instead of reference-style links when importing content. diff --git a/docs/getting_started.rst b/docs/getting_started.rst index f302d8d7..53b466a0 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -136,7 +136,7 @@ manually via ``pip``: * `markdown `_, for supporting Markdown as an input format -* `typogrify-web `_, for +* `typogrify `_, for typographical enhancements Kickstart your site diff --git a/docs/settings.rst b/docs/settings.rst index d4d5d073..ca92a19e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -136,8 +136,8 @@ Setting name (default value) section below for more info. `TYPOGRIFY` (``False``) If set to True, several typographical improvements will be incorporated into the generated HTML via the `Typogrify - `_ library, - which can be installed via: ``pip install typogrify-web`` + `_ library, + which can be installed via: ``pip install typogrify`` `DIRECT_TEMPLATES` (``('index', 'tags', '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 From 1f382c6790c6e2379cae0c6c825e491c818c1f14 Mon Sep 17 00:00:00 2001 From: Utku Demir Date: Thu, 13 Feb 2014 12:21:29 +0200 Subject: [PATCH 09/82] Fix issue #1258 Correctly handle DEFAULT_DATE = None . --- pelican/readers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/readers.py b/pelican/readers.py index 198b5a5f..5746eb34 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -521,7 +521,7 @@ def default_metadata(settings=None, process=None): if process: value = process('category', value) metadata['category'] = value - if 'DEFAULT_DATE' in settings and settings['DEFAULT_DATE'] != 'fs': + if settings.get('DEFAULT_DATE', None) and settings['DEFAULT_DATE'] != 'fs': metadata['date'] = datetime.datetime(*settings['DEFAULT_DATE']) return metadata From 92a4229bb7498281bf44204fac40c58908b541ab Mon Sep 17 00:00:00 2001 From: Anatoly Bubenkov Date: Fri, 5 Jul 2013 01:08:45 +0200 Subject: [PATCH 10/82] multiple authors implemented --- docs/changelog.rst | 1 + docs/getting_started.rst | 15 ++++--- pelican/contents.py | 10 ++++- pelican/generators.py | 8 ++-- pelican/readers.py | 4 ++ .../content/article_with_multiple_authors.rst | 6 +++ pelican/tests/test_contents.py | 11 +++++ pelican/tests/test_generators.py | 11 +++++ pelican/tests/test_importer.py | 28 ++++++------ pelican/tests/test_pelican.py | 15 ++++--- pelican/tests/test_readers.py | 9 ++++ .../notmyidea/templates/article_infos.html | 6 ++- .../themes/notmyidea/templates/authors.html | 1 - pelican/themes/simple/templates/article.html | 6 ++- pelican/themes/simple/templates/index.html | 6 ++- pelican/tools/pelican_import.py | 44 +++++++++---------- 16 files changed, 122 insertions(+), 59 deletions(-) create mode 100644 pelican/tests/content/article_with_multiple_authors.rst diff --git a/docs/changelog.rst b/docs/changelog.rst index 5c8db17f..68348da5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,7 @@ Next release * Added the `:modified:` metadata field to complement `:date:`. Used to specify the last date and time an article was updated independently from the date and time it was published. * Produce inline links instead of reference-style links when importing content. +* Multiple authors support added via new `:authors:` metadata field. 3.3.0 (2013-09-24) ================== diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 53b466a0..6655d8d6 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -311,7 +311,7 @@ this metadata in text files via the following syntax (give your file the :tags: thats, awesome :category: yeah :slug: my-super-post - :author: Alexis Metaireau + :authors: Alexis Metaireau, Conan Doyle :summary: Short version for index and feeds Pelican implements an extension to reStructuredText to enable support for the @@ -331,7 +331,7 @@ pattern:: Category: Python Tags: pelican, publishing Slug: my-super-post - Author: Alexis Metaireau + Authors: Alexis Metaireau, Conan Doyle Summary: Short version for index and feeds This is the content of my super blog post. @@ -351,7 +351,7 @@ interprets the HTML in a very straightforward manner, reading metadata from - + @@ -380,6 +380,9 @@ __ `W3C ISO 8601`_ Besides you can show ``modified`` in the templates, feed entries in feed readers will be updated automatically when you set ``modified`` to the current date after you modified your article. +``authors`` is a comma-separated list of article authors. If there's only one author you +can use ``author`` field. + If you do not explicitly specify summary metadata for a given post, the ``SUMMARY_MAX_LENGTH`` setting can be used to specify how many words from the beginning of an article are used as the summary. @@ -587,12 +590,12 @@ classprefix string String to prepend to token class names hl_lines numbers List of lines to be highlighted. lineanchors string Wrap each line in an anchor using this string and -linenumber. -linenos string If present or set to "table" output line +linenos string If present or set to "table" output line numbers in a table, if set to "inline" output them inline. "none" means - do not output the line numbers for this + do not output the line numbers for this table. -linenospecial number If set every nth line will be given the +linenospecial number If set every nth line will be given the 'special' css class. linenostart number Line number for the first line. linenostep number Print every nth line number. diff --git a/pelican/contents.py b/pelican/contents.py index 51ef8a22..69b7fa69 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -74,11 +74,17 @@ class Content(object): #default template if it's not defined in page self.template = self._get_template() - # default author to the one in settings if not defined + # First, read the authors from "authors", if not, fallback to "author" + # and if not use the settings defined one, if any. if not hasattr(self, 'author'): - if 'AUTHOR' in settings: + if hasattr(self, 'authors'): + self.author = self.authors[0] + elif 'AUTHOR' in settings: self.author = Author(settings['AUTHOR'], settings) + if not hasattr(self, 'authors') and hasattr(self, 'author'): + self.authors = [self.author] + # XXX Split all the following code into pieces, there is too much here. # manage languages diff --git a/pelican/generators.py b/pelican/generators.py index 8c5f446b..d1034eb0 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -434,7 +434,7 @@ class ArticlesGenerator(Generator): self.articles, self.translations = process_translations(all_articles) - signals.article_generator_pretaxonomy.send(self) + signals.article_generator_pretaxonomy.send(self) for article in self.articles: # only main articles are listed in categories and tags @@ -444,9 +444,9 @@ class ArticlesGenerator(Generator): for tag in article.tags: self.tags[tag].append(article) # ignore blank authors as well as undefined - if hasattr(article, 'author') and article.author.name != '': - self.authors[article.author].append(article) - + for author in getattr(article, 'authors', []): + if author.name != '': + 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 5746eb34..d48cf0e5 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -46,6 +46,7 @@ METADATA_PROCESSORS = { 'status': lambda x, y: x.strip(), 'category': Category, 'author': Author, + 'authors': lambda x, y: [Author(author, y) for author in x], } logger = logging.getLogger(__name__) @@ -144,6 +145,9 @@ class RstReader(BaseReader): value = render_node_to_html(document, body_elem) else: value = body_elem.astext() + elif element.tagname == 'authors': # author list + name = element.tagname + value = [element.astext() for element in element.children] else: # standard fields (e.g. address) name = element.tagname value = element.astext() diff --git a/pelican/tests/content/article_with_multiple_authors.rst b/pelican/tests/content/article_with_multiple_authors.rst new file mode 100644 index 00000000..04af017c --- /dev/null +++ b/pelican/tests/content/article_with_multiple_authors.rst @@ -0,0 +1,6 @@ +This is an article with multiple authors! +######################################### + +:date: 2014-02-09 02:20 +:modified: 2014-02-09 02:20 +:authors: First Author, Second Author diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 448f73eb..f831f061 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -346,6 +346,17 @@ class TestPage(unittest.TestCase): 'link' ) + def test_multiple_authors(self): + """Test article with multiple authors.""" + args = self.page_kwargs.copy() + content = Page(**args) + assert content.authors == [content.author] + args['metadata'].pop('author') + args['metadata']['authors'] = ['First Author', 'Second Author'] + content = Page(**args) + assert content.authors + assert content.author == content.authors[0] + class TestArticle(TestPage): def test_template(self): diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index eddaa504..6f13aeb6 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -93,6 +93,7 @@ class TestArticlesGenerator(unittest.TestCase): ['This is a super article !', 'published', 'Default', 'article'], ['This is an article with category !', 'published', 'yeah', 'article'], + ['This is an article with multiple authors!', 'published', 'Default', 'article'], ['This is an article without category !', 'published', 'Default', 'article'], ['This is an article without category !', 'published', @@ -257,6 +258,16 @@ class TestArticlesGenerator(unittest.TestCase): settings, blog=True, dates=dates) + def test_generate_authors(self): + """Check authors generation.""" + authors = [author.name for author, _ in self.generator.authors] + authors_expected = sorted(['Alexis Métaireau', 'First Author', 'Second Author']) + self.assertEqual(sorted(authors), authors_expected) + # test for slug + authors = [author.slug for author, _ in self.generator.authors] + authors_expected = ['alexis-metaireau', 'first-author', 'second-author'] + self.assertEqual(sorted(authors), sorted(authors_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_importer.py b/pelican/tests/test_importer.py index 9d6f911d..8412c75b 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -10,7 +10,7 @@ from pelican.tests.support import (unittest, temporary_folder, mute, from pelican.utils import slugify -CUR_DIR = os.path.dirname(__file__) +CUR_DIR = os.path.abspath(os.path.dirname(__file__)) WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml') WORDPRESS_ENCODED_CONTENT_SAMPLE = os.path.join(CUR_DIR, 'content', @@ -75,7 +75,7 @@ class TestWordpressXmlImporter(unittest.TestCase): out_name = fnames[index] self.assertTrue(out_name.endswith(filename)) index += 1 - + def test_unless_custom_post_all_items_should_be_pages_or_posts(self): self.assertTrue(self.posts) pages_data = [] @@ -85,7 +85,7 @@ class TestWordpressXmlImporter(unittest.TestCase): else: pages_data.append((title, fname)) self.assertEqual(0, len(pages_data)) - + def test_recognise_custom_post_type(self): self.assertTrue(self.custposts) cust_data = [] @@ -98,7 +98,7 @@ class TestWordpressXmlImporter(unittest.TestCase): self.assertEqual(('A custom post in category 4', 'custom1'), cust_data[0]) self.assertEqual(('A custom post in category 5', 'custom1'), cust_data[1]) self.assertEqual(('A 2nd custom post type also in category 5', 'custom2'), cust_data[2]) - + def test_custom_posts_put_in_own_dir(self): silent_f2p = mute(True)(fields2pelican) test_posts = [] @@ -130,7 +130,7 @@ class TestWordpressXmlImporter(unittest.TestCase): else: test_posts.append(post) with temporary_folder() as temp: - fnames = list(silent_f2p(test_posts, 'markdown', temp, + fnames = list(silent_f2p(test_posts, 'markdown', temp, wp_custpost=True, dircat=True)) index = 0 for post in test_posts: @@ -152,7 +152,7 @@ class TestWordpressXmlImporter(unittest.TestCase): if post[7] == 'page': test_posts.append(post) with temporary_folder() as temp: - fnames = list(silent_f2p(test_posts, 'markdown', temp, + fnames = list(silent_f2p(test_posts, 'markdown', temp, wp_custpost=True, dirpage=False)) index = 0 for post in test_posts: @@ -161,8 +161,8 @@ class TestWordpressXmlImporter(unittest.TestCase): filename = os.path.join('pages', name) out_name = fnames[index] self.assertFalse(out_name.endswith(filename)) - - + + def test_can_toggle_raw_html_code_parsing(self): def r(f): with open(f) as infile: @@ -247,9 +247,9 @@ class TestBuildHeader(unittest.TestCase): '##############################################\n\n') def test_galleries_added_to_header(self): - header = build_header('test', None, None, None, None, + header = build_header('test', None, None, None, None, None, ['output/test1', 'output/test2']) - self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, ' + self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, ' + 'output/test2\n\n') def test_galleries_added_to_markdown_header(self): @@ -258,11 +258,11 @@ 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): +@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') +class TestWordpressXMLAttachements(unittest.TestCase): def setUp(self): self.attachments = get_attachments(WORDPRESS_XML_SAMPLE) - + def test_recognise_attachments(self): self.assertTrue(self.attachments) self.assertTrue(len(self.attachments.keys()) == 3) @@ -283,7 +283,7 @@ class TestWordpressXMLAttachements(unittest.TestCase): def test_download_attachments(self): real_file = os.path.join(CUR_DIR, 'content/article.rst') good_url = 'file://' + real_file - bad_url = 'http://www.notarealsite.notarealdomain/not_a_file.txt' + 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]) diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 8cccc918..21a77e6b 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -2,11 +2,11 @@ from __future__ import unicode_literals, print_function import os -from filecmp import dircmp from tempfile import mkdtemp from shutil import rmtree import locale import logging +import subprocess from pelican import Pelican from pelican.settings import read_settings @@ -64,6 +64,13 @@ class TestPelican(LoggedTestCase): 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() + assert not out, out + assert not err, err + def test_basic_generation_works(self): # when running pelican without settings, it should pick up the default # ones and generate correct output without raising any exception @@ -74,8 +81,7 @@ class TestPelican(LoggedTestCase): }) pelican = Pelican(settings=settings) mute(True)(pelican.run)() - dcmp = dircmp(self.temp_path, os.path.join(OUTPUT_PATH, 'basic')) - self.assertFilesEqual(recursiveDiff(dcmp)) + self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'basic')) self.assertLogCountEqual( count=4, msg="Unable to find.*skipping url replacement", @@ -90,8 +96,7 @@ class TestPelican(LoggedTestCase): }) pelican = Pelican(settings=settings) mute(True)(pelican.run)() - dcmp = dircmp(self.temp_path, os.path.join(OUTPUT_PATH, 'custom')) - self.assertFilesEqual(recursiveDiff(dcmp)) + self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'custom')) def test_theme_static_paths_copy(self): # the same thing with a specified set of settings should work diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index e827ed3b..acb268fb 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -360,6 +360,15 @@ class HTMLReaderTest(ReaderTest): for key, value in expected.items(): self.assertEqual(value, page.metadata[key], key) + def test_article_with_multiple_authors(self): + page = self.read_file(path='article_with_multiple_authors.rst') + expected = { + 'authors': ['First Author', 'Second Author'] + } + + for key, value in expected.items(): + self.assertEqual(value, page.metadata[key], key) + def test_article_with_metadata_and_contents_attrib(self): page = self.read_file(path='article_with_metadata_and_contents.html') expected = { diff --git a/pelican/themes/notmyidea/templates/article_infos.html b/pelican/themes/notmyidea/templates/article_infos.html index 54c88004..718967e0 100644 --- a/pelican/themes/notmyidea/templates/article_infos.html +++ b/pelican/themes/notmyidea/templates/article_infos.html @@ -9,9 +9,11 @@ {% endif %} - {% if article.author %} + {% if article.authors %}
- By {{ article.author }} + By {% for author in article.authors %} + {{ author }} + {% endfor %}
{% endif %}

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

diff --git a/pelican/themes/notmyidea/templates/authors.html b/pelican/themes/notmyidea/templates/authors.html index a203422b..b145902e 100644 --- a/pelican/themes/notmyidea/templates/authors.html +++ b/pelican/themes/notmyidea/templates/authors.html @@ -6,7 +6,6 @@

Authors on {{ SITENAME }}

- {%- for author, articles in authors|sort %}
  • {{ author }} ({{ articles|count }})
  • {% endfor %} diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index b5368d9f..e81261c5 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -33,9 +33,11 @@ {{ article.locale_modified }} {% endif %} - {% if article.author %} + {% if article.authors %}
    - By {{ article.author }} + By {% for author in article.authors %} + {{ author }} + {% endfor %}
    {% endif %} diff --git a/pelican/themes/simple/templates/index.html b/pelican/themes/simple/templates/index.html index 5bb94dfc..20104db9 100644 --- a/pelican/themes/simple/templates/index.html +++ b/pelican/themes/simple/templates/index.html @@ -11,7 +11,11 @@

    {{ article.title }}

    {{ article.locale_date }} - {% if article.author %}
    By {{ article.author }}
    {% endif %} +
    By + {% for author in article.authors %} + {{ author }} + {% endfor %} +
    {{ article.summary }}
    diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index b04906c0..d6b57c47 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -124,7 +124,7 @@ def get_filename(filename, post_id): def wp2fields(xml, wp_custpost=False): """Opens a wordpress XML file, and yield Pelican fields""" - + items = get_items(xml) for item in items: @@ -140,7 +140,7 @@ def wp2fields(xml, wp_custpost=False): filename = item.find('post_name').string post_id = item.find('post_id').string filename = get_filename(filename, post_id) - + content = item.find('encoded').string raw_date = item.find('post_date').string date_object = time.strptime(raw_date, "%Y-%m-%d %H:%M:%S") @@ -161,7 +161,7 @@ def wp2fields(xml, wp_custpost=False): pass # Old behaviour was to name everything not a page as an article. # Theoretically all attachments have status == inherit so - # no attachments should be here. But this statement is to + # no attachments should be here. But this statement is to # maintain existing behaviour in case that doesn't hold true. elif post_type == 'attachment': pass @@ -469,7 +469,7 @@ def build_header(title, date, author, categories, tags, slug, attachments=None): header += '\n' return header -def build_markdown_header(title, date, author, categories, tags, slug, +def build_markdown_header(title, date, author, categories, tags, slug, attachments=None): """Build a header from a list of fields""" header = 'Title: %s\n' % title @@ -494,8 +494,8 @@ def get_ext(out_markup, in_markup='html'): else: ext = '.rst' return ext - -def get_out_filename(output_path, filename, ext, kind, + +def get_out_filename(output_path, filename, ext, kind, dirpage, dircat, categories, wp_custpost): filename = os.path.basename(filename) @@ -516,7 +516,7 @@ def get_out_filename(output_path, filename, ext, kind, os.mkdir(pages_dir) out_filename = os.path.join(pages_dir, filename+ext) elif not dirpage and kind == 'page': - pass + pass # option to put wp custom post types in directories with post type # names. Custom post types can also have categories so option to # create subdirectories with category names @@ -530,7 +530,7 @@ def get_out_filename(output_path, filename, ext, kind, catname = slugify(categories[0]) else: catname = '' - out_filename = os.path.join(output_path, typename, + out_filename = os.path.join(output_path, typename, catname, filename+ext) if not os.path.isdir(os.path.join(output_path, typename, catname)): os.makedirs(os.path.join(output_path, typename, catname)) @@ -544,20 +544,20 @@ def get_out_filename(output_path, filename, ext, kind, return out_filename def get_attachments(xml): - """returns a dictionary of posts that have attachments with a list + """returns a dictionary of posts that have attachments with a list of the attachment_urls """ items = get_items(xml) names = {} attachments = [] - + for item in items: kind = item.find('post_type').string filename = item.find('post_name').string post_id = item.find('post_id').string - + if kind == 'attachment': - attachments.append((item.find('post_parent').string, + attachments.append((item.find('post_parent').string, item.find('attachment_url').string)) else: filename = get_filename(filename, post_id) @@ -569,7 +569,7 @@ def get_attachments(xml): except KeyError: #attachment's parent is not a valid post parent_name = None - + try: attachedposts[parent_name].append(url) except KeyError: @@ -578,13 +578,13 @@ def get_attachments(xml): return attachedposts def download_attachments(output_path, urls): - """Downloads wordpress attachments and returns a list of paths to - attachments that can be associated with a post (relative path to output + """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 = [] for url in urls: path = urlparse(url).path - #teardown path and rebuild to negate any errors with + #teardown path and rebuild to negate any errors with #os.path.join and leading /'s path = path.split('/') filename = path.pop(-1) @@ -625,16 +625,16 @@ def fields2pelican(fields, out_markup, output_path, attached_files = download_attachments(output_path, urls) except KeyError: attached_files = None - else: + else: attached_files = None ext = get_ext(out_markup, in_markup) if ext == '.md': - header = build_markdown_header(title, date, author, categories, + header = build_markdown_header(title, date, author, categories, tags, slug, attached_files) else: out_markup = "rst" - header = build_header(title, date, author, categories, + header = build_header(title, date, author, categories, tags, slug, attached_files) out_filename = get_out_filename(output_path, filename, ext, @@ -690,7 +690,7 @@ def fields2pelican(fields, out_markup, output_path, print("downloading attachments that don't have a parent post") urls = attachments[None] orphan_galleries = download_attachments(output_path, urls) - + def main(): parser = argparse.ArgumentParser( description="Transform feed, WordPress, Tumblr, Dotclear, or Posterous " @@ -723,7 +723,7 @@ def main(): parser.add_argument('--strip-raw', action='store_true', dest='strip_raw', help="Strip raw HTML code that can't be converted to " "markup such as flash embeds or iframes (wordpress import only)") - parser.add_argument('--wp-custpost', action='store_true', + parser.add_argument('--wp-custpost', action='store_true', dest='wp_custpost', help='Put wordpress custom post types in directories. If used with ' '--dir-cat option directories will be created as ' @@ -775,7 +775,7 @@ def main(): if args.wp_attach and input_type != 'wordpress': error = "You must be importing a wordpress xml to use the --wp-attach option" exit(error) - + if input_type == 'wordpress': fields = wp2fields(args.input, args.wp_custpost or False) elif input_type == 'dotclear': From cba5a888aca4ee996d08516100d719148dd03581 Mon Sep 17 00:00:00 2001 From: Rob Kennedy Date: Thu, 13 Feb 2014 15:46:33 -0800 Subject: [PATCH 11/82] Allow socket address reuse; fixed #1264 The socket may remain in the TIME_WAIT state for some time after the server shuts down, which prevents another instance of the server from listening on the same port. This change allows the server to reuse the address even when it's still waiting. --- pelican/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pelican/server.py b/pelican/server.py index 5294e7dc..bb31b234 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -36,6 +36,7 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): Handler = ComplexHTTPRequestHandler +socketserver.TCPServer.allow_reuse_address = True try: httpd = socketserver.TCPServer(("", PORT), Handler) except OSError as e: From 47ec2bc4a4ca3c3ddc2ef11d8563d7881d57f7d4 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 14 Feb 2014 14:37:54 -0800 Subject: [PATCH 12/82] Add squashing to CONTRIBUTING docs. Fix URLs. --- CONTRIBUTING.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 9422176a..6063613b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -22,12 +22,14 @@ Contribution submission guidelines #585`` syntax (so the relevant issue is automatically closed upon PR merge). * After the first line of the commit message, add a blank line and then a more detailed explanation (when relevant). +* `Squash your commits`_ to eliminate merge commits and ensure a clean and + readable commit history. * If you have previously filed a GitHub issue and want to contribute code that addresses that issue, **please use** ``hub pull-request`` instead of using GitHub's web UI to submit the pull request. This isn't an absolute requirement, but makes the maintainers' lives much easier! Specifically: - `install hub `_ and then run - `hub pull-request `_ to + `install hub `_ and then run + `hub pull-request `_ to turn your GitHub issue into a pull request containing your code. Check out our `Git Tips`_ page or ask on the `#pelican IRC channel`_ if you @@ -36,6 +38,7 @@ need assistance or have any questions about these guidelines. .. _`plugin`: http://docs.getpelican.com/en/latest/plugins.html .. _`#pelican IRC channel`: http://webchat.freenode.net/?channels=pelican&uio=d4 .. _`Create a new git branch`: https://github.com/getpelican/pelican/wiki/Git-Tips#making-your-changes +.. _`Squash your commits`: https://github.com/getpelican/pelican/wiki/Git-Tips#squashing-commits .. _`Run all the tests`: http://docs.getpelican.com/en/latest/contribute.html#running-the-test-suite .. _`Git Tips`: https://github.com/getpelican/pelican/wiki/Git-Tips .. _`PEP8 coding standards`: http://www.python.org/dev/peps/pep-0008/ From 43f8c2c246aa2618f4023b7569fd09e5350cd88e Mon Sep 17 00:00:00 2001 From: Mario Lang Date: Wed, 12 Feb 2014 11:47:22 +0100 Subject: [PATCH 13/82] HTML error in notmyidea:
  • not allowed in
    .
  • tags need to be inside of
      or
        . Thanks to the w3c_validate plugin for finding this. --- pelican/tests/output/basic/authors.html | 7 +++++-- pelican/tests/output/basic/tags.html | 7 +++++-- pelican/tests/output/custom/authors.html | 7 +++++-- pelican/tests/output/custom/tags.html | 7 +++++-- pelican/themes/notmyidea/templates/authors.html | 4 +++- pelican/themes/notmyidea/templates/tags.html | 5 +++-- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/pelican/tests/output/basic/authors.html b/pelican/tests/output/basic/authors.html index 4022cbab..20df01d2 100644 --- a/pelican/tests/output/basic/authors.html +++ b/pelican/tests/output/basic/authors.html @@ -26,7 +26,10 @@
        -

        Authors on A Pelican Blog

      1. Alexis Métaireau (2)
      2. +

        Authors on A Pelican Blog

        +
        @@ -48,4 +51,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/tags.html b/pelican/tests/output/basic/tags.html index 8172f6ae..0eda47d7 100644 --- a/pelican/tests/output/basic/tags.html +++ b/pelican/tests/output/basic/tags.html @@ -26,12 +26,15 @@
        -

        Tags for A Pelican Blog

      3. bar (3)
      4. +

        Tags for A Pelican Blog

        +
        @@ -53,4 +56,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/custom/authors.html b/pelican/tests/output/custom/authors.html index eb2becfd..d9aaef34 100644 --- a/pelican/tests/output/custom/authors.html +++ b/pelican/tests/output/custom/authors.html @@ -30,7 +30,10 @@
        -

        Authors on Alexis' log

      5. Alexis Métaireau (10)
      6. +

        Authors on Alexis' log

        +
        @@ -76,4 +79,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tags.html b/pelican/tests/output/custom/tags.html index 2e70c9e8..ba8d53a4 100644 --- a/pelican/tests/output/custom/tags.html +++ b/pelican/tests/output/custom/tags.html @@ -30,12 +30,15 @@
        -

        Tags for Alexis' log

      7. bar (3)
      8. +

        Tags for Alexis' log

        +
        @@ -81,4 +84,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/themes/notmyidea/templates/authors.html b/pelican/themes/notmyidea/templates/authors.html index b145902e..e61a332f 100644 --- a/pelican/themes/notmyidea/templates/authors.html +++ b/pelican/themes/notmyidea/templates/authors.html @@ -6,9 +6,11 @@

        Authors on {{ SITENAME }}

        - {%- for author, articles in authors|sort %} +
          + {% for author, articles in authors|sort %}
        • {{ author }} ({{ articles|count }})
        • {% endfor %} +
        {% endblock %} diff --git a/pelican/themes/notmyidea/templates/tags.html b/pelican/themes/notmyidea/templates/tags.html index 76955f27..fb099557 100644 --- a/pelican/themes/notmyidea/templates/tags.html +++ b/pelican/themes/notmyidea/templates/tags.html @@ -6,10 +6,11 @@

        Tags for {{ SITENAME }}

        - - {%- for tag, articles in tags|sort %} +
          + {% for tag, articles in tags|sort %}
        • {{ tag }} ({{ articles|count }})
        • {% endfor %} +
        {% endblock %} From 66441e0efb6d3324fc7d38c7a71ab88487bd6beb Mon Sep 17 00:00:00 2001 From: Mario Lang Date: Wed, 12 Feb 2014 12:42:27 +0100 Subject: [PATCH 14/82] Fix stray
      and
  • if only one article is displayed. We already check if loop.length > 1 before outputting
    and
      tags, but we neglected to do the same check when outputting the corresponding end tags. Also, since I had to read the code when I touched it, simplified a conditional: if (a) if (a and (b or not b and c)) can be simplified to if (a) if (b or c) Note the "b or not b", it was just too ugly to not fix. --- pelican/tests/output/basic/category/bar.html | 4 +--- pelican/tests/output/basic/category/yeah.html | 4 +--- pelican/tests/output/basic/tag/foobar.html | 4 +--- pelican/tests/output/basic/tag/yeah.html | 4 +--- pelican/tests/output/custom/category/bar.html | 4 +--- pelican/tests/output/custom/category/yeah.html | 4 +--- pelican/tests/output/custom/tag/foobar.html | 4 +--- pelican/tests/output/custom/tag/yeah.html | 4 +--- pelican/themes/notmyidea/templates/index.html | 11 +++++++---- 9 files changed, 15 insertions(+), 28 deletions(-) diff --git a/pelican/tests/output/basic/category/bar.html b/pelican/tests/output/basic/category/bar.html index 54b1db3e..763cfe77 100644 --- a/pelican/tests/output/basic/category/bar.html +++ b/pelican/tests/output/basic/category/bar.html @@ -46,8 +46,6 @@ YEAH !

      -
    -
    - -
    - -
    - -

    blogroll

    @@ -100,4 +98,4 @@ YEAH !

    }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/category/yeah.html b/pelican/tests/output/custom/category/yeah.html index c8e066c8..11fd181d 100644 --- a/pelican/tests/output/custom/category/yeah.html +++ b/pelican/tests/output/custom/category/yeah.html @@ -63,8 +63,6 @@ Page 1 / 1

    - -

    blogroll

    @@ -108,4 +106,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tag/foobar.html b/pelican/tests/output/custom/tag/foobar.html index c62d3418..c224992e 100644 --- a/pelican/tests/output/custom/tag/foobar.html +++ b/pelican/tests/output/custom/tag/foobar.html @@ -63,8 +63,6 @@ Page 1 / 1

    - -

    blogroll

    @@ -108,4 +106,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tag/yeah.html b/pelican/tests/output/custom/tag/yeah.html index 288f4ec7..49c78359 100644 --- a/pelican/tests/output/custom/tag/yeah.html +++ b/pelican/tests/output/custom/tag/yeah.html @@ -55,8 +55,6 @@ YEAH !

    Page 1 / 1

    - -

    blogroll

    @@ -100,4 +98,4 @@ YEAH !

    }()); - \ No newline at end of file + diff --git a/pelican/themes/notmyidea/templates/index.html b/pelican/themes/notmyidea/templates/index.html index 2d45bb2a..c8982476 100644 --- a/pelican/themes/notmyidea/templates/index.html +++ b/pelican/themes/notmyidea/templates/index.html @@ -42,12 +42,15 @@
  • {% endif %} {% if loop.last %} - - {% if loop.last and (articles_page.has_previous() - or not articles_page.has_previous() and loop.length > 1) %} + {% if loop.length > 1 %} + + {% endif %} + {% if articles_page.has_previous() or loop.length > 1 %} {% include 'pagination.html' %} {% endif %} -
    + {% if loop.length > 1 %} + + {% endif %} {% endif %} {% endfor %} {% else %} From d4c3b616a9e135382e9be3b4889ae7412cea56a0 Mon Sep 17 00:00:00 2001 From: Mario Lang Date: Sun, 9 Feb 2014 15:17:23 +0100 Subject: [PATCH 15/82] Some browsers like Lynx render adjacent links without implicit spaces. Put an extra space at the end of each link to a tag so that Lynx doesnt render the tags as a single word. --- pelican/themes/notmyidea/templates/taglist.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/themes/notmyidea/templates/taglist.html b/pelican/themes/notmyidea/templates/taglist.html index c792fd7d..b8f4ba95 100644 --- a/pelican/themes/notmyidea/templates/taglist.html +++ b/pelican/themes/notmyidea/templates/taglist.html @@ -1,2 +1,2 @@ -{% if article.tags %}

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

    {% endif %} +{% if article.tags %}

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

    {% endif %} {% if PDF_PROCESSOR %}

    get the pdf

    {% endif %} From c3a8d80f42056e26800c552d58ec4b93bb6d3842 Mon Sep 17 00:00:00 2001 From: Mario Lang Date: Tue, 18 Feb 2014 15:01:31 +0100 Subject: [PATCH 16/82] Run tag name through escape filter to avoid invalid HTML If a tag contains characters like <> or &, we currently generate invalid HTML. This is easily fixed by sending the tag through the jinja escape filter. (This bug is not theoretical, I hit it when using C++ template names for tags, like "boost::variant<>".) --- pelican/themes/notmyidea/templates/taglist.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/themes/notmyidea/templates/taglist.html b/pelican/themes/notmyidea/templates/taglist.html index b8f4ba95..1e0b95a7 100644 --- a/pelican/themes/notmyidea/templates/taglist.html +++ b/pelican/themes/notmyidea/templates/taglist.html @@ -1,2 +1,2 @@ -{% if article.tags %}

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

    {% endif %} +{% if article.tags %}

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

    {% endif %} {% if PDF_PROCESSOR %}

    get the pdf

    {% endif %} From a8c772289a3f7197c2f95b69c55bf598b421a754 Mon Sep 17 00:00:00 2001 From: Ben Bridts Date: Tue, 18 Feb 2014 17:56:57 +0100 Subject: [PATCH 17/82] Split multiple authors on ',' --- pelican/readers.py | 2 +- pelican/tests/content/article_with_multiple_authors.html | 6 ++++++ pelican/tests/test_readers.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 pelican/tests/content/article_with_multiple_authors.html diff --git a/pelican/readers.py b/pelican/readers.py index d48cf0e5..a073f11f 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -46,7 +46,7 @@ METADATA_PROCESSORS = { 'status': lambda x, y: x.strip(), 'category': Category, 'author': Author, - 'authors': lambda x, y: [Author(author, y) for author in x], + 'authors': lambda x, y: [Author(author.strip(), y) for author in x.split(',')], } logger = logging.getLogger(__name__) diff --git a/pelican/tests/content/article_with_multiple_authors.html b/pelican/tests/content/article_with_multiple_authors.html new file mode 100644 index 00000000..a74442c9 --- /dev/null +++ b/pelican/tests/content/article_with_multiple_authors.html @@ -0,0 +1,6 @@ + + + This is an article with multiple authors! + + + diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index acb268fb..6274bdc5 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -361,7 +361,7 @@ class HTMLReaderTest(ReaderTest): self.assertEqual(value, page.metadata[key], key) def test_article_with_multiple_authors(self): - page = self.read_file(path='article_with_multiple_authors.rst') + page = self.read_file(path='article_with_multiple_authors.html') expected = { 'authors': ['First Author', 'Second Author'] } From 656e85a2ddb26c0ef1149eb91b389bef764aeab5 Mon Sep 17 00:00:00 2001 From: Stefan hr Berder Date: Sun, 16 Feb 2014 12:51:52 +0100 Subject: [PATCH 18/82] change date metadata parsing to dateutil.parser --- docs/getting_started.rst | 2 ++ pelican/tests/test_utils.py | 32 +++++++++++++++++++++++++++--- pelican/utils.py | 39 +++++-------------------------------- setup.py | 2 +- 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 6655d8d6..8ee37162 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -130,6 +130,8 @@ automatically installed without any action on your part: utilities * `MarkupSafe `_, for a markup safe string implementation +* `python-dateutil `_, to read + the date metadata If you want the following optional packages, you will need to install them manually via ``pip``: diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index f6f96a1c..9047593f 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -41,6 +41,12 @@ class TestUtils(LoggedTestCase): date = datetime.datetime(year=2012, month=11, day=22) date_hour = datetime.datetime( year=2012, month=11, day=22, hour=22, minute=11) + date_hour_z = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11, + tzinfo=pytz.timezone('UTC')) + date_hour_est = datetime.datetime( + year=2012, month=11, day=22, hour=22, minute=11, + tzinfo=pytz.timezone('EST')) date_hour_sec = datetime.datetime( year=2012, month=11, day=22, hour=22, minute=11, second=10) date_hour_sec_z = datetime.datetime( @@ -61,22 +67,42 @@ class TestUtils(LoggedTestCase): '22/11/2012': date, '22.11.2012': date, '22.11.2012 22:11': date_hour, + '2012-11-22T22:11Z': date_hour_z, + '2012-11-22T22:11-0500': date_hour_est, '2012-11-22 22:11:10': date_hour_sec, '2012-11-22T22:11:10Z': date_hour_sec_z, '2012-11-22T22:11:10-0500': date_hour_sec_est, '2012-11-22T22:11:10.123Z': date_hour_sec_frac_z, } + # examples from http://www.w3.org/TR/NOTE-datetime + iso_8601_date = datetime.datetime(year=1997, month=7, day=16) + iso_8601_date_hour_tz = datetime.datetime( + year=1997, month=7, day=16, hour=19, minute=20, + tzinfo=pytz.timezone('CET')) + iso_8601_date_hour_sec_tz = datetime.datetime( + year=1997, month=7, day=16, hour=19, minute=20, second=30, + tzinfo=pytz.timezone('CET')) + iso_8601_date_hour_sec_ms_tz = datetime.datetime( + year=1997, month=7, day=16, hour=19, minute=20, second=30, + microsecond=450000, tzinfo=pytz.timezone('CET')) + iso_8601 = { + '1997-07-16': iso_8601_date, + '1997-07-16T19:20+01:00': iso_8601_date_hour_tz, + '1997-07-16T19:20:30+01:00': iso_8601_date_hour_sec_tz, + '1997-07-16T19:20:30.45+01:00': iso_8601_date_hour_sec_ms_tz, + } + # invalid ones invalid_dates = ['2010-110-12', 'yay'] - if version_info < (3, 2): - dates.pop('2012-11-22T22:11:10-0500') - invalid_dates.append('2012-11-22T22:11:10-0500') for value, expected in dates.items(): self.assertEqual(utils.get_date(value), expected, value) + for value, expected in iso_8601.items(): + self.assertEqual(utils.get_date(value), expected, value) + for item in invalid_dates: self.assertRaises(ValueError, utils.get_date, item) diff --git a/pelican/utils.py b/pelican/utils.py index 822e50e9..c5aacaa3 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -15,7 +15,7 @@ import traceback from collections import Hashable from contextlib import contextmanager -from datetime import datetime +import dateutil.parser from functools import partial from itertools import groupby from jinja2 import Markup @@ -181,39 +181,10 @@ def get_date(string): If no format matches the given date, raise a ValueError. """ string = re.sub(' +', ' ', string) - formats = [ - # ISO 8601 - '%Y', - '%Y-%m', - '%Y-%m-%d', - '%Y-%m-%dT%H:%M%z', - '%Y-%m-%dT%H:%MZ', - '%Y-%m-%dT%H:%M', - '%Y-%m-%dT%H:%M:%S%z', - '%Y-%m-%dT%H:%M:%SZ', - '%Y-%m-%dT%H:%M:%S', - '%Y-%m-%dT%H:%M:%S.%f%z', - '%Y-%m-%dT%H:%M:%S.%fZ', - '%Y-%m-%dT%H:%M:%S.%f', - # end ISO 8601 forms - '%Y-%m-%d %H:%M', - '%Y-%m-%d %H:%M:%S', - '%Y/%m/%d %H:%M', - '%Y/%m/%d', - '%d-%m-%Y', - '%d.%m.%Y %H:%M', - '%d.%m.%Y', - '%d/%m/%Y', - ] - for date_format in formats: - try: - date = datetime.strptime(string, date_format) - except ValueError: - continue - if date_format.endswith('Z'): - date = date.replace(tzinfo=pytz.timezone('UTC')) - return date - raise ValueError('{0!r} is not a valid date'.format(string)) + try: + return dateutil.parser.parse(string) + except (TypeError, ValueError): + raise ValueError('{0!r} is not a valid date'.format(string)) @contextmanager diff --git a/setup.py b/setup.py index f56a7c41..e989d549 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup requires = ['feedgenerator >= 1.6', 'jinja2 >= 2.7', 'pygments', 'docutils', - 'pytz >= 0a', 'blinker', 'unidecode', 'six'] + 'pytz >= 0a', 'blinker', 'unidecode', 'six', 'python-dateutil'] entry_points = { 'console_scripts': [ From fde856ec45d3796ccd2c8898d31f6031026c1cac Mon Sep 17 00:00:00 2001 From: Stefan hr Berder Date: Thu, 26 Dec 2013 19:30:55 +0100 Subject: [PATCH 19/82] add lang support for drafts (#826 & #1107) Fix #826 Fix #1107 --- docs/settings.rst | 10 +++++-- pelican/__init__.py | 12 ++++++--- pelican/contents.py | 5 ++++ pelican/generators.py | 27 +++++++++++++------ pelican/settings.py | 4 +++ .../output/custom/drafts/a-draft-article.html | 4 +-- 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index ca92a19e..a8e96d71 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -237,12 +237,18 @@ posts for the month at ``posts/2011/Aug/index.html``. ==================================================== ===================================================== Setting name (default value) What does it do? ==================================================== ===================================================== -`ARTICLE_URL` (``'{slug}.html'``) The URL to refer to an ARTICLE. +`ARTICLE_URL` (``'{slug}.html'``) The URL to refer to an article. `ARTICLE_SAVE_AS` (``'{slug}.html'``) The place where we will save an article. -`ARTICLE_LANG_URL` (``'{slug}-{lang}.html'``) The URL to refer to an ARTICLE which doesn't use the +`ARTICLE_LANG_URL` (``'{slug}-{lang}.html'``) The URL to refer to an article which doesn't use the default language. `ARTICLE_LANG_SAVE_AS` (``'{slug}-{lang}.html'``) The place where we will save an article which doesn't use the default language. +`DRAFT_URL` (``'drafts/{slug}.html'``) The URL to refer to an article draft. +`DRAFT_SAVE_AS` (``'drafts/{slug}.html'``) The place where we will save an article draft. +`DRAFT_LANG_URL` (``'drafts/{slug}-{lang}.html'``) The URL to refer to an article draft which doesn't + use the default language. +`DRAFT_LANG_SAVE_AS` (``'drafts/{slug}-{lang}.html'``) The place where we will save an article draft which + doesn't use the default language. `PAGE_URL` (``'pages/{slug}.html'``) The URL we will use to link to a page. `PAGE_SAVE_AS` (``'pages/{slug}.html'``) The location we will save the page. This value has to be the same as PAGE_URL or you need to use a rewrite in diff --git a/pelican/__init__.py b/pelican/__init__.py index 47260551..08dd484e 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -114,9 +114,10 @@ class Pelican(object): structure = re.sub('^/', '', structure) for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL', - 'PAGE_LANG_URL', 'ARTICLE_SAVE_AS', - 'ARTICLE_LANG_SAVE_AS', 'PAGE_SAVE_AS', - 'PAGE_LANG_SAVE_AS'): + 'PAGE_LANG_URL', 'DRAFT_URL', 'DRAFT_LANG_URL', + 'ARTICLE_SAVE_AS', 'ARTICLE_LANG_SAVE_AS', + 'DRAFT_SAVE_AS', 'DRAFT_LANG_SAVE_AS', + 'PAGE_SAVE_AS', 'PAGE_LANG_SAVE_AS'): self.settings[setting] = os.path.join(structure, self.settings[setting]) logger.warning("%s = '%s'" % (setting, self.settings[setting])) @@ -174,8 +175,11 @@ class Pelican(object): pages_generator = next(g for g in generators if isinstance(g, PagesGenerator)) - print('Done: Processed {} articles and {} pages in {:.2f} seconds.'.format( + print('Done: Processed {} article(s), {} draft(s) and {} page(s) in ' \ + '{:.2f} seconds.'.format( len(articles_generator.articles) + len(articles_generator.translations), + len(articles_generator.drafts) + \ + len(articles_generator.drafts_translations), len(pages_generator.pages) + len(pages_generator.translations), time.time() - start_time)) diff --git a/pelican/contents.py b/pelican/contents.py index 69b7fa69..2ba81c1d 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -328,6 +328,11 @@ class Article(Page): default_template = 'article' +class Draft(Page): + mandatory_properties = ('title', 'category') + default_template = 'article' + + class Quote(Page): base_properties = ('author', 'date') diff --git a/pelican/generators.py b/pelican/generators.py index d1034eb0..bfdac1a5 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -18,7 +18,7 @@ from operator import attrgetter, itemgetter from jinja2 import (Environment, FileSystemLoader, PrefixLoader, ChoiceLoader, BaseLoader, TemplateNotFound) -from pelican.contents import Article, Page, Static, is_valid_content +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 from pelican import signals @@ -190,7 +190,8 @@ class ArticlesGenerator(Generator): self.categories = defaultdict(list) self.related_posts = [] self.authors = defaultdict(list) - self.drafts = [] + self.drafts = [] # only drafts in default language + self.drafts_translations = [] super(ArticlesGenerator, self).__init__(*args, **kwargs) signals.article_generator_init.send(self) @@ -376,11 +377,11 @@ class ArticlesGenerator(Generator): def generate_drafts(self, write): """Generate drafts pages.""" - for article in self.drafts: - write(os.path.join('drafts', '%s.html' % article.slug), - self.get_template(article.template), self.context, - article=article, category=article.category, - all_articles=self.articles) + for draft in chain(self.drafts_translations, self.drafts): + write(draft.save_as, self.get_template(draft.template), + self.context, article=draft, category=draft.category, + override_output=hasattr(draft, 'override_save_as'), + all_articles=self.articles) def generate_pages(self, writer): """Generate the pages on the disk""" @@ -403,6 +404,7 @@ class ArticlesGenerator(Generator): """Add the articles into the shared context""" all_articles = [] + all_drafts = [] for f in self.get_files( self.settings['ARTICLE_DIR'], exclude=self.settings['ARTICLE_EXCLUDES']): @@ -426,13 +428,22 @@ class ArticlesGenerator(Generator): if article.status.lower() == "published": all_articles.append(article) elif article.status.lower() == "draft": - self.drafts.append(article) + draft = self.readers.read_file( + base_path=self.path, path=f, content_class=Draft, + context=self.context, + preread_signal=signals.article_generator_preread, + preread_sender=self, + context_signal=signals.article_generator_context, + context_sender=self) + all_drafts.append(draft) else: logger.warning("Unknown status %s for file %s, skipping it." % (repr(article.status), repr(f))) self.articles, self.translations = process_translations(all_articles) + self.drafts, self.drafts_translations = \ + process_translations(all_drafts) signals.article_generator_pretaxonomy.send(self) diff --git a/pelican/settings.py b/pelican/settings.py index 99828935..225a1e9d 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -59,6 +59,10 @@ DEFAULT_CONFIG = { 'ARTICLE_SAVE_AS': '{slug}.html', '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_LANG_URL': 'drafts/{slug}-{lang}.html', + 'DRAFT_LANG_SAVE_AS': os.path.join('drafts', '{slug}-{lang}.html'), 'PAGE_URL': 'pages/{slug}.html', 'PAGE_SAVE_AS': os.path.join('pages', '{slug}.html'), 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html', diff --git a/pelican/tests/output/custom/drafts/a-draft-article.html b/pelican/tests/output/custom/drafts/a-draft-article.html index 9050c04e..57eea18c 100644 --- a/pelican/tests/output/custom/drafts/a-draft-article.html +++ b/pelican/tests/output/custom/drafts/a-draft-article.html @@ -32,7 +32,7 @@

    - A draft article

    @@ -97,4 +97,4 @@ listed anywhere else.

    }()); - \ No newline at end of file + From 49a055991271c027bcf85eaf6cdecae127311f9f Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sat, 1 Mar 2014 18:25:07 +0100 Subject: [PATCH 20/82] Minor improvements to Settings docs --- docs/settings.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index a8e96d71..b75ce53d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -6,7 +6,8 @@ the command line:: $ pelican content -s path/to/your/settingsfile.py -(If you used the `pelican-quickstart` command, your primary settings file will be named `pelicanconf.py` by default.) +(If you used the `pelican-quickstart` command, your primary settings file will +be named `pelicanconf.py` by default.) Settings are configured in the form of a Python module (a file). There is an `example settings file @@ -193,9 +194,10 @@ want. If you specify a ``datetime`` directive, it will be substituted using the input files' date metadata attribute. If the date is not specified for a particular file, Pelican will rely on the file's ``mtime`` timestamp. + Check the `Python datetime documentation`_ for more information. -Check the Python datetime documentation at http://bit.ly/cNcJUC for more -information. +.. _Python datetime documentation: + http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior Also, you can use other file metadata attributes as well: From 6533f803b74f876c49eef121c58ae889711f239b Mon Sep 17 00:00:00 2001 From: th3aftermath Date: Fri, 31 Jan 2014 19:42:20 -0500 Subject: [PATCH 21/82] Add the setting SLUGIFY_ATTRIBUTE --- docs/settings.rst | 4 ++++ pelican/contents.py | 12 +++++++++--- pelican/settings.py | 1 + pelican/tests/test_contents.py | 11 +++++++++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index b75ce53d..3873a479 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -166,6 +166,10 @@ Setting name (default value) `PYGMENTS_RST_OPTIONS` (``[]``) A list of default Pygments settings for your reStructuredText code blocks. See :ref:`internal_pygments_options` for a list of supported options. + +`SLUGIFY_SOURCE` (``'input'``) Specifies where you want the slug to be automatically generated + from. Can be set to 'title' to use the Title: metadata tag or + 'basename' to use the articles basename to make a slug. =============================================================================== ===================================================================== .. [#] Default is the system locale. diff --git a/pelican/contents.py b/pelican/contents.py index 2ba81c1d..66602666 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -96,9 +96,15 @@ class Content(object): self.in_default_lang = (self.lang == default_lang) - # create the slug if not existing, from the title - if not hasattr(self, 'slug') and hasattr(self, 'title'): - self.slug = slugify(self.title, + # 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'): + self.slug = slugify(self.title, + settings.get('SLUG_SUBSTITUTIONS', ())) + elif settings['SLUGIFY_SOURCE'] == 'basename' and source_path != None: + basename = os.path.basename(os.path.splitext(source_path)[0]) + self.slug = slugify(basename, settings.get('SLUG_SUBSTITUTIONS', ())) self.source_path = source_path diff --git a/pelican/settings.py b/pelican/settings.py index 225a1e9d..7ab43a82 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -116,6 +116,7 @@ DEFAULT_CONFIG = { 'IGNORE_FILES': ['.#*'], 'SLUG_SUBSTITUTIONS': (), 'INTRASITE_LINK_REGEX': '[{|](?P.*?)[|}]', + 'SLUGIFY_SOURCE': 'title' } PYGMENTS_RST_OPTIONS = None diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index f831f061..4c6f8ed1 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -32,6 +32,7 @@ class TestPage(unittest.TestCase): 'title': 'foo bar', 'author': 'Blogger', }, + 'source_path': '/path/to/file/foo.ext' } def test_use_args(self): @@ -77,9 +78,15 @@ class TestPage(unittest.TestCase): self.assertEqual(page.summary, '') def test_slug(self): - # If a title is given, it should be used to generate the slug. - page = Page(**self.page_kwargs) + page_kwargs = self._copy_page_kwargs() + settings = get_settings() + page_kwargs['settings'] = settings + settings['SLUGIFY_SOURCE'] = "title" + page = Page(**page_kwargs) self.assertEqual(page.slug, 'foo-bar') + settings['SLUGIFY_SOURCE'] = "basename" + page = Page(**page_kwargs) + self.assertEqual(page.slug, 'foo') def test_defaultlang(self): # If no lang is given, default to the default one. From 7baeb6f70b8a425350778e24417f21282ed0f0f8 Mon Sep 17 00:00:00 2001 From: th3aftermath Date: Sat, 8 Mar 2014 12:56:30 -0500 Subject: [PATCH 22/82] Fix Issue #1165 allows extensions to be set by certain settings PAGINATION_PATTERNS was hard coded so that all files had a ".html" extension. This fixes that and add a test to ensure that the pagination code is not changing the filename incorrectly. --- pelican/paginator.py | 3 +- pelican/settings.py | 2 +- pelican/tests/test_paginator.py | 50 +++++++++++++++++++++++++++++++++ pelican/writers.py | 3 +- 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 pelican/tests/test_paginator.py diff --git a/pelican/paginator.py b/pelican/paginator.py index df8606ec..757c9120 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -69,7 +69,7 @@ class Paginator(object): class Page(object): def __init__(self, name, object_list, number, paginator, settings): - self.name = name + self.name, self.extension = os.path.splitext(name) self.object_list = object_list self.number = number self.paginator = paginator @@ -143,6 +143,7 @@ class Page(object): 'settings': self.settings, 'base_name': os.path.dirname(self.name), 'number_sep': '/', + 'extension': self.extension, } if self.number == 1: diff --git a/pelican/settings.py b/pelican/settings.py index 7ab43a82..796678e0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -79,7 +79,7 @@ DEFAULT_CONFIG = { 'AUTHOR_URL': 'author/{slug}.html', 'AUTHOR_SAVE_AS': os.path.join('author', '{slug}.html'), 'PAGINATION_PATTERNS': [ - (0, '{name}{number}.html', '{name}{number}.html'), + (0, '{name}{number}{extension}', '{name}{number}{extension}'), ], 'YEAR_ARCHIVE_SAVE_AS': False, 'MONTH_ARCHIVE_SAVE_AS': False, diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py new file mode 100644 index 00000000..f454d47d --- /dev/null +++ b/pelican/tests/test_paginator.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import +import six + +from pelican.tests.support import unittest, get_settings + +from pelican.paginator import Paginator +from pelican.contents import Article +from pelican.settings import DEFAULT_CONFIG +from jinja2.utils import generate_lorem_ipsum + +# generate one paragraph, enclosed with

    +TEST_CONTENT = str(generate_lorem_ipsum(n=1)) +TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) + +class TestPage(unittest.TestCase): + def setUp(self): + super(TestPage, self).setUp() + self.page_kwargs = { + 'content': TEST_CONTENT, + 'context': { + 'localsiteurl': '', + }, + 'metadata': { + 'summary': TEST_SUMMARY, + 'title': 'foo bar', + 'author': 'Blogger', + }, + 'source_path': '/path/to/file/foo.ext' + } + + def test_save_as_preservation(self): + settings = get_settings() + # fix up pagination rules + from pelican.paginator import PaginationRule + pagination_rules = [ + PaginationRule(*r) for r in settings.get( + 'PAGINATION_PATTERNS', + DEFAULT_CONFIG['PAGINATION_PATTERNS'], + ) + ] + settings['PAGINATION_PATTERNS'] = sorted( + pagination_rules, + key=lambda r: r[0], + ) + + object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)] + paginator = Paginator('foobar.foo', object_list, settings) + page = paginator.page(1) + self.assertEqual(page.save_as, 'foobar.foo') \ No newline at end of file diff --git a/pelican/writers.py b/pelican/writers.py index 580a3990..63d74126 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -179,10 +179,9 @@ class Writer(object): # pagination if paginated: - name_root = os.path.splitext(name)[0] # pagination needed, init paginators - paginators = {key: Paginator(name_root, val, self.settings) + paginators = {key: Paginator(name, val, self.settings) for key, val in paginated.items()} # generated pages, and write From 3779306de5bd41b332eedebeeb7e3c2d35241f7a Mon Sep 17 00:00:00 2001 From: Jean Lauliac Date: Mon, 24 Mar 2014 14:50:49 -0400 Subject: [PATCH 23/82] Apply typogrify on article summary as well --- pelican/readers.py | 9 ++++--- .../tests/content/article_with_metadata.rst | 2 +- pelican/tests/test_readers.py | 27 ++++++++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index a073f11f..26329af6 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -463,10 +463,13 @@ class Readers(object): find_empty_alt(content, path) # eventually filter the content with typogrify if asked so - if content and self.settings['TYPOGRIFY']: + if self.settings['TYPOGRIFY']: from typogrify.filters import typogrify - content = typogrify(content) - metadata['title'] = typogrify(metadata['title']) + if content: + content = typogrify(content) + metadata['title'] = typogrify(metadata['title']) + if 'summary' in metadata: + metadata['summary'] = typogrify(metadata['summary']) if context_signal: logger.debug('signal {}.send({}, )'.format( diff --git a/pelican/tests/content/article_with_metadata.rst b/pelican/tests/content/article_with_metadata.rst index c5768cfb..9b65a4b0 100644 --- a/pelican/tests/content/article_with_metadata.rst +++ b/pelican/tests/content/article_with_metadata.rst @@ -9,5 +9,5 @@ This is a super article ! :author: Alexis Métaireau :summary: Multi-line metadata should be supported - as well as **inline markup**. + as well as **inline markup** and stuff to "typogrify"... :custom_field: http://notmyidea.org diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 6274bdc5..d4201a5e 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -40,7 +40,8 @@ class RstReaderTest(ReaderTest): 'title': 'This is a super article !', 'summary': '

    Multi-line metadata should be' ' supported\nas well as inline' - ' markup.

    \n', + ' markup and stuff to "typogrify' + '"...

    \n', 'date': datetime.datetime(2010, 12, 2, 10, 14), 'modified': datetime.datetime(2010, 12, 2, 10, 20), 'tags': ['foo', 'bar', 'foobar'], @@ -125,6 +126,30 @@ class RstReaderTest(ReaderTest): except ImportError: return unittest.skip('need the typogrify distribution') + def test_typogrify_summary(self): + # if nothing is specified in the settings, the summary should be + # unmodified + page = self.read_file(path='article_with_metadata.rst') + expected = ('

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

    \n') + + self.assertEqual(page.metadata['summary'], expected) + + try: + # otherwise, typogrify should be applied + page = self.read_file(path='article_with_metadata.rst', + TYPOGRIFY=True) + expected = ('

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

    \n') + + self.assertEqual(page.metadata['summary'], expected) + except ImportError: + return unittest.skip('need the typogrify distribution') + class MdReaderTest(ReaderTest): From 715b9eae6809c7b87d1f9d61a4ee96eeb8710076 Mon Sep 17 00:00:00 2001 From: th3aftermath Date: Sun, 30 Mar 2014 14:35:22 -0400 Subject: [PATCH 24/82] Fix indentation error in the settings doc This was accidentally caused by me in #1248 As a result the Basic Settings table was not being updated. --- docs/settings.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 3873a479..b579ae95 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -167,9 +167,9 @@ Setting name (default value) code blocks. See :ref:`internal_pygments_options` for a list of supported options. -`SLUGIFY_SOURCE` (``'input'``) Specifies where you want the slug to be automatically generated - from. Can be set to 'title' to use the Title: metadata tag or - 'basename' to use the articles basename to make a slug. +`SLUGIFY_SOURCE` (``'input'``) Specifies where you want the slug to be automatically generated + from. Can be set to 'title' to use the 'Title:' metadata tag or + 'basename' to use the articles basename when creating the slug. =============================================================================== ===================================================================== .. [#] Default is the system locale. From da5849d1a00c537ee75dba9ce65b137b8f17c55c Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 30 Mar 2014 11:59:32 -0700 Subject: [PATCH 25/82] Fix docs last_stable version and copyright date --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5ac81b9e..6db0f3d1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,11 +12,11 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.extlinks' source_suffix = '.rst' master_doc = 'index' project = 'Pelican' -copyright = '2010, Alexis Metaireau and contributors' +copyright = '2014, Alexis Metaireau and contributors' exclude_patterns = ['_build'] release = __version__ version = '.'.join(release.split('.')[:1]) -last_stable = '3.2.2' +last_stable = '3.3.0' rst_prolog = ''' .. |last_stable| replace:: :pelican-doc:`{0}` '''.format(last_stable) From cb4b2e7dfaf89abf8891fd9120be22c4ccfd2c8b Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Mon, 31 Mar 2014 19:38:49 +0200 Subject: [PATCH 26/82] change the inhibition value of *_SAVE_AS to '' Previously, the documentation claimed the value of None for this purpose even though False was used for certain defaults. The values False and None cause warnings to be emitted from URLWrapper._from_settings though, so the new way of inhibiting page generation is to set a *_SAVE_AS value to the empty string. --- docs/settings.rst | 8 ++++---- pelican/settings.py | 6 +++--- pelican/writers.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index b579ae95..0c16db3c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -269,9 +269,9 @@ Setting name (default value) What does it do? `TAG_SAVE_AS` (``'tag/{slug}.html'``) The location to save the tag page. `AUTHOR_URL` (``'author/{slug}.html'``) The URL to use for an author. `AUTHOR_SAVE_AS` (``'author/{slug}.html'``) The location to save an author. -`YEAR_ARCHIVE_SAVE_AS` (False) The location to save per-year archives of your posts. -`MONTH_ARCHIVE_SAVE_AS` (False) The location to save per-month archives of your posts. -`DAY_ARCHIVE_SAVE_AS` (False) The location to save per-day archives of your posts. +`YEAR_ARCHIVE_SAVE_AS` (``''``) The location to save per-year archives of your posts. +`MONTH_ARCHIVE_SAVE_AS` (``''``) The location to save per-month archives of your posts. +`DAY_ARCHIVE_SAVE_AS` (``''``) The location to save per-day archives of your posts. `SLUG_SUBSTITUTIONS` (``()``) Substitutions to make prior to stripping out non-alphanumerics when generating slugs. Specified as a list of 2-tuples of ``(from, to)`` which are @@ -282,7 +282,7 @@ Setting name (default value) What does it do? If you do not want one or more of the default pages to be created (e.g., you are the only author on your site and thus do not need an Authors page), - set the corresponding ``*_SAVE_AS`` setting to ``None`` to prevent the + set the corresponding ``*_SAVE_AS`` setting to ``''`` to prevent the relevant page from being generated. `DIRECT_TEMPLATES` diff --git a/pelican/settings.py b/pelican/settings.py index 796678e0..ffd0bc8f 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -81,9 +81,9 @@ DEFAULT_CONFIG = { 'PAGINATION_PATTERNS': [ (0, '{name}{number}{extension}', '{name}{number}{extension}'), ], - 'YEAR_ARCHIVE_SAVE_AS': False, - 'MONTH_ARCHIVE_SAVE_AS': False, - 'DAY_ARCHIVE_SAVE_AS': False, + 'YEAR_ARCHIVE_SAVE_AS': '', + 'MONTH_ARCHIVE_SAVE_AS': '', + 'DAY_ARCHIVE_SAVE_AS': '', 'RELATIVE_URLS': False, 'DEFAULT_LANG': 'en', 'TAG_CLOUD_STEPS': 4, diff --git a/pelican/writers.py b/pelican/writers.py index 63d74126..19e36e39 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -140,7 +140,7 @@ class Writer(object): :param **kwargs: additional variables to pass to the templates """ - if name is False: + if name is False or name == "": return elif not name: # other stuff, just return for now From 1fb04bfc21bcfd367053e6a7ba64b4d63abf3ed3 Mon Sep 17 00:00:00 2001 From: Rogdham Date: Tue, 1 Apr 2014 20:44:09 +0200 Subject: [PATCH 27/82] Limit and filter logs Drop duplicates logs. Allow for logs to be grouped, enforcing a maximum number of logs per group. Add the LOG_FILTER setting to ask from the configuration file to ignore some logs (of level up to warning). --- docs/contribute.rst | 38 ++++++++++++++++++++ docs/settings.rst | 20 +++++++++++ pelican/__init__.py | 5 ++- pelican/contents.py | 6 ++-- pelican/log.py | 65 ++++++++++++++++++++++++++++++++--- pelican/readers.py | 34 ++++++------------ pelican/settings.py | 9 ++++- pelican/tests/test_pelican.py | 2 +- 8 files changed, 146 insertions(+), 33 deletions(-) diff --git a/docs/contribute.rst b/docs/contribute.rst index 304d1de8..28df1fcd 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -143,3 +143,41 @@ and Python 3 at the same time: changed it where I felt necessary. - Changed xrange() back to range(), so it is valid in both Python versions. + + +Logging tips +============ + +Try to use logging with appropriate levels. + +For logging messages that are not repeated, use the usual Python way: + + # at top of file + import logging + logger = logging.getLogger(__name__) + + # when needed + logger.warning('A warning that could occur only once") + +However, if you want to log messages that may occur several times, instead of +a string, gives a tuple to the logging method, with two arguments: + + 1. The message to log for this very execution + 2. A generic message that will appear if the previous one would occur to many + times. + +For example, if you want to log missing resources, use the following code: + + for ressource in ressources: + if ressource.is_missing: + logger.warning(( + 'The resource {r} is missing'.format(r=ressource.name), + 'Other resources were missing')) + +The logs will be displayed as follows: + + WARNING: The resource prettiest_cat.jpg is missing + WARNING: The resource best_cat_ever.jpg is missing + WARNING: The resource cutest_cat.jpg is missing + WARNING: The resource lolcat.jpg is missing + WARNING: Other resources were missing diff --git a/docs/settings.rst b/docs/settings.rst index 0c16db3c..f7cfa69d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -88,6 +88,9 @@ Setting name (default value) here or a single string representing one locale. When providing a list, all the locales will be tried until one works. +`LOG_FILTER` (``[]``) A list of tuples containing the logging level (up to warning) + and the message to be ignored. + For example: ``[(logging.WARN, 'TAG_SAVE_AS is set to False')]`` `READERS` (``{}``) A dictionary of file extensions / Reader classes for Pelican to process or ignore. For example, to avoid processing .html files, set: ``READERS = {'html': None}``. To add a custom reader for the @@ -694,6 +697,23 @@ adding the following to your configuration:: CSS_FILE = "wide.css" + +Logging +======= + +Sometimes, useless lines of log appears while the generation occurs. Finding +**the** meaningful error message in the middle of tons of annoying log outputs +can be quite tricky. To be able to filter out all useless log messages, Pelican +comes with the ``LOG_FILTER`` setting. + +``LOG_FILTER`` should be a list of tuples ``(level, msg)``, each of them being +composed of the logging level (up to warning) and the message to be ignored. +Simply populate the list with the logs you want to hide and they will be +filtered out. + +For example: ``[(logging.WARN, 'TAG_SAVE_AS is set to False')]`` + + Example settings ================ diff --git a/pelican/__init__.py b/pelican/__init__.py index 08dd484e..494e7e43 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -11,12 +11,15 @@ import argparse import locale import collections +# pelican.log has to be the first pelican module to be loaded +# because logging.setLoggerClass has to be called before logging.getLogger +from pelican.log import init + from pelican import signals from pelican.generators import (ArticlesGenerator, PagesGenerator, StaticGenerator, SourceFileGenerator, TemplatePagesGenerator) -from pelican.log import init from pelican.readers import Readers from pelican.settings import read_settings from pelican.utils import clean_output_dir, folder_watcher, file_watcher diff --git a/pelican/contents.py b/pelican/contents.py index 66602666..3096a064 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -239,8 +239,10 @@ class Content(object): self._context['filenames'][path].url)) origin = origin.replace('\\', '/') # for Windows paths. else: - logger.warning("Unable to find {fn}, skipping url" - " replacement".format(fn=path)) + logger.warning(("Unable to find {fn}, skipping url" + " replacement".format(fn=value), + "Other ressources were not found" + " and their urls not replaced")) elif what == 'category': origin = Category(path, self.settings).url elif what == 'tag': diff --git a/pelican/log.py b/pelican/log.py index bde8037e..d3aae012 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -9,7 +9,7 @@ import os import sys import logging -from logging import Formatter, getLogger, StreamHandler, DEBUG +from collections import defaultdict RESET_TERM = '\033[0;m' @@ -30,7 +30,7 @@ def ansi(color, text): return '\033[1;{0}m{1}{2}'.format(code, text, RESET_TERM) -class ANSIFormatter(Formatter): +class ANSIFormatter(logging.Formatter): """Convert a `logging.LogRecord' object into colored text, using ANSI escape sequences. @@ -51,7 +51,7 @@ class ANSIFormatter(Formatter): return ansi('white', record.levelname) + ': ' + msg -class TextFormatter(Formatter): +class TextFormatter(logging.Formatter): """ Convert a `logging.LogRecord' object into text. """ @@ -63,7 +63,62 @@ class TextFormatter(Formatter): return record.levelname + ': ' + record.getMessage() -def init(level=None, logger=getLogger(), handler=StreamHandler()): +class LimitFilter(logging.Filter): + """ + Remove duplicates records, and limit the number of records in the same + group. + + Groups are specified by the message to use when the number of records in + the same group hit the limit. + E.g.: log.warning(('43 is not the answer', 'More erroneous answers')) + """ + + ignore = set() + threshold = 5 + group_count = defaultdict(int) + + def filter(self, record): + # don't limit levels over warnings + if record.levelno > logging.WARN: + return record + # extract group + group = None + if len(record.msg) == 2: + record.msg, group = record.msg + # ignore record if it was already raised + # use .getMessage() and not .msg for string formatting + ignore_key = (record.levelno, record.getMessage()) + to_ignore = ignore_key in LimitFilter.ignore + LimitFilter.ignore.add(ignore_key) + if to_ignore: + return False + # check if we went over threshold + if group: + key = (record.levelno, group) + LimitFilter.group_count[key] += 1 + if LimitFilter.group_count[key] == LimitFilter.threshold: + record.msg = group + if LimitFilter.group_count[key] > LimitFilter.threshold: + return False + return record + + +class LimitLogger(logging.Logger): + """ + A logger which add LimitFilter automatically + """ + + limit_filter = LimitFilter() + + def __init__(self, *args, **kwargs): + super(LimitLogger, self).__init__(*args, **kwargs) + self.addFilter(LimitLogger.limit_filter) + +logging.setLoggerClass(LimitLogger) + + +def init(level=None, handler=logging.StreamHandler()): + logger = logging.getLogger() if (os.isatty(sys.stdout.fileno()) @@ -79,7 +134,7 @@ def init(level=None, logger=getLogger(), handler=StreamHandler()): if __name__ == '__main__': - init(level=DEBUG) + init(level=logging.DEBUG) root_logger = logging.getLogger() root_logger.debug('debug') diff --git a/pelican/readers.py b/pelican/readers.py index 26329af6..35c38220 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -318,7 +318,11 @@ class HTMLReader(BaseReader): if not contents: contents = self._attr_value(attrs, 'contents', '') if contents: - logger.warning("Meta tag attribute 'contents' used in file %s, should be changed to 'content'", self._filename) + logger.warning(( + "Meta tag attribute 'contents' used in file {}, should" + " be changed to 'content'".format(self._filename), + "Other files have meta tag attribute 'contents' that" + " should be changed to 'content'")) if name == 'keywords': name = 'tags' @@ -385,10 +389,6 @@ class Readers(object): """ - # used to warn about missing dependencies only once, at the first - # instanciation of a Readers object. - warn_missing_deps = True - def __init__(self, settings=None): self.settings = settings or {} self.readers = {} @@ -396,16 +396,13 @@ class Readers(object): for cls in [BaseReader] + BaseReader.__subclasses__(): if not cls.enabled: - if self.__class__.warn_missing_deps: - logger.debug('Missing dependencies for {}' - .format(', '.join(cls.file_extensions))) + logger.debug('Missing dependencies for {}' + .format(', '.join(cls.file_extensions))) continue for ext in cls.file_extensions: self.reader_classes[ext] = cls - self.__class__.warn_missing_deps = False - if self.settings['READERS']: self.reader_classes.update(self.settings['READERS']) @@ -505,19 +502,10 @@ def find_empty_alt(content, path): src=(['"])(.*)\5 ) """, re.X) - matches = re.findall(imgs, content) - # find a correct threshold - nb_warnings = 10 - if len(matches) == nb_warnings + 1: - nb_warnings += 1 # avoid bad looking case - # print one warning per image with empty alt until threshold - for match in matches[:nb_warnings]: - logger.warning('Empty alt attribute for image {} in {}'.format( - os.path.basename(match[1] + match[5]), path)) - # print one warning for the other images with empty alt - if len(matches) > nb_warnings: - logger.warning('{} other images with empty alt attributes' - .format(len(matches) - nb_warnings)) + for match in re.findall(imgs, content): + logger.warning(('Empty alt attribute for image {} in {}'.format( + os.path.basename(match[1] + match[5]), path), + 'Other images have empty alt attributes')) def default_metadata(settings=None, process=None): diff --git a/pelican/settings.py b/pelican/settings.py index ffd0bc8f..f70f74a8 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -19,6 +19,8 @@ except ImportError: from os.path import isabs +from pelican.log import LimitFilter + logger = logging.getLogger(__name__) @@ -98,6 +100,7 @@ DEFAULT_CONFIG = { 'MD_EXTENSIONS': ['codehilite(css_class=highlight)', 'extra'], 'JINJA_EXTENSIONS': [], 'JINJA_FILTERS': {}, + 'LOG_FILTER': [], 'LOCALE': [''], # defaults to user locale 'DEFAULT_PAGINATION': False, 'DEFAULT_ORPHANS': 0, @@ -170,12 +173,16 @@ def get_settings_from_file(path, default_settings=DEFAULT_CONFIG): def configure_settings(settings): """Provide optimizations, error checking and warnings for the given settings. - + Set up the logs to be ignored as well. """ if not 'PATH' in settings or not os.path.isdir(settings['PATH']): raise Exception('You need to specify a path containing the content' ' (see pelican --help for more information)') + # set up logs to be ignored + LimitFilter.ignore.update(set(settings.get('LOG_FILTER', + DEFAULT_CONFIG['LOG_FILTER']))) + # lookup the theme in "pelican/themes" if the given one doesn't exist if not os.path.isdir(settings['THEME']): theme_path = os.path.join( diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 21a77e6b..2d4bbdfc 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -83,7 +83,7 @@ class TestPelican(LoggedTestCase): mute(True)(pelican.run)() self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'basic')) self.assertLogCountEqual( - count=4, + count=3, msg="Unable to find.*skipping url replacement", level=logging.WARNING) From a14b432084b92d2d0d6b87f609ed45b038f485a4 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 2 Apr 2014 12:38:49 -0700 Subject: [PATCH 28/82] Fix deprecated logger warning for Python 3 logger.warn() has been deprecated in Python 3 in favor of logger.warning() --- pelican/tools/pelican_import.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index d6b57c47..30d6346c 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -135,7 +135,7 @@ def wp2fields(xml, wp_custpost=False): title = HTMLParser().unescape(item.title.contents[0]) except IndexError: title = 'No title [%s]' % item.find('post_name').string - logger.warn('Post "%s" is lacking a proper title' % title) + logger.warning('Post "%s" is lacking a proper title' % title) filename = item.find('post_name').string post_id = item.find('post_id').string @@ -601,11 +601,11 @@ def download_attachments(output_path, urls): except URLError as e: error = ("No file could be downloaded from {}; Error {}" .format(url, e)) - logger.warn(error) + logger.warning(error) except IOError as e: #Python 2.7 throws an IOError rather Than URLError error = ("No file could be downloaded from {}; Error {}" .format(url, e)) - logger.warn(error) + logger.warning(error) return locations From 3b9564b0698fd5b67b3e9a61a4e200b75c6c07d6 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 7 Apr 2014 14:29:21 -0700 Subject: [PATCH 29/82] Minor correction to settings documentation --- docs/settings.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index f7cfa69d..c35bf08d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -36,7 +36,7 @@ Setting name (default value) =============================================================================== ===================================================================== `AUTHOR` Default author (put your name) `DATE_FORMATS` (``{}``) If you manage multiple languages, you can set the date formatting - here. See the "Date format and locales" section below for details. + here. See the "Date format and locale" section below for details. `USE_FOLDER_AS_CATEGORY` (``True``) When you don't specify a category in your post metadata, set this setting to ``True``, and organize your articles in subfolders, the subfolder will become the category of your post. If set to ``False``, @@ -170,9 +170,9 @@ Setting name (default value) code blocks. See :ref:`internal_pygments_options` for a list of supported options. -`SLUGIFY_SOURCE` (``'input'``) Specifies where you want the slug to be automatically generated - from. Can be set to 'title' to use the 'Title:' metadata tag or - 'basename' to use the articles basename when creating the slug. +`SLUGIFY_SOURCE` (``'input'``) Specifies where you want the slug to be automatically generated + from. Can be set to 'title' to use the 'Title:' metadata tag or + 'basename' to use the articles basename when creating the slug. =============================================================================== ===================================================================== .. [#] Default is the system locale. From 9ca86adffc026cdf9d10a8581a8e909cf2489a41 Mon Sep 17 00:00:00 2001 From: Antoine Brenner Date: Mon, 14 Apr 2014 20:43:19 +0200 Subject: [PATCH 30/82] Fix unittest issue related to python2/python3 differences The test_datetime test passed on python3 but not python2 because datetime.strftime is a byte string in python2, and a unicode string in python3 This patch allows the test to pass in both python2 and python3 (3.3+ only) --- pelican/tests/test_contents.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 4c6f8ed1..27d2a897 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -129,9 +129,15 @@ class TestPage(unittest.TestCase): page_kwargs['metadata']['date'] = dt page = Page(**page_kwargs) - self.assertEqual(page.locale_date, - dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT'])) + # page.locale_date is a unicode string in both python2 and python3 + dt_date = dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT']) + # dt_date is a byte string in python2, and a unicode string in python3 + # Let's make sure it is a unicode string (relies on python 3.3 supporting the u prefix) + if type(dt_date) != type(u''): + # python2: + dt_date = unicode(dt_date, 'utf8') + self.assertEqual(page.locale_date, dt_date ) page_kwargs['settings'] = get_settings() # I doubt this can work on all platforms ... From dafa2c36b1461d00f63481716ea6e61df4cd26c2 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 14 Apr 2014 16:18:07 -0400 Subject: [PATCH 31/82] Minor text changes to log message limitation --- docs/contribute.rst | 16 ++++++++-------- docs/settings.rst | 14 +++++++------- pelican/contents.py | 2 +- pelican/log.py | 4 ++-- pelican/settings.py | 6 +++--- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/contribute.rst b/docs/contribute.rst index 28df1fcd..57349156 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -157,24 +157,24 @@ For logging messages that are not repeated, use the usual Python way: logger = logging.getLogger(__name__) # when needed - logger.warning('A warning that could occur only once") + logger.warning("A warning that would usually occur only once") However, if you want to log messages that may occur several times, instead of -a string, gives a tuple to the logging method, with two arguments: +a string, give a tuple to the logging method, with two arguments: - 1. The message to log for this very execution - 2. A generic message that will appear if the previous one would occur to many + 1. The message to log for the initial execution + 2. A generic message that will appear if the previous one would occur too many times. For example, if you want to log missing resources, use the following code: - for ressource in ressources: - if ressource.is_missing: + for resource in resources: + if resource.is_missing: logger.warning(( - 'The resource {r} is missing'.format(r=ressource.name), + 'The resource {r} is missing'.format(r=resource.name), 'Other resources were missing')) -The logs will be displayed as follows: +The log messages will be displayed as follows: WARNING: The resource prettiest_cat.jpg is missing WARNING: The resource best_cat_ever.jpg is missing diff --git a/docs/settings.rst b/docs/settings.rst index c35bf08d..36cc3f9a 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -88,7 +88,7 @@ Setting name (default value) here or a single string representing one locale. When providing a list, all the locales will be tried until one works. -`LOG_FILTER` (``[]``) A list of tuples containing the logging level (up to warning) +`LOG_FILTER` (``[]``) A list of tuples containing the logging level (up to ``warning``) and the message to be ignored. For example: ``[(logging.WARN, 'TAG_SAVE_AS is set to False')]`` `READERS` (``{}``) A dictionary of file extensions / Reader classes for Pelican to @@ -701,15 +701,15 @@ adding the following to your configuration:: Logging ======= -Sometimes, useless lines of log appears while the generation occurs. Finding -**the** meaningful error message in the middle of tons of annoying log outputs -can be quite tricky. To be able to filter out all useless log messages, Pelican +Sometimes, a long list of warnings may appear during site generation. Finding +the **meaningful** error message in the middle of tons of annoying log output +can be quite tricky. In order to filter out redundant log messages, Pelican comes with the ``LOG_FILTER`` setting. ``LOG_FILTER`` should be a list of tuples ``(level, msg)``, each of them being -composed of the logging level (up to warning) and the message to be ignored. -Simply populate the list with the logs you want to hide and they will be -filtered out. +composed of the logging level (up to ``warning``) and the message to be ignored. +Simply populate the list with the log messages you want to hide, and they will +be filtered out. For example: ``[(logging.WARN, 'TAG_SAVE_AS is set to False')]`` diff --git a/pelican/contents.py b/pelican/contents.py index 3096a064..615a7fd8 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -241,7 +241,7 @@ class Content(object): else: logger.warning(("Unable to find {fn}, skipping url" " replacement".format(fn=value), - "Other ressources were not found" + "Other resources were not found" " and their urls not replaced")) elif what == 'category': origin = Category(path, self.settings).url diff --git a/pelican/log.py b/pelican/log.py index d3aae012..fdf41cb0 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -78,7 +78,7 @@ class LimitFilter(logging.Filter): group_count = defaultdict(int) def filter(self, record): - # don't limit levels over warnings + # don't limit log messages for anything above "warning" if record.levelno > logging.WARN: return record # extract group @@ -105,7 +105,7 @@ class LimitFilter(logging.Filter): class LimitLogger(logging.Logger): """ - A logger which add LimitFilter automatically + A logger which adds LimitFilter automatically """ limit_filter = LimitFilter() diff --git a/pelican/settings.py b/pelican/settings.py index f70f74a8..7277c121 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -171,15 +171,15 @@ def get_settings_from_file(path, default_settings=DEFAULT_CONFIG): def configure_settings(settings): - """Provide optimizations, error checking and warnings for the given + """Provide optimizations, error checking, and warnings for the given settings. - Set up the logs to be ignored as well. + Also, specify the log messages to be ignored. """ if not 'PATH' in settings or not os.path.isdir(settings['PATH']): raise Exception('You need to specify a path containing the content' ' (see pelican --help for more information)') - # set up logs to be ignored + # specify the log messages to be ignored LimitFilter.ignore.update(set(settings.get('LOG_FILTER', DEFAULT_CONFIG['LOG_FILTER']))) From 8b022b3508e3b56e94f22dd083f1e0391124dc69 Mon Sep 17 00:00:00 2001 From: Antoine Brenner Date: Mon, 14 Apr 2014 22:28:25 +0200 Subject: [PATCH 32/82] Fix error in download_attachments() triggered by python2 unit test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The download_attachments error is triggered in the unit tests by a japanese error message (接続を拒否されました) (connexion denied), that python is not able to serialize the into a byte string. This error weirdly does not appear every time the unit tests are run. It might be related to the order in which the tests are run. This error was found and fixed during the PyconUS 2014 pelican sprint. It was discovered on a Linux Fedora20 computer running Python2.7 in virtualenv --- pelican/tools/pelican_import.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 30d6346c..27e47754 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -603,8 +603,18 @@ def download_attachments(output_path, urls): .format(url, e)) logger.warning(error) except IOError as e: #Python 2.7 throws an IOError rather Than URLError - error = ("No file could be downloaded from {}; Error {}" - .format(url, e)) + # For japanese, the error might look kind of like this: + # e = IOError( 'socket error', socket.error(111, u'\u63a5\u7d9a\u3092\u62d2\u5426\u3055\u308c\u307e\u3057\u305f') ) + # and not be suitable to use in "{}".format(e) , raising UnicodeDecodeError + # (This is at least the case on my Fedora running Python 2.7.5 + # (default, Feb 19 2014, 13:47:28) [GCC 4.8.2 20131212 (Red Hat 4.8.2-7)] on linux2 + try: + error = ("No file could be downloaded from {}; Error {}" + .format(url, e)) + except UnicodeDecodeError: + # For lack of a better log message because we could not decode e, let's use repr(e) + error = ("No file could be downloaded from {}; Error {}" + .format(url, repr(e))) logger.warning(error) return locations From 6c06ee9e5b56bc6bb67d905b6b63071485fbf661 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sat, 15 Feb 2014 21:20:51 +0100 Subject: [PATCH 33/82] Cache content to speed up reading. Fixes #224. Cache read content so that it doesn't have to be read next time if its source has not been modified. --- docs/faq.rst | 19 ++++++ docs/settings.rst | 64 ++++++++++++++++- pelican/__init__.py | 19 ++++++ pelican/contents.py | 7 ++ pelican/generators.py | 72 ++++++++++++-------- pelican/settings.py | 7 +- pelican/tests/test_generators.py | 59 ++++++++++++++++ pelican/tests/test_pelican.py | 6 ++ pelican/utils.py | 113 +++++++++++++++++++++++++++++++ 9 files changed, 334 insertions(+), 32 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index 80e14d21..bb9377e6 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -205,3 +205,22 @@ You can also disable generation of tag-related pages via:: TAGS_SAVE_AS = '' TAG_SAVE_AS = '' + +Why does Pelican always write all HTML files even with content caching enabled? +=============================================================================== + +In order to reliably determine whether the HTML output is different +before writing it, a large part of the generation environment +including the template contexts, imported plugins, etc. would have to +be saved and compared, at least in the form of a hash (which would +require special handling of unhashable types), because of all the +possible combinations of plugins, pagination, etc. which may change in +many different ways. This would require a lot more processing time +and memory and storage space. Simply writing the files each time is a +lot faster and a lot more reliable. + +However, this means that the modification time of the files changes +every time, so a ``rsync`` based upload will transfer them even if +their content hasn't changed. A simple solution is to make ``rsync`` +use the ``--checksum`` option, which will make it compare the file +checksums in a much faster way than Pelican would. diff --git a/docs/settings.rst b/docs/settings.rst index 36cc3f9a..d8690230 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -173,6 +173,12 @@ Setting name (default value) `SLUGIFY_SOURCE` (``'input'``) Specifies where you want the slug to be automatically generated from. Can be set to 'title' to use the 'Title:' metadata tag or 'basename' to use the articles basename when creating the slug. +`CACHE_CONTENT` (``True``) If ``True``, save read content in a cache file. + See :ref:`reading_only_modified_content` for details about caching. +`CACHE_DIRECTORY` (``cache``) Directory in which to store cache files. +`CHECK_MODIFIED_METHOD` (``mtime``) Controls how files are checked for modifications. +`LOAD_CONTENT_CACHE` (``True``) If ``True``, load unmodified content from cache. +`GZIP_CACHE` (``True``) If ``True``, use gzip to (de)compress the cache files. =============================================================================== ===================================================================== .. [#] Default is the system locale. @@ -602,7 +608,7 @@ Setting name (default value) What does it do? .. [3] %s is the language Ordering content -================= +================ ================================================ ===================================================== Setting name (default value) What does it do? @@ -697,7 +703,6 @@ adding the following to your configuration:: CSS_FILE = "wide.css" - Logging ======= @@ -713,6 +718,61 @@ be filtered out. For example: ``[(logging.WARN, 'TAG_SAVE_AS is set to False')]`` +.. _reading_only_modified_content: + +Reading only modified content +============================= + +To speed up the build process, pelican can optionally read only articles +and pages with modified content. + +When Pelican is about to read some content source file: + +1. The hash or modification time information for the file from a + previous build are loaded from a cache file if `LOAD_CONTENT_CACHE` + is ``True``. These files are stored in the `CACHE_DIRECTORY` + directory. If the file has no record in the cache file, it is read + as usual. +2. The file is checked according to `CHECK_MODIFIED_METHOD`: + + - If set to ``'mtime'``, the modification time of the file is + checked. + - If set to a name of a function provided by the ``hashlib`` + module, e.g. ``'md5'``, the file hash is checked. + - If set to anything else or the necessary information about the + file cannot be found in the cache file, the content is read as + usual. + +3. If the file is considered unchanged, the content object saved in a + previous build corresponding to the file is loaded from the cache + and the file is not read. +4. If the file is considered changed, the file is read and the new + modification information and the content object are saved to the + cache if `CACHE_CONTENT` is ``True``. + +Modification time based checking is faster than comparing file hashes, +but is not as reliable, because mtime information can be lost when +e.g. copying the content sources using the ``cp`` or ``rsync`` +commands without the mtime preservation mode (invoked e.g. by +``--archive``). + +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 +using the pelican command-line option ``--full-rebuild``. +The cache files also have to be rebuilt when changing the +`GZIP_CACHE` setting for cache file reading to work. + +The ``--full-rebuild`` command-line option is also useful when the +whole site needs to be regenerated due to e.g. modifications to the +settings file or theme files. When pelican runs in autorealod mode, +modification of the settings file or theme will trigger a full rebuild +automatically. + +Note that even when using cached content, all output is always +written, so the modification times of the ``*.html`` files always +change. Therefore, ``rsync`` based upload may benefit from the +``--checksum`` option. Example settings ================ diff --git a/pelican/__init__.py b/pelican/__init__.py index 494e7e43..b6bfe326 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -260,6 +260,10 @@ def parse_arguments(): action='store_true', help='Relaunch pelican each time a modification occurs' ' on the content files.') + + parser.add_argument('-f', '--full-rebuild', action='store_true', + dest='full_rebuild', help='Rebuild everything by not loading from cache') + return parser.parse_args() @@ -275,6 +279,8 @@ def get_config(args): config['THEME'] = abstheme if os.path.exists(abstheme) else args.theme if args.delete_outputdir is not None: config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir + if args.full_rebuild: + config['LOAD_CONTENT_CACHE'] = False # argparse returns bytes in Py2. There is no definite answer as to which # encoding argparse (or sys.argv) uses. @@ -327,6 +333,7 @@ def main(): print(' --- AutoReload Mode: Monitoring `content`, `theme` and' ' `settings` for changes. ---') + first_run = True # load cache on first run while True: try: # Check source dir for changed files ending with the given @@ -335,9 +342,14 @@ def main(): # have changed, no matter what extension the filenames # have. modified = {k: next(v) for k, v in watchers.items()} + original_load_cache = settings['LOAD_CONTENT_CACHE'] if modified['settings']: pelican, settings = get_instance(args) + if not first_run: + original_load_cache = settings['LOAD_CONTENT_CACHE'] + # invalidate cache + pelican.settings['LOAD_CONTENT_CACHE'] = False if any(modified.values()): print('\n-> Modified: {}. re-generating...'.format( @@ -349,8 +361,15 @@ def main(): if modified['theme'] is None: logger.warning('Empty theme folder. Using `basic` ' 'theme.') + elif modified['theme']: + # theme modified, needs full rebuild -> no cache + if not first_run: # but not on first run + pelican.settings['LOAD_CONTENT_CACHE'] = False pelican.run() + first_run = False + # restore original caching policy + pelican.settings['LOAD_CONTENT_CACHE'] = original_load_cache except KeyboardInterrupt: logger.warning("Keyboard interrupt, quitting.") diff --git a/pelican/contents.py b/pelican/contents.py index 615a7fd8..c02047b8 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -325,6 +325,13 @@ class Content(object): os.path.abspath(self.settings['PATH'])) ) + def __eq__(self, other): + """Compare with metadata and content of other Content object""" + return other and self.metadata == other.metadata and self.content == other.content + + # keep basic hashing functionality for caching to work + __hash__ = object.__hash__ + class Page(Content): mandatory_properties = ('title',) diff --git a/pelican/generators.py b/pelican/generators.py index bfdac1a5..7c2dbbf2 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -20,14 +20,15 @@ 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 +from pelican.utils import (copy, process_translations, mkdir_p, DateFormatter, + FileStampDataCacher) from pelican import signals logger = logging.getLogger(__name__) -class Generator(object): +class Generator(FileStampDataCacher): """Baseclass generator""" def __init__(self, context, settings, path, theme, output_path, **kwargs): @@ -73,6 +74,10 @@ class Generator(object): custom_filters = self.settings['JINJA_FILTERS'] self.env.filters.update(custom_filters) + # set up caching + super(Generator, self).__init__(settings, 'CACHE_CONTENT', + 'LOAD_CONTENT_CACHE') + signals.generator_init.send(self) def get_template(self, name): @@ -408,20 +413,24 @@ class ArticlesGenerator(Generator): for f in self.get_files( self.settings['ARTICLE_DIR'], exclude=self.settings['ARTICLE_EXCLUDES']): - try: - article = self.readers.read_file( - base_path=self.path, path=f, content_class=Article, - context=self.context, - preread_signal=signals.article_generator_preread, - preread_sender=self, - context_signal=signals.article_generator_context, - context_sender=self) - except Exception as e: - logger.warning('Could not process {}\n{}'.format(f, e)) - continue + article = self.get_cached_data(f, None) + if article is None: + try: + article = self.readers.read_file( + base_path=self.path, path=f, content_class=Article, + context=self.context, + preread_signal=signals.article_generator_preread, + preread_sender=self, + context_signal=signals.article_generator_context, + context_sender=self) + except Exception as e: + logger.warning('Could not process {}\n{}'.format(f, e)) + continue - if not is_valid_content(article, f): - continue + if not is_valid_content(article, f): + continue + + self.cache_data(f, article) self.add_source_path(article) @@ -502,7 +511,7 @@ class ArticlesGenerator(Generator): self._update_context(('articles', 'dates', 'tags', 'categories', 'tag_cloud', 'authors', 'related_posts')) - + self.save_cache() signals.article_generator_finalized.send(self) def generate_output(self, writer): @@ -527,20 +536,24 @@ class PagesGenerator(Generator): for f in self.get_files( self.settings['PAGE_DIR'], exclude=self.settings['PAGE_EXCLUDES']): - try: - page = self.readers.read_file( - base_path=self.path, path=f, content_class=Page, - context=self.context, - preread_signal=signals.page_generator_preread, - preread_sender=self, - context_signal=signals.page_generator_context, - context_sender=self) - except Exception as e: - logger.warning('Could not process {}\n{}'.format(f, e)) - continue + page = self.get_cached_data(f, None) + if page is None: + try: + page = self.readers.read_file( + base_path=self.path, path=f, content_class=Page, + context=self.context, + preread_signal=signals.page_generator_preread, + preread_sender=self, + context_signal=signals.page_generator_context, + context_sender=self) + except Exception as e: + logger.warning('Could not process {}\n{}'.format(f, e)) + continue - if not is_valid_content(page, f): - continue + if not is_valid_content(page, f): + continue + + self.cache_data(f, page) self.add_source_path(page) @@ -560,6 +573,7 @@ class PagesGenerator(Generator): self._update_context(('pages', )) self.context['PAGES'] = self.pages + self.save_cache() signals.page_generator_finalized.send(self) def generate_output(self, writer): diff --git a/pelican/settings.py b/pelican/settings.py index 7277c121..baf2a497 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -119,7 +119,12 @@ DEFAULT_CONFIG = { 'IGNORE_FILES': ['.#*'], 'SLUG_SUBSTITUTIONS': (), 'INTRASITE_LINK_REGEX': '[{|](?P.*?)[|}]', - 'SLUGIFY_SOURCE': 'title' + 'SLUGIFY_SOURCE': 'title', + 'CACHE_CONTENT': True, + 'CACHE_DIRECTORY': 'cache', + 'GZIP_CACHE': True, + 'CHECK_MODIFIED_METHOD': 'mtime', + 'LOAD_CONTENT_CACHE': True, } PYGMENTS_RST_OPTIONS = None diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 6f13aeb6..a500f87a 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -42,6 +42,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_DATE'] = (1970, 1, 1) settings['READERS'] = {'asc': None} + settings['CACHE_CONTENT'] = False # cache not needed for this logic tests cls.generator = ArticlesGenerator( context=settings.copy(), settings=settings, @@ -50,8 +51,15 @@ class TestArticlesGenerator(unittest.TestCase): cls.articles = [[page.title, page.status, page.category.name, page.template] for page in cls.generator.articles] + def setUp(self): + self.temp_cache = mkdtemp(prefix='pelican_cache.') + + def tearDown(self): + rmtree(self.temp_cache) + def test_generate_feeds(self): settings = get_settings() + settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) @@ -127,6 +135,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_DATE'] = (1970, 1, 1) settings['USE_FOLDER_AS_CATEGORY'] = False + settings['CACHE_DIRECTORY'] = self.temp_cache settings['READERS'] = {'asc': None} settings['filenames'] = {} generator = ArticlesGenerator( @@ -151,6 +160,7 @@ class TestArticlesGenerator(unittest.TestCase): def test_direct_templates_save_as_default(self): settings = get_settings(filenames={}) + settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) @@ -165,6 +175,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings() settings['DIRECT_TEMPLATES'] = ['archives'] settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' + settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) @@ -180,6 +191,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings() settings['DIRECT_TEMPLATES'] = ['archives'] settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' + settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) @@ -206,6 +218,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings(filenames={}) settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' + settings['CACHE_DIRECTORY'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=CONTENT_DIR, theme=settings['THEME'], output_path=None) @@ -268,6 +281,25 @@ class TestArticlesGenerator(unittest.TestCase): authors_expected = ['alexis-metaireau', 'first-author', 'second-author'] self.assertEqual(sorted(authors), sorted(authors_expected)) + def test_content_caching(self): + """Test that the articles are read only once when caching""" + settings = get_settings(filenames={}) + settings['CACHE_DIRECTORY'] = self.temp_cache + settings['READERS'] = {'asc': None} + + generator = ArticlesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + self.assertTrue(hasattr(generator, '_cache')) + + generator = ArticlesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.readers.read_file = MagicMock() + generator.generate_context() + generator.readers.read_file.assert_called_count == 0 + class TestPageGenerator(unittest.TestCase): # Note: Every time you want to test for a new field; Make sure the test @@ -275,12 +307,19 @@ class TestPageGenerator(unittest.TestCase): # distill_pages Then update the assertEqual in test_generate_context # to match expected + def setUp(self): + self.temp_cache = mkdtemp(prefix='pelican_cache.') + + def tearDown(self): + rmtree(self.temp_cache) + def distill_pages(self, pages): return [[page.title, page.status, page.template] for page in pages] def test_generate_context(self): settings = get_settings(filenames={}) settings['PAGE_DIR'] = 'TestPages' # relative to CUR_DIR + settings['CACHE_DIRECTORY'] = self.temp_cache settings['DEFAULT_DATE'] = (1970, 1, 1) generator = PagesGenerator( @@ -306,6 +345,26 @@ class TestPageGenerator(unittest.TestCase): self.assertEqual(sorted(pages_expected), sorted(pages)) self.assertEqual(sorted(hidden_pages_expected), sorted(hidden_pages)) + def test_content_caching(self): + """Test that the pages are read only once when caching""" + settings = get_settings(filenames={}) + settings['CACHE_DIRECTORY'] = 'cache_dir' #TODO + settings['CACHE_DIRECTORY'] = self.temp_cache + settings['READERS'] = {'asc': None} + + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CUR_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + self.assertTrue(hasattr(generator, '_cache')) + + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CUR_DIR, theme=settings['THEME'], output_path=None) + generator.readers.read_file = MagicMock() + generator.generate_context() + generator.readers.read_file.assert_called_count == 0 + class TestTemplatePagesGenerator(unittest.TestCase): diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 2d4bbdfc..15876095 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -43,12 +43,14 @@ class TestPelican(LoggedTestCase): def setUp(self): super(TestPelican, self).setUp() self.temp_path = mkdtemp(prefix='pelicantests.') + self.temp_cache = mkdtemp(prefix='pelican_cache.') self.old_locale = locale.setlocale(locale.LC_ALL) self.maxDiff = None locale.setlocale(locale.LC_ALL, str('C')) def tearDown(self): rmtree(self.temp_path) + rmtree(self.temp_cache) locale.setlocale(locale.LC_ALL, self.old_locale) super(TestPelican, self).tearDown() @@ -77,6 +79,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=None, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, + 'CACHE_DIRECTORY': self.temp_cache, 'LOCALE': locale.normalize('en_US'), }) pelican = Pelican(settings=settings) @@ -92,6 +95,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=SAMPLE_CONFIG, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, + 'CACHE_DIRECTORY': self.temp_cache, 'LOCALE': locale.normalize('en_US'), }) pelican = Pelican(settings=settings) @@ -103,6 +107,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=SAMPLE_CONFIG, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, + 'CACHE_DIRECTORY': self.temp_cache, 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'), os.path.join(SAMPLES_PATH, 'kinda'), os.path.join(SAMPLES_PATH, 'theme_standard')] @@ -123,6 +128,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=SAMPLE_CONFIG, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, + 'CACHE_DIRECTORY': self.temp_cache, 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'theme_standard')] }) diff --git a/pelican/utils.py b/pelican/utils.py index c5aacaa3..8c416921 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -12,6 +12,8 @@ import pytz import re import shutil import traceback +import pickle +import hashlib from collections import Hashable from contextlib import contextmanager @@ -545,3 +547,114 @@ def split_all(path): break path = head return components + + +class FileDataCacher(object): + '''Class that can cache data contained in files''' + + def __init__(self, settings, cache_policy_key, load_policy_key): + '''Load the specified cache within CACHE_DIRECTORY + + only if load_policy_key in setttings is True, + May use gzip if GZIP_CACHE. + Sets caching policy according to *cache_policy_key* + in *settings* + ''' + self.settings = settings + name = self.__class__.__name__ + self._cache_path = os.path.join(self.settings['CACHE_DIRECTORY'], name) + self._cache_data_policy = self.settings[cache_policy_key] + if not self.settings[load_policy_key]: + self._cache = {} + return + if self.settings['GZIP_CACHE']: + import gzip + self._cache_open = gzip.open + else: + self._cache_open = open + try: + with self._cache_open(self._cache_path, 'rb') as f: + self._cache = pickle.load(f) + except Exception as e: + self._cache = {} + + def cache_data(self, filename, data): + '''Cache data for given file''' + if not self._cache_data_policy: + return + self._cache[filename] = data + + def get_cached_data(self, filename, default={}): + '''Get cached data for the given file + + if no data is cached, return the default object + ''' + return self._cache.get(filename, default) + + def save_cache(self): + '''Save the updated cache''' + if not self._cache_data_policy: + return + try: + mkdir_p(self.settings['CACHE_DIRECTORY']) + with self._cache_open(self._cache_path, 'wb') as f: + pickle.dump(self._cache, f) + except Exception as e: + logger.warning('Could not save cache {}\n{}'.format( + self._cache_path, e)) + + +class FileStampDataCacher(FileDataCacher): + '''Subclass that also caches the stamp of the file''' + + def __init__(self, settings, cache_policy_key, load_policy_key): + '''This sublcass additionaly sets filestamp function''' + super(FileStampDataCacher, self).__init__(settings, cache_policy_key, + load_policy_key) + + method = self.settings['CHECK_MODIFIED_METHOD'] + if method == 'mtime': + self._filestamp_func = os.path.getmtime + else: + try: + hash_func = getattr(hashlib, method) + def filestamp_func(buf): + return hash_func(buf).digest() + self._filestamp_func = filestamp_func + except ImportError: + self._filestamp_func = None + + def cache_data(self, filename, data): + '''Cache stamp and data for the given file''' + stamp = self._get_file_stamp(filename) + super(FileStampDataCacher, self).cache_data(filename, (stamp, data)) + + def _get_file_stamp(self, filename): + '''Check if the given file has been modified + since the previous build. + + depending on CHECK_MODIFIED_METHOD + a float may be returned for 'mtime', + a hash for a function name in the hashlib module + or an empty bytes string otherwise + ''' + filename = os.path.join(self.path, filename) + try: + with open(filename, 'rb') as f: + return self._filestamp_func(f.read()) + except Exception: + return b'' + + def get_cached_data(self, filename, default=None): + '''Get the cached data for the given filename + if the file has not been modified. + + If no record exists or file has been modified, return default. + Modification is checked by compaing the cached + and current file stamp. + ''' + stamp, data = super(FileStampDataCacher, self).get_cached_data( + filename, (None, default)) + if stamp != self._get_file_stamp(filename): + return default + return data From d0aa16e8545259df71040f9229a8d2969509ac91 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sun, 9 Mar 2014 22:04:43 +0000 Subject: [PATCH 34/82] Add s3cmd MIME type detection --- pelican/tools/templates/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index fe7a60a4..c542e588 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -97,7 +97,7 @@ ftp_upload: publish lftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit" s3_upload: publish - s3cmd sync $(OUTPUTDIR)/ s3://$(S3_BUCKET) --acl-public --delete-removed + s3cmd sync $(OUTPUTDIR)/ s3://$(S3_BUCKET) --acl-public --delete-removed --guess-mime-type cf_upload: publish cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) . From 900fa755bac7beb5c9a7992eac2d6e57663ca932 Mon Sep 17 00:00:00 2001 From: Lonewolf Date: Mon, 31 Mar 2014 14:28:46 +0530 Subject: [PATCH 35/82] Added new sphinxtheme as requirement for docs Modified docs conf to support the theme update --- dev_requirements.txt | 3 +++ docs/conf.py | 34 +++++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index c90ac630..01fe2507 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -9,3 +9,6 @@ typogrify # To perform release bumpr==0.2.0 + +# For docs theme +sphinx_rtd_theme diff --git a/docs/conf.py b/docs/conf.py index 6db0f3d1..99acd1b6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals import sys, os +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + sys.path.append(os.path.abspath(os.pardir)) from pelican import __version__ @@ -21,29 +23,43 @@ rst_prolog = ''' .. |last_stable| replace:: :pelican-doc:`{0}` '''.format(last_stable) +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + extlinks = { 'pelican-doc': ('http://docs.getpelican.com/%s/', '') } # -- Options for HTML output --------------------------------------------------- -html_theme_path = ['_themes'] -html_theme = 'pelican' - -html_theme_options = { - 'nosidebar': True, - 'index_logo': 'pelican.png', - 'github_fork': 'getpelican/pelican', -} +html_theme = 'default' +if not on_rtd: + try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + except ImportError: + pass html_static_path = ['_static'] # Output file base name for HTML help builder. htmlhelp_basename = 'Pelicandoc' +html_use_smartypants = True + +# If false, no module index is generated. +html_use_modindex = False + +# If false, no index is generated. +html_use_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False + # -- Options for LaTeX output -------------------------------------------------- latex_documents = [ - ('index', 'Pelican.tex', 'Pelican Documentation', + ('index', 'Pelican.tex', 'Pelican Documentation', 'Alexis Métaireau', 'manual'), ] From 103e8e293a630460e9b20294910695556ab59dda Mon Sep 17 00:00:00 2001 From: Antoine Brenner Date: Tue, 15 Apr 2014 00:04:40 +0200 Subject: [PATCH 36/82] Fix unittest issue related to python2/python3 differences Under python 2, with non-ascii locales, u"{:%b}".format(date) can raise UnicodeDecodeError because u"{:%b}".format(date) will call date.__format__(u"%b"), which will return a byte string and not a unicode string. eg: locale.setlocale(locale.LC_ALL, 'ja_JP.utf8') date.__format__(u"%b") == '12\xe6\x9c\x88' # True This commit catches UnicodeDecodeError and calls date.__format__() with byte strings instead of characters, since it to work with character strings --- pelican/generators.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pelican/generators.py b/pelican/generators.py index 7c2dbbf2..1b584d3f 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -309,7 +309,20 @@ class ArticlesGenerator(Generator): # format string syntax can be used for specifying the # period archive dates date = archive[0].date - save_as = save_as_fmt.format(date=date) + # Under python 2, with non-ascii locales, u"{:%b}".format(date) might raise UnicodeDecodeError + # because u"{:%b}".format(date) will call date.__format__(u"%b"), which will return a byte string + # and not a unicode string. + # eg: + # locale.setlocale(locale.LC_ALL, 'ja_JP.utf8') + # date.__format__(u"%b") == '12\xe6\x9c\x88' # True + try: + save_as = save_as_fmt.format(date=date) + except UnicodeDecodeError: + # Python2 only: + # Let date.__format__() work with byte strings instead of characters since it fails to work with characters + bytes_save_as_fmt = save_as_fmt.encode('utf8') + bytes_save_as = bytes_save_as_fmt.format(date=date) + save_as = unicode(bytes_save_as,'utf8') context = self.context.copy() if key == period_date_key['year']: From b9a647547b1c4d6c03fe2e9cb40d021930893d23 Mon Sep 17 00:00:00 2001 From: Antoine Brenner Date: Tue, 15 Apr 2014 16:36:29 +0200 Subject: [PATCH 37/82] Make sure locale is what we want before/after the tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The locale is a global state, and it was not properly reset to whatever it was before the unitttest possibly changed it. This is now fixed. Not restoring the locale led to weird issues: depending on the order chosen by "python -m unittest discover" to run the unit tests, some tests would apparently randomly fail due to the locale not being what was expected. For example, test_period_in_timeperiod_archive would call mock('posts/1970/ 1月/index.html',...) instead of expected mock('posts/1970/Jan/index.html',...) and fail. --- pelican/tests/test_contents.py | 6 ++++++ pelican/tests/test_generators.py | 11 +++++++++++ pelican/tests/test_importer.py | 11 +++++++++++ pelican/tests/test_paginator.py | 8 +++++++- pelican/tests/test_pelican.py | 2 +- pelican/tests/test_settings.py | 5 +++++ pelican/tests/test_utils.py | 3 +++ 7 files changed, 44 insertions(+), 2 deletions(-) diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 27d2a897..3c0f8d75 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals, absolute_import import six from datetime import datetime from sys import platform +import locale from pelican.tests.support import unittest, get_settings @@ -22,6 +23,8 @@ class TestPage(unittest.TestCase): def setUp(self): super(TestPage, self).setUp() + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, str('C')) self.page_kwargs = { 'content': TEST_CONTENT, 'context': { @@ -35,6 +38,9 @@ class TestPage(unittest.TestCase): 'source_path': '/path/to/file/foo.ext' } + def tearDown(self): + locale.setlocale(locale.LC_ALL, self.old_locale) + def test_use_args(self): # Creating a page with arguments passed to the constructor should use # them to initialise object's attributes. diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index a500f87a..ff487c3e 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -14,6 +14,7 @@ from pelican.generators import (Generator, ArticlesGenerator, PagesGenerator, TemplatePagesGenerator) from pelican.writers import Writer from pelican.tests.support import unittest, get_settings +import locale CUR_DIR = os.path.dirname(__file__) CONTENT_DIR = os.path.join(CUR_DIR, 'content') @@ -21,11 +22,17 @@ CONTENT_DIR = os.path.join(CUR_DIR, 'content') class TestGenerator(unittest.TestCase): def setUp(self): + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, str('C')) self.settings = get_settings() self.settings['READERS'] = {'asc': None} self.generator = Generator(self.settings.copy(), self.settings, CUR_DIR, self.settings['THEME'], None) + def tearDown(self): + locale.setlocale(locale.LC_ALL, self.old_locale) + + def test_include_path(self): filename = os.path.join(CUR_DIR, 'content', 'article.rst') include_path = self.generator._include_path @@ -373,10 +380,14 @@ class TestTemplatePagesGenerator(unittest.TestCase): def setUp(self): self.temp_content = mkdtemp(prefix='pelicantests.') self.temp_output = mkdtemp(prefix='pelicantests.') + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, str('C')) + def tearDown(self): rmtree(self.temp_content) rmtree(self.temp_output) + locale.setlocale(locale.LC_ALL, self.old_locale) def test_generate_output(self): diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 8412c75b..65193bf5 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals, print_function import os import re +import locale 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) @@ -30,9 +31,14 @@ except ImportError: class TestWordpressXmlImporter(unittest.TestCase): def setUp(self): + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, str('C')) self.posts = list(wp2fields(WORDPRESS_XML_SAMPLE)) self.custposts = list(wp2fields(WORDPRESS_XML_SAMPLE, True)) + def tearDown(self): + locale.setlocale(locale.LC_ALL, self.old_locale) + def test_ignore_empty_posts(self): self.assertTrue(self.posts) for title, content, fname, date, author, categ, tags, kind, format in self.posts: @@ -261,8 +267,13 @@ class TestBuildHeader(unittest.TestCase): @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') class TestWordpressXMLAttachements(unittest.TestCase): def setUp(self): + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, str('C')) self.attachments = get_attachments(WORDPRESS_XML_SAMPLE) + def tearDown(self): + locale.setlocale(locale.LC_ALL, self.old_locale) + def test_recognise_attachments(self): self.assertTrue(self.attachments) self.assertTrue(len(self.attachments.keys()) == 3) diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index f454d47d..108dc791 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import import six +import locale from pelican.tests.support import unittest, get_settings @@ -16,6 +17,8 @@ TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) class TestPage(unittest.TestCase): def setUp(self): super(TestPage, self).setUp() + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, str('C')) self.page_kwargs = { 'content': TEST_CONTENT, 'context': { @@ -29,6 +32,9 @@ class TestPage(unittest.TestCase): 'source_path': '/path/to/file/foo.ext' } + def tearDown(self): + locale.setlocale(locale.LC_ALL, self.old_locale) + def test_save_as_preservation(self): settings = get_settings() # fix up pagination rules @@ -47,4 +53,4 @@ class TestPage(unittest.TestCase): object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)] paginator = Paginator('foobar.foo', object_list, settings) page = paginator.page(1) - self.assertEqual(page.save_as, 'foobar.foo') \ No newline at end of file + self.assertEqual(page.save_as, 'foobar.foo') diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 15876095..974986cd 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -44,8 +44,8 @@ class TestPelican(LoggedTestCase): super(TestPelican, self).setUp() self.temp_path = mkdtemp(prefix='pelicantests.') self.temp_cache = mkdtemp(prefix='pelican_cache.') - self.old_locale = locale.setlocale(locale.LC_ALL) self.maxDiff = None + self.old_locale = locale.setlocale(locale.LC_ALL) locale.setlocale(locale.LC_ALL, str('C')) def tearDown(self): diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 7907a551..930e0fea 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -16,10 +16,15 @@ class TestSettingsConfiguration(unittest.TestCase): optimizations. """ def setUp(self): + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, str('C')) self.PATH = abspath(dirname(__file__)) default_conf = join(self.PATH, 'default_conf.py') self.settings = read_settings(default_conf) + def tearDown(self): + locale.setlocale(locale.LC_ALL, self.old_locale) + def test_overwrite_existing_settings(self): self.assertEqual(self.settings.get('SITENAME'), "Alexis' log") self.assertEqual(self.settings.get('SITEURL'), diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 9047593f..02398336 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -354,9 +354,12 @@ class TestCopy(unittest.TestCase): def setUp(self): self.root_dir = mkdtemp(prefix='pelicantests.') + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, str('C')) def tearDown(self): shutil.rmtree(self.root_dir) + locale.setlocale(locale.LC_ALL, self.old_locale) def _create_file(self, *path): with open(os.path.join(self.root_dir, *path), 'w') as f: From 60905163c877ce64f77d4cd4e6fef1c07acf381e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 15 Apr 2014 11:13:10 -0400 Subject: [PATCH 38/82] Fix settings table in docs --- docs/settings.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index d8690230..9599ee10 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -249,9 +249,9 @@ posts for the month at ``posts/2011/Aug/index.html``. arrive at an appropriate archive of posts, without having to specify a page name. -==================================================== ===================================================== +====================================================== ===================================================== Setting name (default value) What does it do? -==================================================== ===================================================== +====================================================== ===================================================== `ARTICLE_URL` (``'{slug}.html'``) The URL to refer to an article. `ARTICLE_SAVE_AS` (``'{slug}.html'``) The place where we will save an article. `ARTICLE_LANG_URL` (``'{slug}-{lang}.html'``) The URL to refer to an article which doesn't use the @@ -262,7 +262,7 @@ Setting name (default value) What does it do? `DRAFT_SAVE_AS` (``'drafts/{slug}.html'``) The place where we will save an article draft. `DRAFT_LANG_URL` (``'drafts/{slug}-{lang}.html'``) The URL to refer to an article draft which doesn't use the default language. -`DRAFT_LANG_SAVE_AS` (``'drafts/{slug}-{lang}.html'``) The place where we will save an article draft which +`DRAFT_LANG_SAVE_AS` (``'drafts/{slug}-{lang}.html'``) The place where we will save an article draft which doesn't use the default language. `PAGE_URL` (``'pages/{slug}.html'``) The URL we will use to link to a page. `PAGE_SAVE_AS` (``'pages/{slug}.html'``) The location we will save the page. This value has to be @@ -285,7 +285,7 @@ Setting name (default value) What does it do? non-alphanumerics when generating slugs. Specified as a list of 2-tuples of ``(from, to)`` which are applied in order. -==================================================== ===================================================== +====================================================== ===================================================== .. note:: From cb17f11d9b0e6ee6ae076ee329bdcc3cde50ab83 Mon Sep 17 00:00:00 2001 From: Antoine Brenner Date: Tue, 15 Apr 2014 22:01:20 +0200 Subject: [PATCH 39/82] Test to reproduce an issue that occurs with python3.3 under macos10 only This test passes fine under linux --- pelican/tests/test_utils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 02398336..3c12a15b 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -458,6 +458,25 @@ class TestDateFormatter(unittest.TestCase): locale.setlocale(locale.LC_ALL, '') + @unittest.skipUnless(locale_available('fr_FR.UTF-8') or + locale_available('French'), + 'French locale needed') + def test_french_strftime(self): + # This test tries to reproduce an issue that occured with python3.3 under macos10 only + locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8')) + date = datetime.datetime(2014,8,14) + # we compare the lower() dates since macos10 returns "Jeudi" for %A whereas linux reports "jeudi" + self.assertEqual( u'jeudi, 14 août 2014', utils.strftime(date, date_format="%A, %d %B %Y").lower() ) + df = utils.DateFormatter() + self.assertEqual( u'jeudi, 14 août 2014', df(date, date_format="%A, %d %B %Y").lower() ) + # Let us now set the global locale to C: + locale.setlocale(locale.LC_ALL, str('C')) + # DateFormatter should still work as expected since it is the whole point of DateFormatter + # (This is where pre-2014/4/15 code fails on macos10) + df_date = df(date, date_format="%A, %d %B %Y").lower() + self.assertEqual( u'jeudi, 14 août 2014', df_date ) + + @unittest.skipUnless(locale_available('fr_FR.UTF-8') or locale_available('French'), 'French locale needed') From 23c1f2f3d0f733d353c491fe1cac00df77caf24e Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Thu, 17 Apr 2014 16:28:22 +0200 Subject: [PATCH 40/82] enable writing of only selected output paths - add WRITE_SELECTED setting - add --write-selected commandline option --- docs/faq.rst | 5 +++++ docs/settings.rst | 19 +++++++++++++++++++ pelican/__init__.py | 6 ++++++ pelican/settings.py | 7 +++++++ pelican/tests/test_pelican.py | 23 +++++++++++++++++++++++ pelican/utils.py | 14 ++++++++++++++ pelican/writers.py | 9 +++++++-- 7 files changed, 81 insertions(+), 2 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index bb9377e6..bf468c51 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -224,3 +224,8 @@ every time, so a ``rsync`` based upload will transfer them even if their content hasn't changed. A simple solution is to make ``rsync`` use the ``--checksum`` option, which will make it compare the file checksums in a much faster way than Pelican would. + +When only several specific output files are of interest (e.g. when +working on some specific page or the theme templates), the +`WRITE_SELECTED` option may help, see +:ref:`writing_only_selected_content`. diff --git a/docs/settings.rst b/docs/settings.rst index 9599ee10..8d8f9a16 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -179,6 +179,10 @@ Setting name (default value) `CHECK_MODIFIED_METHOD` (``mtime``) Controls how files are checked for modifications. `LOAD_CONTENT_CACHE` (``True``) If ``True``, load unmodified content from cache. `GZIP_CACHE` (``True``) If ``True``, use gzip to (de)compress the cache files. +`WRITE_SELECTED` (``[]``) If this list is not empty, **only** output files with their paths + in this list are written. Paths should be either relative to the current + working directory of Pelican or absolute. For possible use cases see + :ref:`writing_only_selected_content`. =============================================================================== ===================================================================== .. [#] Default is the system locale. @@ -774,6 +778,21 @@ written, so the modification times of the ``*.html`` files always change. Therefore, ``rsync`` based upload may benefit from the ``--checksum`` option. +.. _writing_only_selected_content: + +Writing only selected content +============================= + +When one article or page or the theme is being worked on it is often +desirable to display selected output files as soon as possible. In +such cases generating and writing all output is often unnecessary. +These selected output files can be given as output paths in the +`WRITE_SELECTED` list and **only** those files will be written. This +list can be also specified on the command-line using the +``--write-selected`` option which accepts a comma separated list +of output file paths. By default the list is empty so all output is +written. + Example settings ================ diff --git a/pelican/__init__.py b/pelican/__init__.py index b6bfe326..1ed98fc3 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -264,6 +264,10 @@ def parse_arguments(): parser.add_argument('-f', '--full-rebuild', action='store_true', dest='full_rebuild', help='Rebuild everything by not loading from cache') + parser.add_argument('-w', '--write-selected', type=str, + dest='selected_paths', default=None, + help='Comma separated list of selected paths to write') + return parser.parse_args() @@ -281,6 +285,8 @@ def get_config(args): config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir if args.full_rebuild: config['LOAD_CONTENT_CACHE'] = False + if args.selected_paths: + config['WRITE_SELECTED'] = args.selected_paths.split(',') # argparse returns bytes in Py2. There is no definite answer as to which # encoding argparse (or sys.argv) uses. diff --git a/pelican/settings.py b/pelican/settings.py index baf2a497..7615c25c 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -125,6 +125,7 @@ DEFAULT_CONFIG = { 'GZIP_CACHE': True, 'CHECK_MODIFIED_METHOD': 'mtime', 'LOAD_CONTENT_CACHE': True, + 'WRITE_SELECTED': [], } PYGMENTS_RST_OPTIONS = None @@ -200,6 +201,12 @@ def configure_settings(settings): raise Exception("Could not find the theme %s" % settings['THEME']) + # make paths selected for writing absolute if necessary + settings['WRITE_SELECTED'] = [ + os.path.abspath(path) for path in + settings.get('WRITE_SELECTED', DEFAULT_CONFIG['WRITE_SELECTED']) + ] + # standardize strings to lowercase strings for key in [ 'DEFAULT_LANG', diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 974986cd..294cf399 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -138,3 +138,26 @@ class TestPelican(LoggedTestCase): for file in ['a_stylesheet', 'a_template']: self.assertTrue(os.path.exists(os.path.join(theme_output, file))) + + def test_write_only_selected(self): + """Test that only the selected files are written""" + settings = read_settings(path=None, override={ + 'PATH': INPUT_PATH, + 'OUTPUT_PATH': self.temp_path, + 'CACHE_DIRECTORY': self.temp_cache, + 'WRITE_SELECTED': [ + os.path.join(self.temp_path, 'oh-yeah.html'), + os.path.join(self.temp_path, 'categories.html'), + ], + 'LOCALE': locale.normalize('en_US'), + }) + pelican = Pelican(settings=settings) + logger = logging.getLogger() + orig_level = logger.getEffectiveLevel() + logger.setLevel(logging.INFO) + mute(True)(pelican.run)() + logger.setLevel(orig_level) + self.assertLogCountEqual( + count=2, + msg="writing .*", + level=logging.INFO) diff --git a/pelican/utils.py b/pelican/utils.py index 8c416921..cd942fd5 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -658,3 +658,17 @@ class FileStampDataCacher(FileDataCacher): if stamp != self._get_file_stamp(filename): return default return data + + +def is_selected_for_writing(settings, path): + '''Check whether path is selected for writing + according to the WRITE_SELECTED list + + If WRITE_SELECTED is an empty list (default), + any path is selected for writing. + ''' + if settings['WRITE_SELECTED']: + return path in settings['WRITE_SELECTED'] + else: + return True + diff --git a/pelican/writers.py b/pelican/writers.py index 19e36e39..a92feee4 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -16,7 +16,8 @@ from feedgenerator import Atom1Feed, Rss201rev2Feed from jinja2 import Markup from pelican.paginator import Paginator -from pelican.utils import get_relative_path, path_to_url, set_date_tzinfo +from pelican.utils import (get_relative_path, path_to_url, set_date_tzinfo, + is_selected_for_writing) from pelican import signals logger = logging.getLogger(__name__) @@ -92,6 +93,8 @@ class Writer(object): :param path: the path to output. :param feed_type: the feed type to use (atom or rss) """ + if not is_selected_for_writing(self.settings, path): + return old_locale = locale.setlocale(locale.LC_ALL) locale.setlocale(locale.LC_ALL, str('C')) try: @@ -140,7 +143,9 @@ class Writer(object): :param **kwargs: additional variables to pass to the templates """ - if name is False or name == "": + if name is False or name == "" or\ + not is_selected_for_writing(self.settings,\ + os.path.join(self.output_path, name)): return elif not name: # other stuff, just return for now From f1a93d68fb7d5c7e7d571091c2da80e3ddaba85f Mon Sep 17 00:00:00 2001 From: Simon Conseil Date: Thu, 17 Apr 2014 22:57:26 +0200 Subject: [PATCH 41/82] Add python 3.4 to tox config. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a72aea21..5dd36c36 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ # depends on some external libraries that aren't released yet. [tox] -envlist = py27,py33 +envlist = py27,py33,py34 [testenv] commands = From 75feec21d6d53d864da974c0b671299e74cebf88 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Fri, 18 Apr 2014 06:57:59 +0200 Subject: [PATCH 42/82] set _cache_open func even if not loading cache, fixes autoreload The _cache_open attribute of the FileDataCacher class was not set when settings[load_policy_key] was not True, so saving later failed. As a precaution, replaced the `if ...: return` style with a plain if structure to prevent such readability issues and added tests. --- pelican/tests/test_generators.py | 48 ++++++++++++++++++++++++++++++++ pelican/utils.py | 36 +++++++++++------------- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index ff487c3e..f951f0cb 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -307,6 +307,30 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_context() generator.readers.read_file.assert_called_count == 0 + def test_full_rebuild(self): + """Test that all the articles are read again when not loading cache + + used in --full-rebuild or autoreload mode""" + settings = get_settings(filenames={}) + settings['CACHE_DIRECTORY'] = self.temp_cache + settings['READERS'] = {'asc': None} + + generator = ArticlesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.readers.read_file = MagicMock() + generator.generate_context() + self.assertTrue(hasattr(generator, '_cache_open')) + orig_call_count = generator.readers.read_file.call_count + + settings['LOAD_CONTENT_CACHE'] = False + generator = ArticlesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.readers.read_file = MagicMock() + generator.generate_context() + generator.readers.read_file.assert_called_count == orig_call_count + class TestPageGenerator(unittest.TestCase): # Note: Every time you want to test for a new field; Make sure the test @@ -372,6 +396,30 @@ class TestPageGenerator(unittest.TestCase): generator.generate_context() generator.readers.read_file.assert_called_count == 0 + def test_full_rebuild(self): + """Test that all the pages are read again when not loading cache + + used in --full-rebuild or autoreload mode""" + settings = get_settings(filenames={}) + settings['CACHE_DIRECTORY'] = self.temp_cache + settings['READERS'] = {'asc': None} + + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.readers.read_file = MagicMock() + generator.generate_context() + self.assertTrue(hasattr(generator, '_cache_open')) + orig_call_count = generator.readers.read_file.call_count + + settings['LOAD_CONTENT_CACHE'] = False + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.readers.read_file = MagicMock() + generator.generate_context() + generator.readers.read_file.assert_called_count == orig_call_count + class TestTemplatePagesGenerator(unittest.TestCase): diff --git a/pelican/utils.py b/pelican/utils.py index cd942fd5..cda3108e 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -564,25 +564,24 @@ class FileDataCacher(object): name = self.__class__.__name__ self._cache_path = os.path.join(self.settings['CACHE_DIRECTORY'], name) self._cache_data_policy = self.settings[cache_policy_key] - if not self.settings[load_policy_key]: - self._cache = {} - return if self.settings['GZIP_CACHE']: import gzip self._cache_open = gzip.open else: self._cache_open = open - try: - with self._cache_open(self._cache_path, 'rb') as f: - self._cache = pickle.load(f) - except Exception as e: + if self.settings[load_policy_key]: + try: + with self._cache_open(self._cache_path, 'rb') as f: + self._cache = pickle.load(f) + except Exception as e: + self._cache = {} + else: self._cache = {} def cache_data(self, filename, data): '''Cache data for given file''' - if not self._cache_data_policy: - return - self._cache[filename] = data + if self._cache_data_policy: + self._cache[filename] = data def get_cached_data(self, filename, default={}): '''Get cached data for the given file @@ -593,15 +592,14 @@ class FileDataCacher(object): def save_cache(self): '''Save the updated cache''' - if not self._cache_data_policy: - return - try: - mkdir_p(self.settings['CACHE_DIRECTORY']) - with self._cache_open(self._cache_path, 'wb') as f: - pickle.dump(self._cache, f) - except Exception as e: - logger.warning('Could not save cache {}\n{}'.format( - self._cache_path, e)) + if self._cache_data_policy: + try: + mkdir_p(self.settings['CACHE_DIRECTORY']) + with self._cache_open(self._cache_path, 'wb') as f: + pickle.dump(self._cache, f) + except Exception as e: + logger.warning('Could not save cache {}\n{}'.format( + self._cache_path, e)) class FileStampDataCacher(FileDataCacher): From e046f59ce2dd8e817a2fb5bfcc09df1e9fcadfc6 Mon Sep 17 00:00:00 2001 From: James Lee Date: Sat, 19 Apr 2014 03:37:47 +0900 Subject: [PATCH 43/82] Handle list metadata as list of string in MarkdownReader --- pelican/readers.py | 6 ++++++ .../tests/content/article_with_markdown_and_footnote.md | 6 ++++++ pelican/tests/test_readers.py | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/pelican/readers.py b/pelican/readers.py index 35c38220..3f8a551e 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -204,12 +204,18 @@ class MarkdownReader(BaseReader): for name, value in meta.items(): name = name.lower() if name == "summary": + # handle summary metadata as markdown + # summary metadata is special case and join all list values summary_values = "\n".join(value) # reset the markdown instance to clear any state self._md.reset() summary = self._md.convert(summary_values) output[name] = self.process_metadata(name, summary) + elif len(value) > 1: + # handle list metadata as list of string + output[name] = self.process_metadata(name, value) else: + # otherwise, handle metadata as single string output[name] = self.process_metadata(name, value[0]) return output diff --git a/pelican/tests/content/article_with_markdown_and_footnote.md b/pelican/tests/content/article_with_markdown_and_footnote.md index 332ccea6..6fea2d6e 100644 --- a/pelican/tests/content/article_with_markdown_and_footnote.md +++ b/pelican/tests/content/article_with_markdown_and_footnote.md @@ -2,6 +2,12 @@ Title: Article with markdown containing footnotes Date: 2012-10-31 Modified: 2012-11-01 Summary: Summary with **inline** markup *should* be supported. +Multiline: Line Metadata should be handle properly. + See syntax of Meta-Data extension of Python Markdown package: + If a line is indented by 4 or more spaces, + that line is assumed to be an additional line of the value + for the previous keyword. + A keyword may have as many lines as desired. This is some content[^1] with some footnotes[^footnote] diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index d4201a5e..fd30e9b9 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -214,6 +214,14 @@ class MdReaderTest(ReaderTest): 'date': datetime.datetime(2012, 10, 31), 'modified': datetime.datetime(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:', + 'If a line is indented by 4 or more spaces,', + 'that line is assumed to be an additional line of the value', + 'for the previous keyword.', + 'A keyword may have as many lines as desired.', + ] } self.assertEqual(content, expected_content) for key, value in metadata.items(): From 2f916d01548009bdf355b4b21d656b4a82899fe0 Mon Sep 17 00:00:00 2001 From: Lonewolf Date: Sun, 2 Mar 2014 19:21:22 +0530 Subject: [PATCH 44/82] Ability to specify PLUGIN_PATH as list PLUGIN_PATH added to settings table --- docs/plugins.rst | 2 +- pelican/__init__.py | 3 ++- pelican/settings.py | 14 +++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index c03b1251..9dddce70 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -24,7 +24,7 @@ If your plugins are not in an importable path, you can specify a ``PLUGIN_PATH`` in the settings. ``PLUGIN_PATH`` can be an absolute path or a path relative to the settings file:: - PLUGIN_PATH = "plugins" + PLUGIN_PATH = ["list", "of", plugins path"] PLUGINS = ["list", "of", "plugins"] Where to find plugins diff --git a/pelican/__init__.py b/pelican/__init__.py index 1ed98fc3..077859bb 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -65,7 +65,8 @@ class Pelican(object): self.plugins = [] logger.debug('Temporarily adding PLUGIN_PATH to system path') _sys_path = sys.path[:] - sys.path.insert(0, self.settings['PLUGIN_PATH']) + for pluginpath in self.settings['PLUGIN_PATH']: + sys.path.insert(0, pluginpath) for plugin in self.settings['PLUGINS']: # if it's a string, then import it if isinstance(plugin, six.string_types): diff --git a/pelican/settings.py b/pelican/settings.py index 7615c25c..7caffa61 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -112,7 +112,7 @@ DEFAULT_CONFIG = { 'ARTICLE_PERMALINK_STRUCTURE': '', 'TYPOGRIFY': False, 'SUMMARY_MAX_LENGTH': 50, - 'PLUGIN_PATH': '', + 'PLUGIN_PATH': [], 'PLUGINS': [], 'PYGMENTS_RST_OPTIONS': {}, 'TEMPLATE_PAGES': {}, @@ -135,13 +135,21 @@ def read_settings(path=None, override=None): if path: local_settings = get_settings_from_file(path) # Make the paths relative to the settings file - for p in ['PATH', 'OUTPUT_PATH', 'THEME', 'PLUGIN_PATH']: + for p in ['PATH', 'OUTPUT_PATH', 'THEME']: if p in local_settings and local_settings[p] is not None \ and not isabs(local_settings[p]): absp = os.path.abspath(os.path.normpath(os.path.join( os.path.dirname(path), local_settings[p]))) - if p not in ('THEME', 'PLUGIN_PATH') or os.path.exists(absp): + if p not in ('THEME') or os.path.exists(absp): local_settings[p] = absp + + if isinstance(local_settings['PLUGIN_PATH'], six.string_types): + logger.warning("Detected misconfiguration with %s setting ""(must be a list)" % 'PLUGIN_PATH') + local_settings['PLUGIN_PATH'] = [local_settings['PLUGIN_PATH']] + else: + if 'PLUGIN_PATH' in local_settings and local_settings['PLUGIN_PATH'] is not None: + local_settings['PLUGIN_PATH'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath))) + if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATH']] else: local_settings = copy.deepcopy(DEFAULT_CONFIG) From be22bc807c31429d6373bbd4e7bab8fdd9f39a80 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 18 Apr 2014 13:21:06 -0700 Subject: [PATCH 45/82] Text tweaks for "PLUGIN_PATH as list" feature --- docs/plugins.rst | 10 +++++----- pelican/settings.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index 9dddce70..16d697fa 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -20,12 +20,12 @@ Alternatively, another method is to import them and add them to the list:: from package import myplugin PLUGINS = [myplugin,] -If your plugins are not in an importable path, you can specify a ``PLUGIN_PATH`` -in the settings. ``PLUGIN_PATH`` can be an absolute path or a path relative to -the settings file:: +If your plugins are not in an importable path, you can specify a list of paths +via the ``PLUGIN_PATH`` setting. As shown in the following example, paths in +the ``PLUGIN_PATH`` list can be absolute or relative to the settings file:: - PLUGIN_PATH = ["list", "of", plugins path"] - PLUGINS = ["list", "of", "plugins"] + PLUGIN_PATH = ["plugins", "/srv/pelican/plugins"] + PLUGINS = ["assets", "liquid_tags", "sitemap"] Where to find plugins ===================== diff --git a/pelican/settings.py b/pelican/settings.py index 7caffa61..ee337386 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -144,7 +144,7 @@ def read_settings(path=None, override=None): local_settings[p] = absp if isinstance(local_settings['PLUGIN_PATH'], six.string_types): - logger.warning("Detected misconfiguration with %s setting ""(must be a list)" % 'PLUGIN_PATH') + logger.warning("Defining %s setting as string has been deprecated (should be a list)" % 'PLUGIN_PATH') local_settings['PLUGIN_PATH'] = [local_settings['PLUGIN_PATH']] else: if 'PLUGIN_PATH' in local_settings and local_settings['PLUGIN_PATH'] is not None: From 5c317329b3d9ce2b4b0088042afd46f7d894d60f Mon Sep 17 00:00:00 2001 From: Tastalian Date: Mon, 10 Mar 2014 04:16:38 +0100 Subject: [PATCH 46/82] Make docutils requirement explicit. Fixes #1243. Previously, the error returned by Python when docutils is not installed was not explicit, instead saying that HTMLTranslator is not defined (needed by FeedGenerator and such), forcing the user to go into readers.py to figure out that this happens because "import docutils" failed. This pull request makes the docutils dependency explicit, so that there is an ImportError if doctutils is not found. --- pelican/readers.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index 3f8a551e..fa9d92ae 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -6,16 +6,13 @@ import logging import os import re -try: - import docutils - import docutils.core - import docutils.io - from docutils.writers.html4css1 import HTMLTranslator +import docutils +import docutils.core +import docutils.io +from docutils.writers.html4css1 import HTMLTranslator - # import the directives to have pygments support - from pelican import rstdirectives # NOQA -except ImportError: - docutils = False +# import the directives to have pygments support +from pelican import rstdirectives # NOQA try: from markdown import Markdown except ImportError: From 55cab637140d292df394ae12c6ae254fa3fc9230 Mon Sep 17 00:00:00 2001 From: Shauna Date: Sat, 5 Apr 2014 15:27:03 -0400 Subject: [PATCH 47/82] Add feeds for each author --- docs/settings.rst | 2 + pelican/generators.py | 12 ++++ pelican/settings.py | 3 + .../basic/feeds/alexis-metaireau.atom.xml | 20 ++++++ .../basic/feeds/alexis-metaireau.rss.xml | 20 ++++++ .../custom/feeds/alexis-metaireau.atom.xml | 61 +++++++++++++++++++ .../custom/feeds/alexis-metaireau.rss.xml | 61 +++++++++++++++++++ 7 files changed, 179 insertions(+) create mode 100644 pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml create mode 100644 pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml create mode 100644 pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml create mode 100644 pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml diff --git a/docs/settings.rst b/docs/settings.rst index 8d8f9a16..0de811ec 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -474,6 +474,8 @@ Setting name (default value) What does it do? language. `CATEGORY_FEED_ATOM` ('feeds/%s.atom.xml'[2]_) Where to put the category Atom feeds. `CATEGORY_FEED_RSS` (``None``, i.e. no RSS) Where to put the category RSS feeds. +`AUTHOR_FEED_ATOM` ('feeds/%s.atom.xml'[2]_) Where to put the author Atom feeds. +`AUTHOR_FEED_RSS` ('feeds/%s.rss.xml'[2]_) Where to put the author RSS feeds. `TAG_FEED_ATOM` (``None``, i.e. no tag feed) Relative URL to output the tag Atom feed. It should be defined using a "%s" match in the tag name. `TAG_FEED_RSS` (``None``, ie no RSS tag feed) Relative URL to output the tag RSS feed diff --git a/pelican/generators.py b/pelican/generators.py index 1b584d3f..a2d7320a 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -239,6 +239,18 @@ class ArticlesGenerator(Generator): self.settings['CATEGORY_FEED_RSS'] % cat.slug, feed_type='rss') + for auth, arts in self.authors: + arts.sort(key=attrgetter('date'), reverse=True) + if self.settings.get('AUTHOR_FEED_ATOM'): + writer.write_feed(arts, self.context, + self.settings['AUTHOR_FEED_ATOM'] + % auth.slug) + + if self.settings.get('AUTHOR_FEED_RSS'): + writer.write_feed(arts, self.context, + self.settings['AUTHOR_FEED_RSS'] + % auth.slug, feed_type='rss') + if (self.settings.get('TAG_FEED_ATOM') or self.settings.get('TAG_FEED_RSS')): for tag, arts in self.tags.items(): diff --git a/pelican/settings.py b/pelican/settings.py index ee337386..1d0ada0c 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -41,6 +41,8 @@ DEFAULT_CONFIG = { '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_MAX_ITEMS': '', 'SITEURL': '', @@ -269,6 +271,7 @@ def configure_settings(settings): 'FEED_ATOM', 'FEED_RSS', 'FEED_ALL_ATOM', 'FEED_ALL_RSS', 'CATEGORY_FEED_ATOM', 'CATEGORY_FEED_RSS', + 'AUTHOR_FEED_ATOM', 'AUTHOR_FEED_RSS', 'TAG_FEED_ATOM', 'TAG_FEED_RSS', 'TRANSLATION_FEED_ATOM', 'TRANSLATION_FEED_RSS', ] diff --git a/pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml b/pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml new file mode 100644 index 00000000..d87023b5 --- /dev/null +++ b/pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml @@ -0,0 +1,20 @@ + +A Pelican Blog/2013-11-17T23:29:00ZThis is a super article !2013-11-17T23:29:00ZAlexis Métaireautag:,2010-12-02:this-is-a-super-article.html<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="|filename|/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Oh yeah !2010-10-20T10:14:00ZAlexis Métaireautag:,2010-10-20:oh-yeah.html<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> + \ No newline at end of file diff --git a/pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml b/pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml new file mode 100644 index 00000000..09409217 --- /dev/null +++ b/pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml @@ -0,0 +1,20 @@ + +A Pelican Blog/Sun, 17 Nov 2013 23:29:00 -0000This is a super article !/this-is-a-super-article.html<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="|filename|/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Alexis MétaireauSun, 17 Nov 2013 23:29:00 -0000tag:,2010-12-02:this-is-a-super-article.htmlfoobarfoobarOh yeah !/oh-yeah.html<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Alexis MétaireauWed, 20 Oct 2010 10:14:00 -0000tag:,2010-10-20:oh-yeah.htmlohbaryeah \ No newline at end of file diff --git a/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml b/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml new file mode 100644 index 00000000..cb746377 --- /dev/null +++ b/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml @@ -0,0 +1,61 @@ + +Alexis' loghttp://blog.notmyidea.org/2013-11-17T23:29:00+01:00FILENAME_METADATA example2012-11-30T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-11-30:filename_metadata-example.html<p>Some cool stuff!</p> +Second article2012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:second-article.html<p>This is some article, in english</p> +A markdown powered article2011-04-20T00:00:00+02:00Alexis Métaireautag:blog.notmyidea.org,2011-04-20:a-markdown-powered-article.html<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/unbelievable.html">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/unbelievable.html">a file-relative link to unbelievable</a></p>Article 12011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:article-1.html<p>Article 1</p> +Article 22011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:article-2.html<p>Article 2</p> +Article 32011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:article-3.html<p>Article 3</p> +This is a super article !2013-11-17T23:29:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-12-02:this-is-a-super-article.html<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Oh yeah !2010-10-20T10:14:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-20:oh-yeah.html<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Unbelievable !2010-10-15T20:30:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-15:unbelievable.html<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +The baz tag2010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> + \ No newline at end of file diff --git a/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml b/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml new file mode 100644 index 00000000..2c4b1160 --- /dev/null +++ b/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml @@ -0,0 +1,61 @@ + +Alexis' loghttp://blog.notmyidea.org/Sun, 17 Nov 2013 23:29:00 +0100FILENAME_METADATA examplehttp://blog.notmyidea.org/filename_metadata-example.html<p>Some cool stuff!</p> +Alexis MétaireauFri, 30 Nov 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-11-30:filename_metadata-example.htmlSecond articlehttp://blog.notmyidea.org/second-article.html<p>This is some article, in english</p> +Alexis MétaireauWed, 29 Feb 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-02-29:second-article.htmlfoobarbazA markdown powered articlehttp://blog.notmyidea.org/a-markdown-powered-article.html<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/unbelievable.html">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/unbelievable.html">a file-relative link to unbelievable</a></p>Alexis MétaireauWed, 20 Apr 2011 00:00:00 +0200tag:blog.notmyidea.org,2011-04-20:a-markdown-powered-article.htmlArticle 1http://blog.notmyidea.org/article-1.html<p>Article 1</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:article-1.htmlArticle 2http://blog.notmyidea.org/article-2.html<p>Article 2</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:article-2.htmlArticle 3http://blog.notmyidea.org/article-3.html<p>Article 3</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:article-3.htmlThis is a super article !http://blog.notmyidea.org/this-is-a-super-article.html<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Alexis MétaireauSun, 17 Nov 2013 23:29:00 +0100tag:blog.notmyidea.org,2010-12-02:this-is-a-super-article.htmlfoobarfoobarOh yeah !http://blog.notmyidea.org/oh-yeah.html<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:oh-yeah.htmlohbaryeahUnbelievable !http://blog.notmyidea.org/unbelievable.html<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:unbelievable.htmlThe baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:tag/baz.html \ No newline at end of file From 3e12f40b08970278d12d74a2ae3fc1c9416374e4 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 20 Apr 2014 14:34:52 +0200 Subject: [PATCH 48/82] split content caching into two layers This is a reworked and improved version of content caching. Notable changes: - by default only raw content and metadata returned by readers are cached which should prevent conficts with plugins, the speed benefit of content objects caching is not very big with a simple setup - renamed --full-rebuild to --ignore-cache - added more elaborate logging to caching code --- README.rst | 1 + docs/index.rst | 1 + docs/settings.rst | 41 +++++++++++++------ pelican/__init__.py | 24 +++++------ pelican/generators.py | 46 ++++++++++++++++----- pelican/readers.py | 20 +++++++-- pelican/settings.py | 10 +++++ pelican/tests/test_generators.py | 67 ++++++++++++++++++++++++++----- pelican/utils.py | 69 ++++++++++++++++++-------------- 9 files changed, 199 insertions(+), 80 deletions(-) diff --git a/README.rst b/README.rst index 20c3f217..bf506c5f 100644 --- a/README.rst +++ b/README.rst @@ -29,6 +29,7 @@ Pelican currently supports: * 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. Have a look at the `Pelican documentation`_ for more information. diff --git a/docs/index.rst b/docs/index.rst index 43193e9e..c2deb6de 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,6 +33,7 @@ Pelican |version| currently supports: * 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. Why the name "Pelican"? ----------------------- diff --git a/docs/settings.rst b/docs/settings.rst index 0de811ec..1b4bae94 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -161,6 +161,7 @@ Setting name (default value) `_ `WITH_FUTURE_DATES` (``True``) If disabled, content with dates in the future will get a default status of ``draft``. + see :ref:`reading_only_modified_content` for details. `INTRASITE_LINK_REGEX` (``'[{|](?P.*?)[|}]'``) Regular expression that is used to parse internal links. Default syntax of links to internal files, tags, etc., is to enclose the identifier, say ``filename``, in ``{}`` or ``||``. @@ -173,12 +174,16 @@ Setting name (default value) `SLUGIFY_SOURCE` (``'input'``) Specifies where you want the slug to be automatically generated from. Can be set to 'title' to use the 'Title:' metadata tag or 'basename' to use the articles basename when creating the slug. -`CACHE_CONTENT` (``True``) If ``True``, save read content in a cache file. +`CACHE_CONTENT` (``True``) If ``True``, save content in a cache file. See :ref:`reading_only_modified_content` for details about caching. +`CONTENT_CACHING_LAYER` (``'reader'``) If set to ``'reader'``, save only the raw content and metadata returned + by readers, if set to ``'generator'``, save processed content objects. `CACHE_DIRECTORY` (``cache``) Directory in which to store cache files. +`GZIP_CACHE` (``True``) If ``True``, use gzip to (de)compress the cache files. `CHECK_MODIFIED_METHOD` (``mtime``) Controls how files are checked for modifications. `LOAD_CONTENT_CACHE` (``True``) If ``True``, load unmodified content from cache. -`GZIP_CACHE` (``True``) If ``True``, use gzip to (de)compress the cache files. +`AUTORELOAD_IGNORE_CACHE` (``False``) If ``True``, do not load content cache in autoreload mode + when the settings file changes. `WRITE_SELECTED` (``[]``) If this list is not empty, **only** output files with their paths in this list are written. Paths should be either relative to the current working directory of Pelican or absolute. For possible use cases see @@ -749,13 +754,21 @@ When Pelican is about to read some content source file: file cannot be found in the cache file, the content is read as usual. -3. If the file is considered unchanged, the content object saved in a +3. If the file is considered unchanged, the content data saved in a previous build corresponding to the file is loaded from the cache and the file is not read. 4. If the file is considered changed, the file is read and the new - modification information and the content object are saved to the + modification information and the content data are saved to the cache if `CACHE_CONTENT` is ``True``. +Depending on `CONTENT_CACHING_LAYER` either the raw content and +metadata returned by a reader are cached if set to ``'reader'``, or +the processed content object is cached if set to ``'generator'``. +Caching the processed content object may conflict with plugins (as +some reading related signals may be skipped) or e.g. the +`WITH_FUTURE_DATES` functionality (as the ``draft`` status of the +cached content objects would not change automatically over time). + Modification time based checking is faster than comparing file hashes, but is not as reliable, because mtime information can be lost when e.g. copying the content sources using the ``cp`` or ``rsync`` @@ -764,16 +777,18 @@ commands without the mtime preservation mode (invoked e.g. by 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 -using the pelican command-line option ``--full-rebuild``. -The cache files also have to be rebuilt when changing the -`GZIP_CACHE` setting for cache file reading to work. +such an error is encountered, the cache files have to be rebuilt by +running pelican after removing them 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. -The ``--full-rebuild`` command-line option is also useful when the -whole site needs to be regenerated due to e.g. modifications to the -settings file or theme files. When pelican runs in autorealod mode, -modification of the settings file or theme will trigger a full rebuild -automatically. +The ``--ignore-cache`` command-line option is also useful when the +whole cache needs to be regenerated due to e.g. modifications to the +settings file which should change the cached content or just for +debugging purposes. When pelican runs in autoreload mode, modification +of the settings file will make it ignore the cache automatically if +`AUTORELOAD_IGNORE_CACHE` is ``True``. Note that even when using cached content, all output is always written, so the modification times of the ``*.html`` files always diff --git a/pelican/__init__.py b/pelican/__init__.py index 077859bb..8cae468c 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -262,8 +262,9 @@ def parse_arguments(): help='Relaunch pelican each time a modification occurs' ' on the content files.') - parser.add_argument('-f', '--full-rebuild', action='store_true', - dest='full_rebuild', help='Rebuild everything by not loading from cache') + parser.add_argument('-c', '--ignore-cache', action='store_true', + dest='ignore_cache', help='Ignore content cache ' + 'from previous runs by not loading cache files.') parser.add_argument('-w', '--write-selected', type=str, dest='selected_paths', default=None, @@ -284,7 +285,7 @@ def get_config(args): config['THEME'] = abstheme if os.path.exists(abstheme) else args.theme if args.delete_outputdir is not None: config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir - if args.full_rebuild: + if args.ignore_cache: config['LOAD_CONTENT_CACHE'] = False if args.selected_paths: config['WRITE_SELECTED'] = args.selected_paths.split(',') @@ -340,7 +341,10 @@ def main(): print(' --- AutoReload Mode: Monitoring `content`, `theme` and' ' `settings` for changes. ---') - first_run = True # load cache on first run + def _ignore_cache(pelican_obj): + if pelican_obj.settings['AUTORELOAD_IGNORE_CACHE']: + pelican_obj.settings['LOAD_CONTENT_CACHE'] = False + while True: try: # Check source dir for changed files ending with the given @@ -353,10 +357,9 @@ def main(): if modified['settings']: pelican, settings = get_instance(args) - if not first_run: - original_load_cache = settings['LOAD_CONTENT_CACHE'] - # invalidate cache - pelican.settings['LOAD_CONTENT_CACHE'] = False + original_load_cache = settings['LOAD_CONTENT_CACHE'] + print(pelican.settings['AUTORELOAD_IGNORE_CACHE']) + _ignore_cache(pelican) if any(modified.values()): print('\n-> Modified: {}. re-generating...'.format( @@ -368,13 +371,8 @@ def main(): if modified['theme'] is None: logger.warning('Empty theme folder. Using `basic` ' 'theme.') - elif modified['theme']: - # theme modified, needs full rebuild -> no cache - if not first_run: # but not on first run - pelican.settings['LOAD_CONTENT_CACHE'] = False pelican.run() - first_run = False # restore original caching policy pelican.settings['LOAD_CONTENT_CACHE'] = original_load_cache diff --git a/pelican/generators.py b/pelican/generators.py index a2d7320a..3cc84fa8 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -28,10 +28,11 @@ from pelican import signals logger = logging.getLogger(__name__) -class Generator(FileStampDataCacher): +class Generator(object): """Baseclass generator""" - def __init__(self, context, settings, path, theme, output_path, **kwargs): + def __init__(self, context, settings, path, theme, output_path, + readers_cache_name='', **kwargs): self.context = context self.settings = settings self.path = path @@ -41,7 +42,7 @@ class Generator(FileStampDataCacher): for arg, value in kwargs.items(): setattr(self, arg, value) - self.readers = Readers(self.settings) + self.readers = Readers(self.settings, readers_cache_name) # templates cache self._templates = {} @@ -74,10 +75,6 @@ class Generator(FileStampDataCacher): custom_filters = self.settings['JINJA_FILTERS'] self.env.filters.update(custom_filters) - # set up caching - super(Generator, self).__init__(settings, 'CACHE_CONTENT', - 'LOAD_CONTENT_CACHE') - signals.generator_init.send(self) def get_template(self, name): @@ -153,6 +150,35 @@ class Generator(FileStampDataCacher): self.context[item] = value +class CachingGenerator(Generator, FileStampDataCacher): + '''Subclass of Generator and FileStampDataCacher classes + + enables content caching, either at the generator or reader level + ''' + + def __init__(self, *args, **kwargs): + '''Initialize the generator, then set up caching + + note the multiple inheritance structure + ''' + cls_name = self.__class__.__name__ + Generator.__init__(self, *args, + readers_cache_name=(cls_name + '-Readers'), + **kwargs) + + cache_this_level = self.settings['CONTENT_CACHING_LAYER'] == 'generator' + caching_policy = cache_this_level and self.settings['CACHE_CONTENT'] + load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE'] + FileStampDataCacher.__init__(self, self.settings, cls_name, + caching_policy, load_policy + ) + + def _get_file_stamp(self, filename): + '''Get filestamp for path relative to generator.path''' + filename = os.path.join(self.path, filename) + return super(Generator, self)._get_file_stamp(filename) + + class _FileLoader(BaseLoader): def __init__(self, path, basedir): @@ -183,7 +209,7 @@ class TemplatePagesGenerator(Generator): del self.env.loader.loaders[0] -class ArticlesGenerator(Generator): +class ArticlesGenerator(CachingGenerator): """Generate blog articles""" def __init__(self, *args, **kwargs): @@ -537,6 +563,7 @@ class ArticlesGenerator(Generator): self._update_context(('articles', 'dates', 'tags', 'categories', 'tag_cloud', 'authors', 'related_posts')) self.save_cache() + self.readers.save_cache() signals.article_generator_finalized.send(self) def generate_output(self, writer): @@ -545,7 +572,7 @@ class ArticlesGenerator(Generator): signals.article_writer_finalized.send(self, writer=writer) -class PagesGenerator(Generator): +class PagesGenerator(CachingGenerator): """Generate pages""" def __init__(self, *args, **kwargs): @@ -599,6 +626,7 @@ class PagesGenerator(Generator): self.context['PAGES'] = self.pages self.save_cache() + self.readers.save_cache() signals.page_generator_finalized.send(self) def generate_output(self, writer): diff --git a/pelican/readers.py b/pelican/readers.py index fa9d92ae..c63b8981 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -33,7 +33,7 @@ except ImportError: from pelican import signals from pelican.contents import Page, Category, Tag, Author -from pelican.utils import get_date, pelican_open +from pelican.utils import get_date, pelican_open, FileStampDataCacher METADATA_PROCESSORS = { @@ -382,7 +382,7 @@ class AsciiDocReader(BaseReader): return content, metadata -class Readers(object): +class Readers(FileStampDataCacher): """Interface for all readers. This class contains a mapping of file extensions / Reader classes, to know @@ -392,7 +392,7 @@ class Readers(object): """ - def __init__(self, settings=None): + def __init__(self, settings=None, cache_name=''): self.settings = settings or {} self.readers = {} self.reader_classes = {} @@ -417,6 +417,15 @@ class Readers(object): self.readers[fmt] = reader_class(self.settings) + # set up caching + cache_this_level = (cache_name != '' and + self.settings['CONTENT_CACHING_LAYER'] == 'reader') + caching_policy = cache_this_level and self.settings['CACHE_CONTENT'] + load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE'] + super(Readers, self).__init__(settings, cache_name, + caching_policy, load_policy, + ) + @property def extensions(self): return self.readers.keys() @@ -455,7 +464,10 @@ class Readers(object): source_path=source_path, settings=self.settings, process=reader.process_metadata)) - content, reader_metadata = reader.read(path) + content, reader_metadata = self.get_cached_data(path, (None, None)) + if content is None: + content, reader_metadata = reader.read(path) + self.cache_data(path, (content, reader_metadata)) metadata.update(reader_metadata) if content: diff --git a/pelican/settings.py b/pelican/settings.py index 1d0ada0c..abf16b32 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -123,10 +123,12 @@ DEFAULT_CONFIG = { 'INTRASITE_LINK_REGEX': '[{|](?P.*?)[|}]', 'SLUGIFY_SOURCE': 'title', 'CACHE_CONTENT': True, + 'CONTENT_CACHING_LAYER': 'reader', 'CACHE_DIRECTORY': 'cache', 'GZIP_CACHE': True, 'CHECK_MODIFIED_METHOD': 'mtime', 'LOAD_CONTENT_CACHE': True, + 'AUTORELOAD_IGNORE_CACHE': False, 'WRITE_SELECTED': [], } @@ -266,6 +268,14 @@ def configure_settings(settings): if not 'FEED_DOMAIN' in settings: settings['FEED_DOMAIN'] = settings['SITEURL'] + # check content caching layer and warn of incompatibilities + if (settings.get('CACHE_CONTENT', False) and + settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and + settings.get('WITH_FUTURE_DATES', DEFAULT_CONFIG['WITH_FUTURE_DATES'])): + logger.warning('WITH_FUTURE_DATES conflicts with ' + "CONTENT_CACHING_LAYER set to 'generator', " + "use 'reader' layer instead") + # Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined feed_keys = [ 'FEED_ATOM', 'FEED_RSS', diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index f951f0cb..9463047e 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -288,10 +288,11 @@ class TestArticlesGenerator(unittest.TestCase): authors_expected = ['alexis-metaireau', 'first-author', 'second-author'] self.assertEqual(sorted(authors), sorted(authors_expected)) - def test_content_caching(self): - """Test that the articles are read only once when caching""" + def test_article_object_caching(self): + """Test Article objects caching at the generator level""" settings = get_settings(filenames={}) settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CONTENT_CACHING_LAYER'] = 'generator' settings['READERS'] = {'asc': None} generator = ArticlesGenerator( @@ -307,10 +308,32 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_context() generator.readers.read_file.assert_called_count == 0 - def test_full_rebuild(self): + def test_reader_content_caching(self): + """Test raw content caching at the reader level""" + settings = get_settings(filenames={}) + settings['CACHE_DIRECTORY'] = self.temp_cache + settings['READERS'] = {'asc': None} + + generator = ArticlesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + self.assertTrue(hasattr(generator.readers, '_cache')) + + generator = ArticlesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + readers = generator.readers.readers + for reader in readers.values(): + reader.read = MagicMock() + generator.generate_context() + for reader in readers.values(): + reader.read.assert_called_count == 0 + + def test_ignore_cache(self): """Test that all the articles are read again when not loading cache - used in --full-rebuild or autoreload mode""" + used in --ignore-cache or autoreload mode""" settings = get_settings(filenames={}) settings['CACHE_DIRECTORY'] = self.temp_cache settings['READERS'] = {'asc': None} @@ -376,30 +399,52 @@ class TestPageGenerator(unittest.TestCase): self.assertEqual(sorted(pages_expected), sorted(pages)) self.assertEqual(sorted(hidden_pages_expected), sorted(hidden_pages)) - def test_content_caching(self): - """Test that the pages are read only once when caching""" + def test_page_object_caching(self): + """Test Page objects caching at the generator level""" settings = get_settings(filenames={}) - settings['CACHE_DIRECTORY'] = 'cache_dir' #TODO settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CONTENT_CACHING_LAYER'] = 'generator' settings['READERS'] = {'asc': None} generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) generator.generate_context() self.assertTrue(hasattr(generator, '_cache')) generator = PagesGenerator( context=settings.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) generator.readers.read_file = MagicMock() generator.generate_context() generator.readers.read_file.assert_called_count == 0 - def test_full_rebuild(self): + def test_reader_content_caching(self): + """Test raw content caching at the reader level""" + settings = get_settings(filenames={}) + settings['CACHE_DIRECTORY'] = self.temp_cache + settings['READERS'] = {'asc': None} + + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + self.assertTrue(hasattr(generator.readers, '_cache')) + + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + readers = generator.readers.readers + for reader in readers.values(): + reader.read = MagicMock() + generator.generate_context() + for reader in readers.values(): + reader.read.assert_called_count == 0 + + def test_ignore_cache(self): """Test that all the pages are read again when not loading cache - used in --full-rebuild or autoreload mode""" + used in --ignore_cache or autoreload mode""" settings = get_settings(filenames={}) settings['CACHE_DIRECTORY'] = self.temp_cache settings['READERS'] = {'asc': None} diff --git a/pelican/utils.py b/pelican/utils.py index cda3108e..7b58a231 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -552,28 +552,30 @@ def split_all(path): class FileDataCacher(object): '''Class that can cache data contained in files''' - def __init__(self, settings, cache_policy_key, load_policy_key): - '''Load the specified cache within CACHE_DIRECTORY + def __init__(self, settings, cache_name, caching_policy, load_policy): + '''Load the specified cache within CACHE_DIRECTORY in settings - only if load_policy_key in setttings is True, - May use gzip if GZIP_CACHE. - Sets caching policy according to *cache_policy_key* - in *settings* + only if *load_policy* is True, + May use gzip if GZIP_CACHE ins settings is True. + Sets caching policy according to *caching_policy*. ''' self.settings = settings - name = self.__class__.__name__ - self._cache_path = os.path.join(self.settings['CACHE_DIRECTORY'], name) - self._cache_data_policy = self.settings[cache_policy_key] + self._cache_path = os.path.join(self.settings['CACHE_DIRECTORY'], + cache_name) + self._cache_data_policy = caching_policy if self.settings['GZIP_CACHE']: import gzip self._cache_open = gzip.open else: self._cache_open = open - if self.settings[load_policy_key]: + if load_policy: try: - with self._cache_open(self._cache_path, 'rb') as f: - self._cache = pickle.load(f) - except Exception as e: + with self._cache_open(self._cache_path, 'rb') as fhandle: + self._cache = pickle.load(fhandle) + except (IOError, OSError, pickle.UnpicklingError) as err: + logger.warning(('Cannot load cache {}, ' + 'proceeding with empty cache.\n{}').format( + self._cache_path, err)) self._cache = {} else: self._cache = {} @@ -583,7 +585,7 @@ class FileDataCacher(object): if self._cache_data_policy: self._cache[filename] = data - def get_cached_data(self, filename, default={}): + def get_cached_data(self, filename, default=None): '''Get cached data for the given file if no data is cached, return the default object @@ -595,20 +597,23 @@ class FileDataCacher(object): if self._cache_data_policy: try: mkdir_p(self.settings['CACHE_DIRECTORY']) - with self._cache_open(self._cache_path, 'wb') as f: - pickle.dump(self._cache, f) - except Exception as e: + with self._cache_open(self._cache_path, 'wb') as fhandle: + pickle.dump(self._cache, fhandle) + except (IOError, OSError, pickle.PicklingError) as err: logger.warning('Could not save cache {}\n{}'.format( - self._cache_path, e)) + self._cache_path, err)) class FileStampDataCacher(FileDataCacher): '''Subclass that also caches the stamp of the file''' - def __init__(self, settings, cache_policy_key, load_policy_key): - '''This sublcass additionaly sets filestamp function''' - super(FileStampDataCacher, self).__init__(settings, cache_policy_key, - load_policy_key) + def __init__(self, settings, cache_name, caching_policy, load_policy): + '''This sublcass additionaly sets filestamp function + and base path for filestamping operations + ''' + super(FileStampDataCacher, self).__init__(settings, cache_name, + caching_policy, + load_policy) method = self.settings['CHECK_MODIFIED_METHOD'] if method == 'mtime': @@ -616,10 +621,14 @@ class FileStampDataCacher(FileDataCacher): else: try: hash_func = getattr(hashlib, method) - def filestamp_func(buf): - return hash_func(buf).digest() + def filestamp_func(filename): + '''return hash of file contents''' + with open(filename, 'rb') as fhandle: + return hash_func(fhandle.read()).digest() self._filestamp_func = filestamp_func - except ImportError: + except AttributeError as err: + logger.warning('Could not get hashing function\n{}'.format( + err)) self._filestamp_func = None def cache_data(self, filename, data): @@ -636,11 +645,11 @@ class FileStampDataCacher(FileDataCacher): a hash for a function name in the hashlib module or an empty bytes string otherwise ''' - filename = os.path.join(self.path, filename) try: - with open(filename, 'rb') as f: - return self._filestamp_func(f.read()) - except Exception: + return self._filestamp_func(filename) + except (IOError, OSError, TypeError) as err: + logger.warning('Cannot get modification stamp for {}\n{}'.format( + filename, err)) return b'' def get_cached_data(self, filename, default=None): @@ -648,7 +657,7 @@ class FileStampDataCacher(FileDataCacher): if the file has not been modified. If no record exists or file has been modified, return default. - Modification is checked by compaing the cached + Modification is checked by comparing the cached and current file stamp. ''' stamp, data = super(FileStampDataCacher, self).get_cached_data( From de3ed6c1ef6e13203d2fbdafb8bf7b06885114ed Mon Sep 17 00:00:00 2001 From: Bernhard Scheirle Date: Thu, 24 Apr 2014 15:30:34 +0200 Subject: [PATCH 49/82] send the static_generator_{init, finalized} signals. Note: The two signals were already present but were never sent. --- docs/plugins.rst | 8 ++++++++ pelican/generators.py | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/docs/plugins.rst b/docs/plugins.rst index 16d697fa..7fe497e6 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -87,8 +87,16 @@ get_generators generators invoked in Pe can return a Generator, or several generator in a tuple or in a list. page_generator_context page_generator, metadata +page_generator_preread page_generator invoked before a page is read in PageGenerator.generate_context; + 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 +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 + staticfiles list. +static_generator_init static_generator invoked in the StaticGenerator.__init__ +static_generator_finalized static_generator invoked at the end of StaticGenerator.generate_context content_object_init content_object invoked at the end of Content.__init__ (see note below) content_written path, context invoked each time a content file is written. ================================= ============================ =========================================================================== diff --git a/pelican/generators.py b/pelican/generators.py index 3cc84fa8..dc16a146 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -643,6 +643,10 @@ class StaticGenerator(Generator): """copy static paths (what you want to copy, like images, medias etc. to output""" + def __init__(self, *args, **kwargs): + super(StaticGenerator, self).__init__(*args, **kwargs) + signals.static_generator_init.send(self) + def _copy_paths(self, paths, source, destination, output_path, final_path=None): """Copy all the paths from source to destination""" @@ -671,6 +675,7 @@ class StaticGenerator(Generator): self.staticfiles.append(static) self.add_source_path(static) self._update_context(('staticfiles',)) + signals.static_generator_finalized.send(self) def generate_output(self, writer): self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, From a03881fd97e5f1a667d782eefff1c2624aa3cc49 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Fri, 25 Apr 2014 19:44:26 +0200 Subject: [PATCH 50/82] use correct CachingGenerator class in super() call This was a leftover from code moving in c1324b0. Detected by pylint. --- pelican/generators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/generators.py b/pelican/generators.py index dc16a146..7c6ba66b 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -176,7 +176,7 @@ class CachingGenerator(Generator, FileStampDataCacher): def _get_file_stamp(self, filename): '''Get filestamp for path relative to generator.path''' filename = os.path.join(self.path, filename) - return super(Generator, self)._get_file_stamp(filename) + return super(CachingGenerator, self)._get_file_stamp(filename) class _FileLoader(BaseLoader): From 3b7189ec8a24e13f41288756afd4eae9513ce34a Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 27 Apr 2014 14:25:08 +0200 Subject: [PATCH 51/82] add get_writer signal and unify with get_generators Fix outdated docs of get_generators to unify. --- docs/plugins.rst | 10 ++++++---- pelican/__init__.py | 15 ++++++++++++++- pelican/signals.py | 1 + 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index 7fe497e6..717019a8 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -83,9 +83,11 @@ article_generator_finalized article_generator invoked at th article_generator_write_article article_generator, content invoked before writing each article, the article is passed as content article_writer_finalized article_generator, writer invoked after all articles and related pages have been written, but before the article generator is closed. -get_generators generators invoked in Pelican.get_generator_classes, +get_generators pelican object invoked in Pelican.get_generator_classes, can return a Generator, or several - generator in a tuple or in a list. + generators in a tuple or in a list. +get_writer pelican object invoked in Pelican.get_writer, + can return a custom Writer. page_generator_context page_generator, metadata page_generator_preread page_generator invoked before a page is read in PageGenerator.generate_context; use if code needs to do something before every page is parsed. @@ -200,8 +202,8 @@ Adding a new generator is also really easy. You might want to have a look at :: - def get_generators(generators): + def get_generators(pelican_object): # define a new generator here if you need to - return generators + return MyGenerator signals.get_generators.connect(get_generators) diff --git a/pelican/__init__.py b/pelican/__init__.py index 8cae468c..5208c317 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -209,7 +209,20 @@ class Pelican(object): return generators def get_writer(self): - return Writer(self.output_path, settings=self.settings) + writers = [ w for w in signals.get_writer.send(self) + if isinstance(w, type) ] + writers_found = len(writers) + if writers_found == 0: + return Writer(self.output_path, settings=self.settings) + else: + _, writer = writers[0] + if writers_found == 1: + logger.debug('Found writer: {}'.format(writer)) + else: + logger.warning( + '{} writers found, using only first one: {}'.format( + writers_found, writer)) + return writer(self.output_path, settings=self.settings) def parse_arguments(): diff --git a/pelican/signals.py b/pelican/signals.py index 9eb907dc..e06b89ac 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') +get_writer = signal('get_writer') finalized = signal('pelican_finalized') # Reader-level signals From b3fe098c00989f55fd5e80b64d6c0ad44ad012fc Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Mon, 28 Apr 2014 20:37:49 +0200 Subject: [PATCH 52/82] Fix #1311 wide tables in RTD theme, remove old theme files This works by adding a CSS overrides file to the Sphinx app stylesheets. --- docs/_static/pelican.gif | Bin 16941 -> 0 bytes docs/_static/pelican.png | Bin 6441 -> 0 bytes docs/_static/theme_overrides.css | 12 + docs/_themes/.gitignore | 3 - docs/_themes/pelican/layout.html | 22 -- docs/_themes/pelican/static/pelican.css_t | 254 ---------------------- docs/_themes/pelican/theme.conf | 10 - docs/conf.py | 6 + 8 files changed, 18 insertions(+), 289 deletions(-) delete mode 100644 docs/_static/pelican.gif delete mode 100644 docs/_static/pelican.png create mode 100644 docs/_static/theme_overrides.css delete mode 100644 docs/_themes/.gitignore delete mode 100644 docs/_themes/pelican/layout.html delete mode 100644 docs/_themes/pelican/static/pelican.css_t delete mode 100644 docs/_themes/pelican/theme.conf diff --git a/docs/_static/pelican.gif b/docs/_static/pelican.gif deleted file mode 100644 index d9208590b7cce4c7f9284dfc4686b940eb1912bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16941 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%0DPy!XW zK`zk!N_@kizdbUkd`j%YEx a=5G={=Xbv6e?I7kzUYrW>Hkqc0028+la#Cg diff --git a/docs/_static/pelican.png b/docs/_static/pelican.png deleted file mode 100644 index c2d61c88997a3b853a1afec2aa2e2ce84102fbf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6441 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 j4}To;|DHdOxViP7=V+AmQfdJFje+x2O - {% endif %} -{% endblock %} -{% block footer %} - {% if pagename == 'index' %} - - {% endif %} -{% endblock %} -{# do not display relbars #} -{% block relbar1 %}{% endblock %} -{% block relbar2 %} - {% if theme_github_fork %} - Fork me on GitHub - {% endif %} -{% endblock %} -{% block sidebar1 %}{% endblock %} -{% block sidebar2 %}{% endblock %} diff --git a/docs/_themes/pelican/static/pelican.css_t b/docs/_themes/pelican/static/pelican.css_t deleted file mode 100644 index 3cb2a3c1..00000000 --- a/docs/_themes/pelican/static/pelican.css_t +++ /dev/null @@ -1,254 +0,0 @@ -/* - * pelican.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- pelican theme, based on the nature theme - * - * :copyright: Copyright 2011 by Alexis Metaireau. - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Arial, sans-serif; - font-size: 100%; - background-color: white; - color: #555; - margin: 0; - padding: 0; -} - -div.documentwrapper { - width: 70%; - margin: auto; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.document { -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; - font-size: 0.9em; -} - -div.footer { - color: #555; - width: 100%; - padding: 13px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #444; - text-decoration: underline; -} - -div.related { - background-color: #6BA81E; - line-height: 32px; - color: #fff; - text-shadow: 0px 1px 0 #444; - font-size: 0.9em; -} - -div.related a { - color: #E2F3CC; -} - -div.sphinxsidebar { - font-size: 0.75em; - line-height: 1.5em; -} - -div.sphinxsidebarwrapper{ - padding: 20px 0; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Arial, sans-serif; - color: #222; - font-size: 1.2em; - font-weight: normal; - margin: 0; - padding: 5px 10px; - background-color: #ddd; - text-shadow: 1px 1px 0 white -} - -div.sphinxsidebar h4{ - font-size: 1.1em; -} - -div.sphinxsidebar h3 a { - color: #444; -} - - -div.sphinxsidebar p { - color: #888; - padding: 5px 20px; -} - -div.sphinxsidebar p.topless { -} - -div.sphinxsidebar ul { - margin: 10px 20px; - padding: 0; - color: #000; -} - -div.sphinxsidebar a { - color: #444; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar input[type=text]{ - margin-left: 20px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #005B81; - text-decoration: none; -} - -a:hover { - color: #E32E00; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Arial, sans-serif; - font-weight: normal; - color: #212224; - margin: 30px 0px 10px 0px; - padding: 5px 0 5px 10px; - text-shadow: 0px 1px 0 white -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} -div.body h1 { - border-top: 20px solid white; - margin-top: 0; - font-size: 250%; - text-align: center; -} - -div.body h2 { font-size: 150%; background-color: #C8D5E3; } -div.body h3 { font-size: 120%; background-color: #D8DEE3; } -div.body h4 { font-size: 110%; background-color: #D8DEE3; } -div.body h5 { font-size: 100%; background-color: #D8DEE3; } -div.body h6 { font-size: 100%; background-color: #D8DEE3; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - line-height: 1.5em; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.highlight{ - background-color: #111; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 10px; - background-color: #111; - color: #fff; - line-height: 1.2em; - border: 1px solid #C6C9CB; - font-size: 1.1em; - margin: 1.5em 0 1.5em 0; - -webkit-box-shadow: 1px 1px 1px #d8d8d8; - -moz-box-shadow: 1px 1px 1px #d8d8d8; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ - font-size: 1.1em; - font-family: monospace; -} - -.viewcode-back { - font-family: Arial, sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} diff --git a/docs/_themes/pelican/theme.conf b/docs/_themes/pelican/theme.conf deleted file mode 100644 index ffbb7945..00000000 --- a/docs/_themes/pelican/theme.conf +++ /dev/null @@ -1,10 +0,0 @@ -[theme] -inherit = basic -stylesheet = pelican.css -nosidebar = true -pygments_style = fruity - -[options] -index_logo_height = 120px -index_logo = -github_fork = diff --git a/docs/conf.py b/docs/conf.py index 99acd1b6..d4efcb38 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,6 +57,12 @@ html_use_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = False + +def setup(app): + # overrides for wide tables in RTD theme + app.add_stylesheet('theme_overrides.css') # path relative to _static + + # -- Options for LaTeX output -------------------------------------------------- latex_documents = [ ('index', 'Pelican.tex', 'Pelican Documentation', From 9d5b2e9489109498421cf89858b5e5c0e6f101c9 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Mon, 28 Apr 2014 21:39:20 +0200 Subject: [PATCH 53/82] Really fix #1311 by declaring CSS overrides as !important this is needed because on RTD the common hosted theme stylesheets get added after the overrides. --- docs/_static/theme_overrides.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css index d4b31645..83afc78e 100644 --- a/docs/_static/theme_overrides.css +++ b/docs/_static/theme_overrides.css @@ -1,12 +1,12 @@ /* override table width restrictions */ .wy-table-responsive table td, .wy-table-responsive table th { - white-space: normal; + /* !important prevents the common CSS stylesheets from + overriding this as on RTD they are loaded after this stylesheet */ + white-space: normal !important; } .wy-table-responsive { - margin-bottom: 24px; - max-width: 100%; - overflow: visible; + overflow: visible !important; } From 91d4202a68e730e09e40cd0ff87c4226b44a0bf6 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Mon, 28 Apr 2014 21:47:18 +0200 Subject: [PATCH 54/82] Fix #1277 use rsync -c option as all output is rewritten Because Pelican always rewrites all output, the mtimes always change, so rsync would always transfer all the files which defeats the purpose of rsync. The '-c' option (--checksum) compares the checksums rather than mtimes. --- pelican/tools/templates/Makefile.in | 2 +- pelican/tools/templates/fabfile.py.in | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index c542e588..6bfa7b0c 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -88,7 +88,7 @@ ssh_upload: publish scp -P $$(SSH_PORT) -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR) rsync_upload: publish - rsync -e "ssh -p $(SSH_PORT)" -P -rvz --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude + rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude dropbox_upload: publish cp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR) diff --git a/pelican/tools/templates/fabfile.py.in b/pelican/tools/templates/fabfile.py.in index fb56ae85..fbd03e2c 100644 --- a/pelican/tools/templates/fabfile.py.in +++ b/pelican/tools/templates/fabfile.py.in @@ -68,5 +68,6 @@ def publish(): remote_dir=dest_path, exclude=".DS_Store", local_dir=DEPLOY_PATH.rstrip('/') + '/', - delete=True + delete=True, + extra_opts='-c', ) From 4c1009e59c988f995128b2ad1baff0fd1f4db4e7 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 30 Apr 2014 06:44:55 -0700 Subject: [PATCH 55/82] Add Python 3.4 to Travic CI configuration --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 93a7ab54..41ad82b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: python python: - "2.7" - "3.3" + - "3.4" before_install: - sudo apt-get update -qq - sudo apt-get install -qq --no-install-recommends asciidoc From 01a0e727e368d30457340639756392ca296cfe4e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 30 Apr 2014 12:35:13 -0700 Subject: [PATCH 56/82] Show setting defaults as actual code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For some reason, setting names on the Settings page have long been wrapped in single back-ticks (usually meant for linking in reST) instead of double back-ticks (meant for denoting code). This seems to be widespread throughout the docs, and it's not clear if this is intentional or simply a reST formatting error that got propagated by others in order to stay consistent. This commit applies double back-ticks in any case where something resembling code is shown, with the idea that single back-ticks should only be used when linking. More importantly, the settings denoted their default values in parentheses, which hapless users often included when copying and pasting these values into their config files. As one can imagine, confusion — not hilarity — ensued. Setting defaults are now shown as they would actually appear in one's settings file, with an equal sign and without parentheses. During this spelunking expedition, many other minor improvements were concurrently conducted. --- docs/settings.rst | 422 +++++++++++++++++++++++----------------------- 1 file changed, 210 insertions(+), 212 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 1b4bae94..c9eddfba 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -6,8 +6,8 @@ the command line:: $ pelican content -s path/to/your/settingsfile.py -(If you used the `pelican-quickstart` command, your primary settings file will -be named `pelicanconf.py` by default.) +(If you used the ``pelican-quickstart`` command, your primary settings file will +be named ``pelicanconf.py`` by default.) Settings are configured in the form of a Python module (a file). There is an `example settings file @@ -32,33 +32,33 @@ Basic settings ============== =============================================================================== ===================================================================== -Setting name (default value) What does it do? +Setting name (followed by default value, if any) What does it do? =============================================================================== ===================================================================== -`AUTHOR` Default author (put your name) -`DATE_FORMATS` (``{}``) If you manage multiple languages, you can set the date formatting +``AUTHOR`` Default author (put your name) +``DATE_FORMATS = {}`` If you manage multiple languages, you can set the date formatting here. See the "Date format and locale" section below for details. -`USE_FOLDER_AS_CATEGORY` (``True``) When you don't specify a category in your post metadata, set this +``USE_FOLDER_AS_CATEGORY = True`` When you don't specify a category in your post metadata, set this setting to ``True``, and organize your articles in subfolders, the subfolder will become the category of your post. If set to ``False``, ``DEFAULT_CATEGORY`` will be used as a fallback. -`DEFAULT_CATEGORY` (``'misc'``) The default category to fall back on. -`DEFAULT_DATE_FORMAT` (``'%a %d %B %Y'``) The default date format you want to use. -`DISPLAY_PAGES_ON_MENU` (``True``) Whether to display pages on the menu of the +``DEFAULT_CATEGORY = 'misc'`` The default category to fall back on. +``DEFAULT_DATE_FORMAT = '%a %d %B %Y'`` The default date format you want to use. +``DISPLAY_PAGES_ON_MENU = True`` Whether to display pages on the menu of the template. Templates may or may not honor this setting. -`DISPLAY_CATEGORIES_ON_MENU` (``True``) Whether to display categories on the menu of the +``DISPLAY_CATEGORIES_ON_MENU = True`` Whether to display categories on the menu of the template. Templates may or not honor this setting. -`DEFAULT_DATE` (``None``) The default date you want to use. +``DEFAULT_DATE = None`` The default date you want to use. 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 be generated by passing the tuple to the ``datetime.datetime`` constructor. -`DEFAULT_METADATA` (``()``) The default metadata you want to use for all articles +``DEFAULT_METADATA = ()`` The default metadata you want to use for all articles and pages. -`FILENAME_METADATA` (``'(?P\d{4}-\d{2}-\d{2}).*'``) The regexp that will be used to extract any metadata +``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. The default value will only extract the date from @@ -67,38 +67,38 @@ Setting name (default value) date and the slug, you could set something like: ``'(?P\d{4}-\d{2}-\d{2})_(?P.*)'``. See :ref:`path_metadata`. -`PATH_METADATA` (``''``) Like ``FILENAME_METADATA``, but parsed from a page's +``PATH_METADATA = ''`` Like ``FILENAME_METADATA``, but parsed from a page's full path relative to the content source directory. See :ref:`path_metadata`. -`EXTRA_PATH_METADATA` (``{}``) Extra metadata dictionaries keyed by relative path. +``EXTRA_PATH_METADATA = {}`` Extra metadata dictionaries keyed by relative path. See :ref:`path_metadata`. -`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory, and **all** of its contents, before +``DELETE_OUTPUT_DIRECTORY = False`` Delete the output directory, and **all** of its contents, before 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 tuple 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")`` -`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_EXTENSIONS = []`` A list of any Jinja2 extensions you want to use. +``JINJA_FILTERS = {}`` A list 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`_. -`LOCALE` (''[#]_) Change the locale. A list of locales can be provided +``LOCALE`` [#]_ Change the locale. A list of locales can be provided here or a single string representing one locale. When providing a list, all the locales will be tried until one works. -`LOG_FILTER` (``[]``) A list of tuples containing the logging level (up to ``warning``) +``LOG_FILTER = []`` A list of tuples containing the logging level (up to ``warning``) and the message to be ignored. For example: ``[(logging.WARN, 'TAG_SAVE_AS is set to False')]`` -`READERS` (``{}``) A dictionary of file extensions / Reader classes for Pelican to +``READERS = {}`` A dictionary of file extensions / Reader classes for Pelican to process or ignore. For example, to avoid processing .html files, set: ``READERS = {'html': None}``. To add a custom reader for the `foo` extension, set: ``READERS = {'foo': FooReader}`` -`IGNORE_FILES` (``['.#*']``) A list of file globbing patterns to match against the +``IGNORE_FILES = ['.#*']`` A list of file globbing patterns to match against the source files to be ignored by the processor. For example, the default ``['.#*']`` will ignore emacs lock files. -`MD_EXTENSIONS` (``['codehilite(css_class=highlight)','extra']``) A list of the extensions that the Markdown processor +``MD_EXTENSIONS =`` ``['codehilite(css_class=highlight)','extra']`` A list of the extensions that the Markdown processor will use. Refer to the Python Markdown documentation's `Extensions section `_ for a complete list of supported extensions. (Note that @@ -107,87 +107,87 @@ Setting name (default value) to the default values for this setting, you'll need to include them explicitly and enumerate the full list of desired Markdown extensions.) -`OUTPUT_PATH` (``'output/'``) Where to output the generated files. -`PATH` (``None``) Path to content directory to be processed by Pelican. -`PAGE_DIR` (``'pages'``) Directory to look at for pages, relative to `PATH`. -`PAGE_EXCLUDES` (``()``) A list of directories to exclude when looking for pages. -`ARTICLE_DIR` (``''``) Directory to look at for articles, relative to `PATH`. -`ARTICLE_EXCLUDES`: (``('pages',)``) A list of directories to exclude when looking for articles. -`OUTPUT_SOURCES` (``False``) Set to True if you want to copy the articles and pages in their +``OUTPUT_PATH = 'output/'`` Where to output the generated files. +``PATH = None`` Path to content directory to be processed by Pelican. +``PAGE_DIR = 'pages'`` Directory to look at for pages, relative to `PATH`. +``PAGE_EXCLUDES = ()`` A list of directories to exclude when looking for pages. +``ARTICLE_DIR = ''`` Directory to look at for articles, relative to `PATH`. +``ARTICLE_EXCLUDES` = ('pages',)`` A list of directories to exclude when looking for articles. +``OUTPUT_SOURCES = False`` Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the specified ``OUTPUT_PATH``. -`OUTPUT_SOURCES_EXTENSION` (``.text``) Controls the extension that will be used by the SourcesGenerator. +``OUTPUT_SOURCES_EXTENSION = '.text'`` Controls the extension that will be used by the SourcesGenerator. Defaults to ``.text``. If not a valid string the default value will be used. -`RELATIVE_URLS` (``False``) Defines whether Pelican should use document-relative URLs or +``RELATIVE_URLS = False`` Defines whether Pelican should use document-relative URLs or not. Only set this to ``True`` when developing/testing and only if you fully understand the effect it can have on links/feeds. -`PLUGINS` (``[]``) The list of plugins to load. See :ref:`plugins`. -`SITENAME` (``'A Pelican Blog'``) Your site name -`SITEURL` Base URL of your website. Not defined by default, +``PLUGINS = []`` The list of plugins to load. See :ref:`plugins`. +``SITENAME = 'A Pelican Blog'`` Your site name +``SITEURL`` Base URL of your website. Not defined by default, so it is best to specify your SITEURL; if you do not, feeds will not be generated with properly-formed URLs. You should include ``http://`` and your domain, with no trailing slash at the end. Example: ``SITEURL = 'http://mydomain.com'`` -`TEMPLATE_PAGES` (``None``) A mapping containing template pages that will be rendered with +``TEMPLATE_PAGES = None`` A mapping containing template pages that will be rendered with the blog entries. See :ref:`template_pages`. -`STATIC_PATHS` (``['images']``) The static paths you want to have accessible +``STATIC_PATHS = ['images']`` The static paths you want to have accessible on the output path "static". By default, Pelican will copy the "images" folder to the output folder. -`TIMEZONE` The timezone used in the date information, to +``TIMEZONE`` The timezone used in the date information, to generate Atom and RSS feeds. See the *Timezone* section below for more info. -`TYPOGRIFY` (``False``) If set to True, several typographical improvements will be +``TYPOGRIFY = False`` If set to True, several typographical improvements will be incorporated into the generated HTML via the `Typogrify `_ library, which can be installed via: ``pip install typogrify`` -`DIRECT_TEMPLATES` (``('index', 'tags', '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. -`SUMMARY_MAX_LENGTH` (``50``) When creating a short summary of an article, this will +``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 specify a summary. Setting to ``None`` will cause the summary to be a copy of the original content. -`EXTRA_TEMPLATES_PATHS` (``[]``) A list of paths you want Jinja2 to search for templates. +``EXTRA_TEMPLATES_PATHS = []`` A list of paths you want Jinja2 to search for templates. Can be used to separate templates from the theme. Example: projects, resume, profile ... These templates need to use ``DIRECT_TEMPLATES`` setting. -`ASCIIDOC_OPTIONS` (``[]``) A list of options to pass to AsciiDoc. See the `manpage - `_ -`WITH_FUTURE_DATES` (``True``) If disabled, content with dates in the future will get a - default status of ``draft``. - see :ref:`reading_only_modified_content` for details. -`INTRASITE_LINK_REGEX` (``'[{|](?P.*?)[|}]'``) Regular expression that is used to parse internal links. - Default syntax of links to internal files, tags, etc., is - to enclose the identifier, say ``filename``, in ``{}`` or ``||``. - Identifier between ``{`` and ``}`` goes into the ``what`` capturing group. +``ASCIIDOC_OPTIONS = []`` A list of options to pass to AsciiDoc. See the `manpage + `_. +``WITH_FUTURE_DATES = True`` If disabled, content with dates in the future will get a default + status of ``draft``. See :ref:`reading_only_modified_content` + for caveats. +``INTRASITE_LINK_REGEX = '[{|](?P.*?)[|}]'`` Regular expression that is used to parse internal links. Default + syntax when linking to internal files, tags, etc., is to enclose + the identifier, say ``filename``, in ``{}`` or ``||``. Identifier + between ``{`` and ``}`` goes into the ``what`` capturing group. For details see :ref:`ref-linking-to-internal-content`. -`PYGMENTS_RST_OPTIONS` (``[]``) A list of default Pygments settings for your reStructuredText +``PYGMENTS_RST_OPTIONS = []`` A list of default Pygments settings for your reStructuredText code blocks. See :ref:`internal_pygments_options` for a list of supported options. - -`SLUGIFY_SOURCE` (``'input'``) Specifies where you want the slug to be automatically generated - from. Can be set to 'title' to use the 'Title:' metadata tag or - 'basename' to use the articles basename when creating the slug. -`CACHE_CONTENT` (``True``) If ``True``, save content in a cache file. +``SLUGIFY_SOURCE = 'input'`` Specifies where you want the slug to be automatically generated + from. Can be set to ``title`` to use the 'Title:' metadata tag or + ``basename`` to use the article's basename when creating the slug. +``CACHE_CONTENT = True`` If ``True``, save content in a cache file. See :ref:`reading_only_modified_content` for details about caching. -`CONTENT_CACHING_LAYER` (``'reader'``) If set to ``'reader'``, save only the raw content and metadata returned - by readers, if set to ``'generator'``, save processed content objects. -`CACHE_DIRECTORY` (``cache``) Directory in which to store cache files. -`GZIP_CACHE` (``True``) If ``True``, use gzip to (de)compress the cache files. -`CHECK_MODIFIED_METHOD` (``mtime``) Controls how files are checked for modifications. -`LOAD_CONTENT_CACHE` (``True``) If ``True``, load unmodified content from cache. -`AUTORELOAD_IGNORE_CACHE` (``False``) If ``True``, do not load content cache in autoreload mode +``CONTENT_CACHING_LAYER = 'reader'`` If set to ``'reader'``, save only the raw content and metadata + returned by readers. If set to ``'generator'``, save processed + content objects. +``CACHE_DIRECTORY = 'cache'`` Directory in which to store cache files. +``GZIP_CACHE = True`` If ``True``, use gzip to (de)compress the cache files. +``CHECK_MODIFIED_METHOD = 'mtime'`` Controls how files are checked for modifications. +``LOAD_CONTENT_CACHE = True`` If ``True``, load unmodified content from cache. +``AUTORELOAD_IGNORE_CACHE = False`` If ``True``, do not load content cache in autoreload mode when the settings file changes. -`WRITE_SELECTED` (``[]``) If this list is not empty, **only** output files with their paths - in this list are written. Paths should be either relative to the current - working directory of Pelican or absolute. For possible use cases see - :ref:`writing_only_selected_content`. +``WRITE_SELECTED = []`` If this list is not empty, **only** output files with their paths + 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`. =============================================================================== ===================================================================== .. [#] Default is the system locale. @@ -231,8 +231,8 @@ Also, you can use other file metadata attributes as well: Example usage: -* ARTICLE_URL = ``'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/'`` -* ARTICLE_SAVE_AS = ``'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html'`` +* ``ARTICLE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/'`` +* ``ARTICLE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html'`` This would save your articles in something like ``/posts/2011/Aug/07/sample-post/index.html``, and the URL to this would be ``/posts/2011/Aug/07/sample-post/``. @@ -245,8 +245,8 @@ make it easier for readers to navigate through the posts you've written over tim Example usage: -* YEAR_ARCHIVE_SAVE_AS = ``'posts/{date:%Y}/index.html'`` -* MONTH_ARCHIVE_SAVE_AS = ``'posts/{date:%Y}/{date:%b}/index.html'`` +* ``YEAR_ARCHIVE_SAVE_AS = 'posts/{date:%Y}/index.html'`` +* ``MONTH_ARCHIVE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/index.html'`` With these settings, Pelican will create an archive of all your posts for the year at (for instance) ``posts/2011/index.html`` and an archive of all your @@ -258,43 +258,43 @@ posts for the month at ``posts/2011/Aug/index.html``. arrive at an appropriate archive of posts, without having to specify a page name. -====================================================== ===================================================== -Setting name (default value) What does it do? -====================================================== ===================================================== -`ARTICLE_URL` (``'{slug}.html'``) The URL to refer to an article. -`ARTICLE_SAVE_AS` (``'{slug}.html'``) The place where we will save an article. -`ARTICLE_LANG_URL` (``'{slug}-{lang}.html'``) The URL to refer to an article which doesn't use the +====================================================== ======================================================== +Setting name (followed by default value, if any) What does it do? +====================================================== ======================================================== +``ARTICLE_URL = '{slug}.html'`` The URL to refer to an article. +``ARTICLE_SAVE_AS = '{slug}.html'`` The place where we will save an article. +``ARTICLE_LANG_URL = '{slug}-{lang}.html'`` The URL to refer to an article which doesn't use the default language. -`ARTICLE_LANG_SAVE_AS` (``'{slug}-{lang}.html'``) The place where we will save an article which +``ARTICLE_LANG_SAVE_AS = '{slug}-{lang}.html'`` The place where we will save an article which doesn't use the default language. -`DRAFT_URL` (``'drafts/{slug}.html'``) The URL to refer to an article draft. -`DRAFT_SAVE_AS` (``'drafts/{slug}.html'``) The place where we will save an article draft. -`DRAFT_LANG_URL` (``'drafts/{slug}-{lang}.html'``) The URL to refer to an article draft which doesn't +``DRAFT_URL = 'drafts/{slug}.html'`` The URL to refer to an article draft. +``DRAFT_SAVE_AS = 'drafts/{slug}.html'`` The place where we will save an article draft. +``DRAFT_LANG_URL = 'drafts/{slug}-{lang}.html'`` The URL to refer to an article draft which doesn't use the default language. -`DRAFT_LANG_SAVE_AS` (``'drafts/{slug}-{lang}.html'``) The place where we will save an article draft which +``DRAFT_LANG_SAVE_AS = 'drafts/{slug}-{lang}.html'`` The place where we will save an article draft which doesn't use the default language. -`PAGE_URL` (``'pages/{slug}.html'``) The URL we will use to link to a page. -`PAGE_SAVE_AS` (``'pages/{slug}.html'``) The location we will save the page. This value has to be +``PAGE_URL = 'pages/{slug}.html'`` The URL we will use to link to a page. +``PAGE_SAVE_AS = 'pages/{slug}.html'`` The location we will save the page. This value has to be the same as PAGE_URL or you need to use a rewrite in your server config. -`PAGE_LANG_URL` (``'pages/{slug}-{lang}.html'``) The URL we will use to link to a page which doesn't +``PAGE_LANG_URL = 'pages/{slug}-{lang}.html'`` The URL we will use to link to a page which doesn't use the default language. -`PAGE_LANG_SAVE_AS` (``'pages/{slug}-{lang}.html'``) The location we will save the page which doesn't +``PAGE_LANG_SAVE_AS = 'pages/{slug}-{lang}.html'`` The location we will save the page which doesn't use the default language. -`CATEGORY_URL` (``'category/{slug}.html'``) The URL to use for a category. -`CATEGORY_SAVE_AS` (``'category/{slug}.html'``) The location to save a category. -`TAG_URL` (``'tag/{slug}.html'``) The URL to use for a tag. -`TAG_SAVE_AS` (``'tag/{slug}.html'``) The location to save the tag page. -`AUTHOR_URL` (``'author/{slug}.html'``) The URL to use for an author. -`AUTHOR_SAVE_AS` (``'author/{slug}.html'``) The location to save an author. -`YEAR_ARCHIVE_SAVE_AS` (``''``) The location to save per-year archives of your posts. -`MONTH_ARCHIVE_SAVE_AS` (``''``) The location to save per-month archives of your posts. -`DAY_ARCHIVE_SAVE_AS` (``''``) The location to save per-day archives of your posts. -`SLUG_SUBSTITUTIONS` (``()``) Substitutions to make prior to stripping out +``CATEGORY_URL = 'category/{slug}.html'`` The URL to use for a category. +``CATEGORY_SAVE_AS = 'category/{slug}.html'`` The location to save a category. +``TAG_URL = 'tag/{slug}.html'`` The URL to use for a tag. +``TAG_SAVE_AS = 'tag/{slug}.html'`` The location to save the tag page. +``AUTHOR_URL = 'author/{slug}.html'`` The URL to use for an author. +``AUTHOR_SAVE_AS = 'author/{slug}.html'`` The location to save an author. +``YEAR_ARCHIVE_SAVE_AS = ''`` The location to save per-year archives of your posts. +``MONTH_ARCHIVE_SAVE_AS = ''`` The location to save per-month archives of your posts. +``DAY_ARCHIVE_SAVE_AS = ''`` The location to save per-day archives of your posts. +``SLUG_SUBSTITUTIONS` = ()`` Substitutions to make prior to stripping out non-alphanumerics when generating slugs. Specified as a list of 2-tuples of ``(from, to)`` which are applied in order. -====================================================== ===================================================== +====================================================== ======================================================== .. note:: @@ -303,23 +303,21 @@ Setting name (default value) What does it do? set the corresponding ``*_SAVE_AS`` setting to ``''`` to prevent the relevant page from being generated. -`DIRECT_TEMPLATES` -~~~~~~~~~~~~~~~~~~ - -These templates (``('index', 'tags', 'categories', 'archives')`` by default) -works a bit differently than above. Only the ``_SAVE_AS`` setting is available: +``DIRECT_TEMPLATES``, which are ``('index', 'tags', 'categories', 'archives')`` +by default, work a bit differently than noted above. Only the ``_SAVE_AS`` +settings are available: ============================================= =============================================== -Setting name (default value) What does it do? +Setting name (followed by default value) What does it do? ============================================= =============================================== -`ARCHIVES_SAVE_AS` (``'archives.html'``) The location to save the article archives page. -`AUTHORS_SAVE_AS` (``'authors.html'``) The location to save the author list. -`CATEGORIES_SAVE_AS` (``'categories.html'``) The location to save the category list. -`TAGS_SAVE_AS` (``'tags.html'``) The location to save the tag list. +``ARCHIVES_SAVE_AS = 'archives.html'`` The location to save the article archives page. +``AUTHORS_SAVE_AS = 'authors.html'`` The location to save the author list. +``CATEGORIES_SAVE_AS = 'categories.html'`` The location to save the category list. +``TAGS_SAVE_AS = 'tags.html'`` The location to save the tag list. ============================================= =============================================== -The corresponding urls are hard-coded in the themes: ``'archives.html'``, -``'authors.html'``, ``'categories.html'``, ``'tags.html'``. +URLs for direct template pages are theme-dependent. Some themes hard-code them: +``'archives.html'``, ``'authors.html'``, ``'categories.html'``, ``'tags.html'``. Timezone -------- @@ -460,33 +458,33 @@ Pelican generates category feeds as well as feeds for all your articles. It does not generate feeds for tags by default, but it is possible to do so using the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings: -================================================ ===================================================== -Setting name (default value) What does it do? -================================================ ===================================================== -`FEED_DOMAIN` (``None``, i.e. base URL is "/") The domain prepended to feed URLs. Since feed URLs +================================================= ===================================================== +Setting name (followed by default value, if any) What does it do? +================================================= ===================================================== +``FEED_DOMAIN = None``, i.e. base URL is "/" The domain prepended to feed URLs. Since feed URLs should always be absolute, it is highly recommended to define this (e.g., "http://feeds.example.com"). If you have already explicitly defined SITEURL (see above) and want to use the same domain for your feeds, you can just set: ``FEED_DOMAIN = SITEURL``. -`FEED_ATOM` (``None``, i.e. no Atom feed) Relative URL to output the Atom feed. -`FEED_RSS` (``None``, i.e. no RSS) Relative URL to output the RSS feed. -`FEED_ALL_ATOM` (``'feeds/all.atom.xml'``) Relative URL to output the all posts Atom feed: +``FEED_ATOM = None``, i.e. no Atom feed Relative URL to output the Atom feed. +``FEED_RSS = None``, i.e. no RSS Relative URL to output the RSS feed. +``FEED_ALL_ATOM = 'feeds/all.atom.xml'`` Relative URL to output the all-posts Atom feed: this feed will contain all posts regardless of their language. -`FEED_ALL_RSS` (``None``, i.e. no all RSS) Relative URL to output the all posts RSS feed: +``FEED_ALL_RSS = None``, i.e. no all-posts RSS Relative URL to output the all-posts RSS feed: this feed will contain all posts regardless of their language. -`CATEGORY_FEED_ATOM` ('feeds/%s.atom.xml'[2]_) Where to put the category Atom feeds. -`CATEGORY_FEED_RSS` (``None``, i.e. no RSS) Where to put the category RSS feeds. -`AUTHOR_FEED_ATOM` ('feeds/%s.atom.xml'[2]_) Where to put the author Atom feeds. -`AUTHOR_FEED_RSS` ('feeds/%s.rss.xml'[2]_) Where to put the author RSS feeds. -`TAG_FEED_ATOM` (``None``, i.e. no tag feed) Relative URL to output the tag Atom feed. It should +``CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'`` [2]_ Where to put the category Atom feeds. +``CATEGORY_FEED_RSS = None``, i.e. no RSS Where to put the category RSS feeds. +``AUTHOR_FEED_ATOM = 'feeds/%s.atom.xml'`` [2]_ Where to put the author Atom feeds. +``AUTHOR_FEED_RSS = 'feeds/%s.rss.xml'`` [2]_ Where to put the author RSS feeds. +``TAG_FEED_ATOM = None``, i.e. no tag feed Relative URL to output the tag Atom feed. It should be defined using a "%s" match in the tag name. -`TAG_FEED_RSS` (``None``, ie no RSS tag feed) Relative URL to output the tag RSS feed -`FEED_MAX_ITEMS` Maximum number of items allowed in a feed. Feed item +``TAG_FEED_RSS = None``, i.e. no RSS tag feed Relative URL to output the tag RSS feed +``FEED_MAX_ITEMS`` Maximum number of items allowed in a feed. Feed item quantity is unrestricted by default. -================================================ ===================================================== +================================================= ===================================================== If you don't want to generate some or any of these feeds, set the above variables to ``None``. @@ -499,17 +497,17 @@ If you want to use FeedBurner for your feed, you will likely need to decide upon a unique identifier. For example, if your site were called "Thyme" and hosted on the www.example.com domain, you might use "thymefeeds" as your unique identifier, which we'll use throughout this section for illustrative -purposes. In your Pelican settings, set the `FEED_ATOM` attribute to -"thymefeeds/main.xml" to create an Atom feed with an original address of -`http://www.example.com/thymefeeds/main.xml`. Set the `FEED_DOMAIN` attribute -to `http://feeds.feedburner.com`, or `http://feeds.example.com` if you are -using a CNAME on your own domain (i.e., FeedBurner's "MyBrand" feature). +purposes. In your Pelican settings, set the ``FEED_ATOM`` attribute to +``thymefeeds/main.xml`` to create an Atom feed with an original address of +``http://www.example.com/thymefeeds/main.xml``. Set the ``FEED_DOMAIN`` +attribute to ``http://feeds.feedburner.com``, or ``http://feeds.example.com`` if +you are using a CNAME on your own domain (i.e., FeedBurner's "MyBrand" feature). There are two fields to configure in the `FeedBurner `_ interface: "Original Feed" and "Feed Address". In this example, the "Original Feed" would be -`http://www.example.com/thymefeeds/main.xml` and the "Feed Address" suffix -would be `thymefeeds/main.xml`. +``http://www.example.com/thymefeeds/main.xml`` and the "Feed Address" suffix +would be ``thymefeeds/main.xml``. Pagination ========== @@ -522,15 +520,15 @@ benefit from paginating this list. You can use the following settings to configure the pagination. ================================================ ===================================================== -Setting name (default value) What does it do? +Setting name (followed by default value, if any) What does it do? ================================================ ===================================================== -`DEFAULT_ORPHANS` (``0``) The minimum number of articles allowed on the +``DEFAULT_ORPHANS = 0`` The minimum number of articles allowed on the last page. Use this when you don't want the last page to only contain a handful of articles. -`DEFAULT_PAGINATION` (``False``) The maximum number of articles to include on a +``DEFAULT_PAGINATION = False`` The maximum number of articles to include on a page, not including orphans. False to disable pagination. -`PAGINATION_PATTERNS` A set of patterns that are used to determine advanced +``PAGINATION_PATTERNS`` A set of patterns that are used to determine advanced pagination output. ================================================ ===================================================== @@ -563,11 +561,11 @@ If you want to generate a tag cloud with all your tags, you can do so using the following settings. ================================================ ===================================================== -Setting name (default value) What does it do? +Setting name (followed by default value) What does it do? ================================================ ===================================================== -`TAG_CLOUD_STEPS` (``4``) Count of different font sizes in the tag +``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. +``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:: @@ -608,13 +606,13 @@ Translations Pelican offers a way to translate articles. See the :doc:`Getting Started ` section for more information. -===================================================== ===================================================== -Setting name (default value) What does it do? -===================================================== ===================================================== -`DEFAULT_LANG` (``'en'``) The default language to use. -`TRANSLATION_FEED_ATOM` ('feeds/all-%s.atom.xml'[3]_) Where to put the Atom feed for translations. -`TRANSLATION_FEED_RSS` (``None``, i.e. no RSS) Where to put the RSS feed for translations. -===================================================== ===================================================== +======================================================== ===================================================== +Setting name (followed by default value, if any) What does it do? +======================================================== ===================================================== +``DEFAULT_LANG = 'en'`` The default language to use. +``TRANSLATION_FEED_ATOM = 'feeds/all-%s.atom.xml'`` [3]_ Where to put the Atom feed for translations. +``TRANSLATION_FEED_RSS = None``, i.e. no RSS Where to put the RSS feed for translations. +======================================================== ===================================================== .. [3] %s is the language @@ -622,11 +620,11 @@ Ordering content ================ ================================================ ===================================================== -Setting name (default value) What does it do? +Setting name (followed by default value) What does it do? ================================================ ===================================================== -`NEWEST_FIRST_ARCHIVES` (``True``) Order archives by newest first by date. (False: +``NEWEST_FIRST_ARCHIVES = True`` Order archives by newest first by date. (False: orders by date with older articles first.) -`REVERSE_CATEGORY_ORDER` (``False``) Reverse the category order. (True: lists by reverse +``REVERSE_CATEGORY_ORDER = False`` Reverse the category order. (True: lists by reverse alphabetical order; default lists alphabetically.) ================================================ ===================================================== @@ -637,32 +635,32 @@ Creating Pelican themes is addressed in a dedicated section (see :ref:`theming-p However, here are the settings that are related to themes. ================================================ ===================================================== -Setting name (default value) What does it do? +Setting name (followed by default value, if any) What does it do? ================================================ ===================================================== -`THEME` Theme to use to produce the output. Can be a relative +``THEME`` Theme to use to produce the output. Can be a relative or absolute path to a theme folder, or the name of a default theme or a theme installed via ``pelican-themes`` (see below). -`THEME_STATIC_DIR` (``'theme'``) Destination directory in the output path where +``THEME_STATIC_DIR = 'theme'`` Destination directory in the output path where Pelican will place the files collected from `THEME_STATIC_PATHS`. Default is `theme`. -`THEME_STATIC_PATHS` (``['static']``) Static theme paths you want to copy. Default +``THEME_STATIC_PATHS = ['static']`` Static theme paths you want to copy. Default value is `static`, but if your theme has other static paths, you can put them here. If files or directories with the same names are included in the paths defined in this settings, they will be progressively overwritten. -`CSS_FILE` (``'main.css'``) Specify the CSS file you want to load. +``CSS_FILE = 'main.css'`` Specify the CSS file you want to load. ================================================ ===================================================== -By default, two themes are available. You can specify them using the `THEME` setting or by passing the -``-t`` option to the ``pelican`` command: +By default, two themes are available. You can specify them using the ``THEME`` +setting or by passing the ``-t`` option to the ``pelican`` command: * notmyidea * simple (a synonym for "plain text" :) -There are a number of other themes available at http://github.com/getpelican/pelican-themes. +There are a number of other themes available at https://github.com/getpelican/pelican-themes. Pelican comes with :doc:`pelican-themes`, a small script for managing themes. You can define your own theme, either by starting from scratch or by duplicating @@ -677,34 +675,34 @@ Following are example ways to specify your preferred theme:: # Specify a customized theme, via path relative to the settings file THEME = "themes/mycustomtheme" # Specify a customized theme, via absolute path - THEME = "~/projects/mysite/themes/mycustomtheme" + THEME = "/home/myuser/projects/mysite/themes/mycustomtheme" The built-in ``notmyidea`` theme can make good use of the following settings. Feel free to use them in your themes as well. ======================= ======================================================= -Setting name What does it do ? +Setting name What does it do? ======================= ======================================================= -`SITESUBTITLE` A subtitle to appear in the header. -`DISQUS_SITENAME` Pelican can handle Disqus comments. Specify the +``SITESUBTITLE`` A subtitle to appear in the header. +``DISQUS_SITENAME`` Pelican can handle Disqus comments. Specify the Disqus sitename identifier here. -`GITHUB_URL` Your GitHub URL (if you have one). It will then +``GITHUB_URL`` Your GitHub URL (if you have one). It will then use this information to create a GitHub ribbon. -`GOOGLE_ANALYTICS` 'UA-XXXX-YYYY' to activate Google Analytics. -`GOSQUARED_SITENAME` 'XXX-YYYYYY-X' to activate GoSquared. -`MENUITEMS` A list of tuples (Title, URL) for additional menu +``GOOGLE_ANALYTICS`` Set to 'UA-XXXX-YYYY' to activate Google Analytics. +``GOSQUARED_SITENAME`` Set to 'XXX-YYYYYY-X' to activate GoSquared. +``MENUITEMS`` A list of tuples (Title, URL) for additional menu items to appear at the beginning of the main menu. -`PIWIK_URL` URL to your Piwik server - without 'http://' at the +``PIWIK_URL`` URL to your Piwik server - without 'http://' at the beginning. -`PIWIK_SSL_URL` If the SSL-URL differs from the normal Piwik-URL +``PIWIK_SSL_URL`` If the SSL-URL differs from the normal Piwik-URL you have to include this setting too. (optional) -`PIWIK_SITE_ID` ID for the monitored website. You can find the ID - in the Piwik admin interface > settings > websites. -`LINKS` A list of tuples (Title, URL) for links to appear on +``PIWIK_SITE_ID`` ID for the monitored website. You can find the ID + in the Piwik admin interface > Settings > Websites. +``LINKS`` A list of tuples (Title, URL) for links to appear on the header. -`SOCIAL` A list of tuples (Title, URL) to appear in the +``SOCIAL`` A list of tuples (Title, URL) to appear in the "social" section. -`TWITTER_USERNAME` Allows for adding a button to articles to encourage +``TWITTER_USERNAME`` Allows for adding a button to articles to encourage others to tweet about them. Add your Twitter username if you want this button to appear. ======================= ======================================================= @@ -734,17 +732,17 @@ For example: ``[(logging.WARN, 'TAG_SAVE_AS is set to False')]`` Reading only modified content ============================= -To speed up the build process, pelican can optionally read only articles +To speed up the build process, Pelican can optionally read only articles and pages with modified content. When Pelican is about to read some content source file: 1. The hash or modification time information for the file from a - previous build are loaded from a cache file if `LOAD_CONTENT_CACHE` - is ``True``. These files are stored in the `CACHE_DIRECTORY` + previous build are loaded from a cache file if ``LOAD_CONTENT_CACHE`` + is ``True``. These files are stored in the ``CACHE_DIRECTORY`` directory. If the file has no record in the cache file, it is read as usual. -2. The file is checked according to `CHECK_MODIFIED_METHOD`: +2. The file is checked according to ``CHECK_MODIFIED_METHOD``: - If set to ``'mtime'``, the modification time of the file is checked. @@ -755,60 +753,60 @@ When Pelican is about to read some content source file: usual. 3. If the file is considered unchanged, the content data saved in a - previous build corresponding to the file is loaded from the cache + previous build corresponding to the file is loaded from the cache, and the file is not read. 4. If the file is considered changed, the file is read and the new modification information and the content data are saved to the - cache if `CACHE_CONTENT` is ``True``. + cache if ``CACHE_CONTENT`` is ``True``. -Depending on `CONTENT_CACHING_LAYER` either the raw content and -metadata returned by a reader are cached if set to ``'reader'``, or -the processed content object is cached if set to ``'generator'``. -Caching the processed content object may conflict with plugins (as -some reading related signals may be skipped) or e.g. the -`WITH_FUTURE_DATES` functionality (as the ``draft`` status of the +If ``CONTENT_CACHING_LAYER`` is set to ``'reader'`` (the default), +the raw content and metadata returned by a reader are cached. If this +setting is instead set to ``'generator'``, the processed content +object is cached. Caching the processed content object may conflict +with plugins (as some reading related signals may be skipped) and the +``WITH_FUTURE_DATES`` functionality (as the ``draft`` status of the cached content objects would not change automatically over time). -Modification time based checking is faster than comparing file hashes, -but is not as reliable, because mtime information can be lost when -e.g. copying the content sources using the ``cp`` or ``rsync`` -commands without the mtime preservation mode (invoked e.g. by -``--archive``). +Checking modification times is faster than comparing file hashes, +but it is not as reliable because ``mtime`` information can be lost, +e.g., when copying content source files using the ``cp`` or ``rsync`` +commands without the ``mtime`` preservation mode (which for ``rsync`` +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 -running pelican after removing them 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. +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. The ``--ignore-cache`` command-line option is also useful when the -whole cache needs to be regenerated due to e.g. modifications to the -settings file which should change the cached content or just for -debugging purposes. When pelican runs in autoreload mode, modification +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 of the settings file will make it ignore the cache automatically if -`AUTORELOAD_IGNORE_CACHE` is ``True``. +``AUTORELOAD_IGNORE_CACHE`` is ``True``. Note that even when using cached content, all output is always -written, so the modification times of the ``*.html`` files always -change. Therefore, ``rsync`` based upload may benefit from the -``--checksum`` option. +written, so the modification times of the generated ``*.html`` files +will always change. Therefore, ``rsync``-based uploading may benefit +from the ``--checksum`` option. .. _writing_only_selected_content: Writing only selected content ============================= -When one article or page or the theme is being worked on it is often -desirable to display selected output files as soon as possible. In -such cases generating and writing all output is often unnecessary. -These selected output files can be given as output paths in the -`WRITE_SELECTED` list and **only** those files will be written. This -list can be also specified on the command-line using the -``--write-selected`` option which accepts a comma separated list -of output file paths. By default the list is empty so all output is -written. +When only working on a single article or page, or making tweaks to +your theme, it is often desirable to generate and review your work +as quickly as possible. In such cases, generating and writing the +entire site output is often unnecessary. By specifying only the +desired files as output paths in the ``WRITE_SELECTED`` list, +**only** those files will be written. This list can be also specified +on the command line using the ``--write-selected`` option, which +accepts a comma-separated list of output file paths. By default this +list is empty, so all output is written. Example settings ================ From 0d3687866a01373cb841597b2e9bb1a212e8ab05 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 30 Apr 2014 13:35:10 -0700 Subject: [PATCH 57/82] Remove errant leading spaces from fabfile.py.in --- pelican/tools/templates/fabfile.py.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pelican/tools/templates/fabfile.py.in b/pelican/tools/templates/fabfile.py.in index fbd03e2c..e693bb48 100644 --- a/pelican/tools/templates/fabfile.py.in +++ b/pelican/tools/templates/fabfile.py.in @@ -36,13 +36,13 @@ def regenerate(): def serve(): os.chdir(env.deploy_path) - + PORT = 8000 class AddressReuseTCPServer(SocketServer.TCPServer): allow_reuse_address = True - + server = AddressReuseTCPServer(('', PORT), SimpleHTTPServer.SimpleHTTPRequestHandler) - + sys.stderr.write('Serving on port {0} ...\n'.format(PORT)) server.serve_forever() From 9e6c669949e5e5543eb041c7ae1c06d969fe18fa Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Thu, 1 May 2014 10:39:07 +0200 Subject: [PATCH 58/82] Fix get_writer signal received result unpacking --- pelican/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 5208c317..74f55c46 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -209,13 +209,13 @@ class Pelican(object): return generators def get_writer(self): - writers = [ w for w in signals.get_writer.send(self) + writers = [ w for (_, w) in signals.get_writer.send(self) if isinstance(w, type) ] writers_found = len(writers) if writers_found == 0: return Writer(self.output_path, settings=self.settings) else: - _, writer = writers[0] + writer = writers[0] if writers_found == 1: logger.debug('Found writer: {}'.format(writer)) else: From 4239b47b836498c1e522de28907f1ce25a834f51 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 2 May 2014 07:34:27 -0700 Subject: [PATCH 59/82] Remove errant backtick --- docs/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index c9eddfba..d5802d1d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -112,7 +112,7 @@ Setting name (followed by default value, if any) ``PAGE_DIR = 'pages'`` Directory to look at for pages, relative to `PATH`. ``PAGE_EXCLUDES = ()`` A list of directories to exclude when looking for pages. ``ARTICLE_DIR = ''`` Directory to look at for articles, relative to `PATH`. -``ARTICLE_EXCLUDES` = ('pages',)`` A list of directories to exclude when looking for articles. +``ARTICLE_EXCLUDES = ('pages',)`` A list of directories to exclude when looking for articles. ``OUTPUT_SOURCES = False`` Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the specified ``OUTPUT_PATH``. From 4fc448ac0dfa702beedbfb29e453124ba9f19be5 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 27 Apr 2014 08:53:56 +0200 Subject: [PATCH 60/82] rename CACHE_DIR -> CACHE_PATH to unify with rest of Pelican CACHE_PATH can now be relative to settings file like OUTPUT_PATH. Also add --cache-path commandline option. Change cache loading warning to a less scary and more helpful message. --- docs/settings.rst | 4 ++-- pelican/__init__.py | 8 +++++++- pelican/settings.py | 4 ++-- pelican/tests/test_generators.py | 26 +++++++++++++------------- pelican/tests/test_pelican.py | 10 +++++----- pelican/utils.py | 18 ++++++++++++------ 6 files changed, 41 insertions(+), 29 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index d5802d1d..2782977c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -178,7 +178,7 @@ Setting name (followed by default value, if any) ``CONTENT_CACHING_LAYER = 'reader'`` If set to ``'reader'``, save only the raw content and metadata returned by readers. If set to ``'generator'``, save processed content objects. -``CACHE_DIRECTORY = 'cache'`` Directory in which to store cache files. +``CACHE_PATH = 'cache'`` Directory in which to store cache files. ``GZIP_CACHE = True`` If ``True``, use gzip to (de)compress the cache files. ``CHECK_MODIFIED_METHOD = 'mtime'`` Controls how files are checked for modifications. ``LOAD_CONTENT_CACHE = True`` If ``True``, load unmodified content from cache. @@ -739,7 +739,7 @@ When Pelican is about to read some content source file: 1. The hash or modification time information for the file from a previous build are loaded from a cache file if ``LOAD_CONTENT_CACHE`` - is ``True``. These files are stored in the ``CACHE_DIRECTORY`` + is ``True``. These files are stored in the ``CACHE_PATH`` directory. If the file has no record in the cache file, it is read as usual. 2. The file is checked according to ``CHECK_MODIFIED_METHOD``: diff --git a/pelican/__init__.py b/pelican/__init__.py index 74f55c46..d6417391 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -275,7 +275,11 @@ def parse_arguments(): help='Relaunch pelican each time a modification occurs' ' on the content files.') - parser.add_argument('-c', '--ignore-cache', action='store_true', + parser.add_argument('--cache-path', dest='cache_path', + help=('Directory in which to store cache files. ' + 'If not specified, defaults to "cache".')) + + parser.add_argument('--ignore-cache', action='store_true', dest='ignore_cache', help='Ignore content cache ' 'from previous runs by not loading cache files.') @@ -300,6 +304,8 @@ def get_config(args): config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir if args.ignore_cache: config['LOAD_CONTENT_CACHE'] = False + if args.cache_path: + config['CACHE_PATH'] = args.cache_path if args.selected_paths: config['WRITE_SELECTED'] = args.selected_paths.split(',') diff --git a/pelican/settings.py b/pelican/settings.py index abf16b32..f759ff9e 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -124,7 +124,7 @@ DEFAULT_CONFIG = { 'SLUGIFY_SOURCE': 'title', 'CACHE_CONTENT': True, 'CONTENT_CACHING_LAYER': 'reader', - 'CACHE_DIRECTORY': 'cache', + 'CACHE_PATH': 'cache', 'GZIP_CACHE': True, 'CHECK_MODIFIED_METHOD': 'mtime', 'LOAD_CONTENT_CACHE': True, @@ -139,7 +139,7 @@ def read_settings(path=None, override=None): if path: local_settings = get_settings_from_file(path) # Make the paths relative to the settings file - for p in ['PATH', 'OUTPUT_PATH', 'THEME']: + for p in ['PATH', 'OUTPUT_PATH', 'THEME', 'CACHE_PATH']: if p in local_settings and local_settings[p] is not None \ and not isabs(local_settings[p]): absp = os.path.abspath(os.path.normpath(os.path.join( diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 9463047e..7b79e8f3 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -66,7 +66,7 @@ class TestArticlesGenerator(unittest.TestCase): def test_generate_feeds(self): settings = get_settings() - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) @@ -142,7 +142,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_DATE'] = (1970, 1, 1) settings['USE_FOLDER_AS_CATEGORY'] = False - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache settings['READERS'] = {'asc': None} settings['filenames'] = {} generator = ArticlesGenerator( @@ -167,7 +167,7 @@ class TestArticlesGenerator(unittest.TestCase): def test_direct_templates_save_as_default(self): settings = get_settings(filenames={}) - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) @@ -182,7 +182,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings() settings['DIRECT_TEMPLATES'] = ['archives'] settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) @@ -198,7 +198,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings() settings['DIRECT_TEMPLATES'] = ['archives'] settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) @@ -225,7 +225,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings(filenames={}) settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=CONTENT_DIR, theme=settings['THEME'], output_path=None) @@ -291,7 +291,7 @@ class TestArticlesGenerator(unittest.TestCase): def test_article_object_caching(self): """Test Article objects caching at the generator level""" settings = get_settings(filenames={}) - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache settings['CONTENT_CACHING_LAYER'] = 'generator' settings['READERS'] = {'asc': None} @@ -311,7 +311,7 @@ class TestArticlesGenerator(unittest.TestCase): def test_reader_content_caching(self): """Test raw content caching at the reader level""" settings = get_settings(filenames={}) - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache settings['READERS'] = {'asc': None} generator = ArticlesGenerator( @@ -335,7 +335,7 @@ class TestArticlesGenerator(unittest.TestCase): used in --ignore-cache or autoreload mode""" settings = get_settings(filenames={}) - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache settings['READERS'] = {'asc': None} generator = ArticlesGenerator( @@ -373,7 +373,7 @@ class TestPageGenerator(unittest.TestCase): def test_generate_context(self): settings = get_settings(filenames={}) settings['PAGE_DIR'] = 'TestPages' # relative to CUR_DIR - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache settings['DEFAULT_DATE'] = (1970, 1, 1) generator = PagesGenerator( @@ -402,7 +402,7 @@ class TestPageGenerator(unittest.TestCase): def test_page_object_caching(self): """Test Page objects caching at the generator level""" settings = get_settings(filenames={}) - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache settings['CONTENT_CACHING_LAYER'] = 'generator' settings['READERS'] = {'asc': None} @@ -422,7 +422,7 @@ class TestPageGenerator(unittest.TestCase): def test_reader_content_caching(self): """Test raw content caching at the reader level""" settings = get_settings(filenames={}) - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache settings['READERS'] = {'asc': None} generator = PagesGenerator( @@ -446,7 +446,7 @@ class TestPageGenerator(unittest.TestCase): used in --ignore_cache or autoreload mode""" settings = get_settings(filenames={}) - settings['CACHE_DIRECTORY'] = self.temp_cache + settings['CACHE_PATH'] = self.temp_cache settings['READERS'] = {'asc': None} generator = PagesGenerator( diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 294cf399..411fb7da 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -79,7 +79,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=None, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, - 'CACHE_DIRECTORY': self.temp_cache, + 'CACHE_PATH': self.temp_cache, 'LOCALE': locale.normalize('en_US'), }) pelican = Pelican(settings=settings) @@ -95,7 +95,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=SAMPLE_CONFIG, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, - 'CACHE_DIRECTORY': self.temp_cache, + 'CACHE_PATH': self.temp_cache, 'LOCALE': locale.normalize('en_US'), }) pelican = Pelican(settings=settings) @@ -107,7 +107,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=SAMPLE_CONFIG, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, - 'CACHE_DIRECTORY': self.temp_cache, + 'CACHE_PATH': self.temp_cache, 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'), os.path.join(SAMPLES_PATH, 'kinda'), os.path.join(SAMPLES_PATH, 'theme_standard')] @@ -128,7 +128,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=SAMPLE_CONFIG, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, - 'CACHE_DIRECTORY': self.temp_cache, + 'CACHE_PATH': self.temp_cache, 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'theme_standard')] }) @@ -144,7 +144,7 @@ class TestPelican(LoggedTestCase): settings = read_settings(path=None, override={ 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, - 'CACHE_DIRECTORY': self.temp_cache, + 'CACHE_PATH': self.temp_cache, 'WRITE_SELECTED': [ os.path.join(self.temp_path, 'oh-yeah.html'), os.path.join(self.temp_path, 'categories.html'), diff --git a/pelican/utils.py b/pelican/utils.py index 7b58a231..2af34ecf 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -553,14 +553,14 @@ class FileDataCacher(object): '''Class that can cache data contained in files''' def __init__(self, settings, cache_name, caching_policy, load_policy): - '''Load the specified cache within CACHE_DIRECTORY in settings + '''Load the specified cache within CACHE_PATH in settings only if *load_policy* is True, May use gzip if GZIP_CACHE ins settings is True. Sets caching policy according to *caching_policy*. ''' self.settings = settings - self._cache_path = os.path.join(self.settings['CACHE_DIRECTORY'], + self._cache_path = os.path.join(self.settings['CACHE_PATH'], cache_name) self._cache_data_policy = caching_policy if self.settings['GZIP_CACHE']: @@ -572,9 +572,15 @@ class FileDataCacher(object): try: with self._cache_open(self._cache_path, 'rb') as fhandle: self._cache = pickle.load(fhandle) - except (IOError, OSError, pickle.UnpicklingError) as err: - logger.warning(('Cannot load cache {}, ' - 'proceeding with empty cache.\n{}').format( + except (IOError, OSError) as err: + logger.debug(('Cannot load cache {} (this is normal on first ' + 'run). Proceeding with empty cache.\n{}').format( + self._cache_path, err)) + self._cache = {} + except pickle.UnpicklingError as err: + logger.warning(('Cannot unpickle cache {}, cache may be using ' + 'an incompatible protocol (see pelican caching docs). ' + 'Proceeding with empty cache.\n{}').format( self._cache_path, err)) self._cache = {} else: @@ -596,7 +602,7 @@ class FileDataCacher(object): '''Save the updated cache''' if self._cache_data_policy: try: - mkdir_p(self.settings['CACHE_DIRECTORY']) + mkdir_p(self.settings['CACHE_PATH']) with self._cache_open(self._cache_path, 'wb') as fhandle: pickle.dump(self._cache, fhandle) except (IOError, OSError, pickle.PicklingError) as err: From faa5f4763a0d3486c2f14b05f3740f06c3adb103 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Mon, 28 Apr 2014 20:43:19 +0200 Subject: [PATCH 61/82] Fix #1335 remove old and unmaintained French docs Unfortunately nobody keeps them up to date, they would just spread confusion. --- docs/fr/astuces.rst | 20 ---- docs/fr/bases.rst | 58 ----------- docs/fr/configuration.rst | 165 ------------------------------- docs/fr/conventions.rst | 18 ---- docs/fr/faq.rst | 40 -------- docs/fr/index.rst | 57 ----------- docs/fr/installation.rst | 67 ------------- docs/fr/parametres_article.rst | 106 -------------------- docs/fr/pelican-themes.rst | 172 --------------------------------- docs/fr/themes.rst | 171 -------------------------------- docs/index.rst | 2 - 11 files changed, 876 deletions(-) delete mode 100644 docs/fr/astuces.rst delete mode 100644 docs/fr/bases.rst delete mode 100644 docs/fr/configuration.rst delete mode 100644 docs/fr/conventions.rst delete mode 100644 docs/fr/faq.rst delete mode 100644 docs/fr/index.rst delete mode 100644 docs/fr/installation.rst delete mode 100644 docs/fr/parametres_article.rst delete mode 100644 docs/fr/pelican-themes.rst delete mode 100644 docs/fr/themes.rst diff --git a/docs/fr/astuces.rst b/docs/fr/astuces.rst deleted file mode 100644 index 3f9a3987..00000000 --- a/docs/fr/astuces.rst +++ /dev/null @@ -1,20 +0,0 @@ -Trucs et astuces pour Pelican -############################# - -Personnaliser l'url d'un article pour Pelican -============================================= - -Par défaut, quand vous créez un article ayant pour titre *Mon article pour Pelican*, -l'url par défaut devient *mon-article-pour-pelican.html*. Cependant, il est possible -de modifier cela en utilisant la technique utilisée pour les traductions d'article, -c'est à dire le paramètre *:slug:* :: - - Mon article pour Pelican - ######################## - - :date: 2011-01-31 11:05 - :slug: super-article-pour-pelican - - bla, bla, bla … - -En prenant cet exemple ci dessus, votre url deviendra *super-article-pour-pelican.html* diff --git a/docs/fr/bases.rst b/docs/fr/bases.rst deleted file mode 100644 index 7a6fd118..00000000 --- a/docs/fr/bases.rst +++ /dev/null @@ -1,58 +0,0 @@ -Les bases de Pelican -#################### - -Créer son premier article -========================= - -Pour créer notre premier article, nous allons éditer un fichier, par exemple premier_article.rst :: - - Premier article pour Pelican - ############################ - :author: Guillaume - :date: 2011-01-08 10:20 - :category: GNU-Linux - :tags: tutoriel, git - Ceci est un tutoriel pour configurer git. - Bla, bla, bla .... - -Maintenant que ce fichier est créé, on va lancer la création du blog :: - - pelican . - -Vous aller obtenir une sortie comme celle ci — $PATH représente le dossier où vous -avez créé votre article :: - - [ok] writing $PATH/output/feeds/all.atom.xml - [ok] writing $PATH/output/feeds/GNU/Linux.atom.xml - [ok] writing $PATH/output/feeds/all-en.atom.xml - [ok] writing $PATH/output/premier-article-pour-pelican.html - [ok] writing $PATH/output/index.html - [ok] writing $PATH/output/tags.html - [ok] writing $PATH/output/categories.html - [ok] writing $PATH/output/archives.html - [ok] writing $PATH/output/tag/tutoriel.html - [ok] writing $PATH/output/tag/git.html - [ok] writing $PATH/output/category/GNU-Linux.html - - -Première analyse -================ - -Nous allons décortiquer un peu tout ça ensemble. - -* Un dossier output/ a été créé pour y mettre le fichiers xml et html du blog. -* Dans le dossier feeds/, nous retrouvons les différents flux de syndication. -* Le fichier de l’article et la page principale du blog a été généré. -* Le répertoire tag/ propose une page par tag. -* La page correspondant à la catégorie est générée dans le répertoire category/ - -Si vous ouvrez le fichier index.html — ou un autre — avec votre navigateur, vous -remarquerez que : - -* Le thème utilisé par défaut est notmyidea -* Le nom du blog est A Pelican Blog. - -Bien évidemment, il y a des paramètres de base que l’on peut modifier pour mettre -un peu tout ça à sa sauce. C’est ce que nous allons voir au travers du fichier de configuration. - - diff --git a/docs/fr/configuration.rst b/docs/fr/configuration.rst deleted file mode 100644 index 5388dae3..00000000 --- a/docs/fr/configuration.rst +++ /dev/null @@ -1,165 +0,0 @@ -Fichier de configuration -************************ - -On va créer un fichier de configuration que l’on va appeler **settings.py**. On peut -utiliser Pelican sans faire ce fichier, mais il faudrait à chaque fois passer les paramètres -en ligne de commande. Et comme il va nous servir à faire d’autres choses bien utile, -autant l’appréhender de suite. Cependant, nous n’allons voir que la base pour l’instant. - -Paramètres de base -================== - -AUTHOR : - Désigne l’auteur par défaut ; - -DEFAULT_CATEGORY : - La catégorie par défaut des articles. Si ce paramètre n’est - pas documenté, il prendra la valeur misc — pour miscellaneous (divers en français) ; - -SITENAME : - Le nom de votre site ; - -OUTPUT_PATH : - Le répertoire de sortie du blog. - -Quand je dis qu’on va faire simple, on fait simple ! -Passons donc à ce quoi doit ressembler le fichier de configuration :: - - # -*- coding: utf-8 -*- - AUTHOR = "Guillaume" - DEFAULT_CATEGORY = "GNU-Linux" - SITENAME = "Free Culture" - - -Si vous avez un serveur comme Apache de configuré pour votre machine, vous -pouvez paramétrer le répertoire de sortie vers **/var/www/blog** par exemple :: - - OUTPUT_PATH = "/var/www/blog" - -Une remarque importante. Si vous avez besoin de passer un caractère accentué, il -faut le préciser que la chaine est en unicode en faisant par exemple -*AUTHOR = u"Guillaume LAMÉ"* - -Pour bien vérifier que les paramètres sont bien pris en compte, nous allons enlever les lignes *:author: Guillaume* et *:category: GNU-Linux* de notre fichier -**premier_article.rst** et regénérer le blog. - -Rafraichissez votre page, ce devrait être bon. - -Nous allons maintenant passer en revue les différents paramètres de Pelican. Je les -ai regroupé par thème. Cependant, c’est surtout un listing avant de rentrer dans les -détails au prochain chapitre. - -Flux de syndication -=================== - -CATEGORY_FEED_ATOM : - Chemin d’écriture des flux Atom liés aux catégories ; - -CATEGORY_FEED_RSS : - Idem pour les flux rss (Optionnel); - -FEED_ATOM : - Chemin du flux Atom global; - -FEED_RSS : - Chemin du flux Rss global (Optionnel); - -FEED_ALL_ATOM : - Chemin du flux Atom global qui inclut la totalité des posts, indépendamment de la langue; - -FEED_ALL_RSS : - Chemin du flux Rss global qui inclut la totalité des posts, indépendamment de la langue (Optionnel); - -TAG_FEED_ATOM : - Chemin des flux Atom pour les tags (Optionnel); - -TAG_FEED_RSS : - Chemin des flux Rss pour les tags (Optionnel). - - -Traductions -=========== - -DEFAULT_LANG : - Le langage par défaut à utiliser. «*en*» par défaut ; - -TRANSLATION_FEED_ATOM : - Chemin du flux Atom pour les traductions. - -TRANSLATION_FEED_RSS : - Chemin du flux RSS pour les traductions. - - -Thèmes -====== - -CSS_FILE : - Fichier css à utiliser si celui-ci est différent du fichier par défaut (*main.css*) ; - -DISPLAY_PAGES_ON_MENU : - Affiche ou non les pages statiques sur le menu du thème ; - -DISQUS_SITENAME : - Indiquer le nom du site spécifié sur Disqus ; - -GITHUB_URL : - Indiquez votre url Github ; - -GOOGLE_ANALYTICS : - 'UA-XXXX-YYYY' pour activer Google analytics ; - -GOSQUARED_SITENAME : - 'XXX-YYYYYY-X' pour activer GoSquared ; - -JINJA_EXTENSIONS : - Liste d'extension Jinja2 que vous souhaitez utiliser ; - -LINKS : - Une liste de tuples (Titre, url) pour afficher la liste de lien ; - -PDF_PROCESSOR : - Génère ou non les articles et pages au format pdf ; - -NEWEST_FIRST_ARCHIVES : - Met les articles plus récent en tête de l'archive ; - -SOCIAL : - Une liste de tuples (Titre, url) pour afficher la liste de lien dans la section "Social" ; - -STATIC_THEME_PATHS : - Répertoire du thème que vous souhaitez importer dans l'arborescence finale ; - -THEME : - Thème à utiliser: - -TWITTER_USERNAME : - Permet d'afficher un bouton permettant le tweet des articles. - -Pelican est fournit avec :doc:`pelican-themes`, un script permettant de gérer les thèmes - - - -Paramètres divers -================= - -DEFAULT_DATE: - Date par défaut à utiliser si l'information de date n'est pas spécifiée - dans les metadonnées de l'article. - Si 'fs', Pelican se basera sur le *mtime* du fichier. - Si c'est un tuple, il sera passé au constructeur datetime.datetime pour - générer l'objet datetime utilisé par défaut. - -KEEP_OUTPUT DIRECTORY : - Ne génère que les fichiers modifiés et n'efface pas le repertoire de sortie ; - -MARKUP : - Langage de balisage à utiliser ; - -PATH : - Répertoire à suivre pour les fichiers inclus ; - -SITEURL : - URL de base de votre site ; - -STATIC_PATHS : - Les chemins statiques que vous voulez avoir accès sur le chemin de sortie "statique" ; diff --git a/docs/fr/conventions.rst b/docs/fr/conventions.rst deleted file mode 100644 index bf88c07e..00000000 --- a/docs/fr/conventions.rst +++ /dev/null @@ -1,18 +0,0 @@ -Conventions -########### - -Environnement de test -===================== - -Les exemples sont basées sur une distribution Debian. Pour les autres distributions, -il y aura des ajustements à faire, notamment pour l’installation de Pelican. Les -noms des paquets peuvent changer. - -Conventions typographiques -========================== - -Un petit rappel concernant les codes sources. - - * $ correspond à une ligne à exécuter en tant qu’utilisateur courant du systême ; - * # correspond à une ligne à exécuter en tant que root ; - * **settings.py** : Les noms des répertoires et fichiers sont en gras. diff --git a/docs/fr/faq.rst b/docs/fr/faq.rst deleted file mode 100644 index d945f447..00000000 --- a/docs/fr/faq.rst +++ /dev/null @@ -1,40 +0,0 @@ -Foire aux questions (FAQ) -######################### - -Voici un résumé des questions fréquemment posées pour pelican. - -Est-il obligatoire d'avoir un fichier de configuration ? -======================================================== - -Non. Les fichiers de configuration sont juste un moyen facile de configurer -pelican. Pour les opérations de base, il est possible de spécifier des -options -en invoquant pelican avec la ligne de commande (voir pelican --help pour -plus -d'informations à ce sujet) - -Je crée mon propre thème, comment utiliser pygments? -==================================================== - -Pygment ajoute quelques classes au contenu généré, de sorte qua colorisation -de votre thème se fait grâce à un fichier css. Vous pouvez jeter un oeil à -celui proposé par`sur le site du projet `_ - -Comment puis-je créer mon propre thèm -===================================== - -Vueillez vous référer à :ref:`theming-pelican-fr`. - -Comment puis-je aider? -====================== - -Vous avez plusieurs options pour aider. Tout d'abord, vous pouvez utiliser -le -pélican, et signaler toute idée ou problème que vous avez sur le bugtracker -. - -Si vous voulez contribuer, jeter un oeil au dépôt git , ajoutez vos -modifications et faites une demande, je les regarderai dès que possible - -Vous pouvez aussi contribuer en créant des thèmes, et/ou compléter la -documentation. diff --git a/docs/fr/index.rst b/docs/fr/index.rst deleted file mode 100644 index 2deb5050..00000000 --- a/docs/fr/index.rst +++ /dev/null @@ -1,57 +0,0 @@ -Pelican -####### - -Pelican est un generateur de blog simple codé en python - -* Écrivez vos articles directement dans votre éditeur favori (vim !) et - directement en syntaxe reStructuredText ou Markdown ; -* Un outil simple en ligne de conmmande pour (re)générer le blog ; -* Sortie complètement statique, facile pour l'héberger n'importe où ; - -Fonctionnalités -=============== - -Pelican supporte actuellement : - -* des articles de blog ; -* des pages statiques ; -* les commentaires via un service externe (`disqus `_) - Notez qu'étant bien un service externe assez pratique, vous ne gérez pas - vous même les commentaires. Ce qui pourrait occasionner une perte de vos données; -* support de template (les templates sont crées avec `jinja2 `_) ; -* génération optionnelle de vos pages et articles en pdf. - -Pourquoi le nom "Pelican" ? -============================ - -Vous n'avez pas remarqué ? "Pelican" est un anagramme pour "Calepin" ;) - -Code source -=========== - -Vous pouvez accéder au code source via git à l'adresse -http://github.com/getpelican/pelican/ - -Feedback ! -========== - -Si vous voulez de nouvelles fonctionnalitées pour Pelican, n'hésitez pas à nous le dire, -à cloner le dépôt, etc … C'est open source !!! - -Contactez Alexis à "alexis at notmyidea dot org" pour quelques requêtes ou retour d'expérience que ce soi ! - -Documentation -============= - -.. toctree:: - :maxdepth: 2 - - conventions - installation - bases - configuration - themes - parametres_article - astuces - faq - pelican-themes diff --git a/docs/fr/installation.rst b/docs/fr/installation.rst deleted file mode 100644 index da327725..00000000 --- a/docs/fr/installation.rst +++ /dev/null @@ -1,67 +0,0 @@ -Installation et mise à jour de Pelican -###################################### - -Installation -============ - -Il y a deux façons d’installer Pelican sur son système. La première est via l’utilitaire -pip, l’autre façon est de télécharger Pelican via Github. Ici nous allons voir les deux -façons de procéder. - -Via pip -------- - -Pour installer Pelican via pip, vous aurez besoin du paquet python-pip. puis installez Pelican :: - - # apt-get install python-pip - # pip install pelican - - -Via Github ----------- - -Pour installer Pelican en reprenant le code via Github, nous aurons besoin du paquet -git-core pour récupérez les sources de Pelican. Puis nous procédons à l’installation :: - - # apt-get install git-core - $ git clone https://github.com/getpelican/pelican.git - $ cd pelican - # python setup.py install - -Mises à jour -============ - -Via pip -------- - -Rien de bien compliqué pour mettre à jour via pip :: - - $ cd votreRepertoireSource - $ pip install --upgrade pelican - - -Via Github ----------- - -C'est un peu plus long avec Github par contre :: - - $ cd votreRepertoireSource - $ git pull origin master - $ cd pelican - # python setup.py install - -Vous aurez un message d’erreur si le module setuptools de python n’est pas installé. -La manipulation est la suivante :: - - # apt-get install python-setuptools - -Alors, quelle méthode choisir ? -=============================== - -Vous avez le choix entre deux méthodes, mais aussi entre deux concepts. La méthode -de Github est la version de développement, où les modifications arrivent assez -fréquemment sans être testées à fond. La version de pip est une version arrêtée avec un -numéro de version dans laquelle vous aurez moins de bug. N’oubliez cependant pas -que le projet est très jeune et manque donc de maturité. Si vous aimez avoir les toutes -dernières versions utilisez Github, sinon penchez vous sur pip. - diff --git a/docs/fr/parametres_article.rst b/docs/fr/parametres_article.rst deleted file mode 100644 index a3d25b55..00000000 --- a/docs/fr/parametres_article.rst +++ /dev/null @@ -1,106 +0,0 @@ -Les paramètres des articles dans Pelican -######################################## - -Les catégories -============== - -Nous avons vu que pour affecter un article à une catégorie, nous avions le paramètre *:category:*. -Il y a cependant plus simple, affecter un répertoire à une catégorie. - -Dans le répertoire ou vous avez vos articles, créez le repertoire **GNU-Linux** et déplacez y le fichier -**premier_article.rst**. Bien évidemment nous ne verront pas la différence, car jusqu'ici *GNU-Linux* -est notre catégorie par défaut. - -Nous allons faire un autre exemple d'article avec la catégorie Pelican. Créez le répertoire **Pelican** -et collez cette exemple d'article :: - - Préparation de la documentation - ############################### - - :date: 2011-01-27 15:28 - :tags: documentation - - Il y a quand même pas mal de boulot pour faire une documentation ! - -Et lancez la compilation du blog. Vous voyez que la catégorie est affectée automatiquement. - -Les tags -======== - -Pour les tags, il n'y a rien de compliqué. il suffit de mettre le(s) tags séparés si besoin d'une virgule. :: - - Préparation de la documentation - ############################### - - :date: 2011-01-27 15:28 - :tags: documentation, pelican - -Par contre, par soucis de clarté au niveau des url je vous conseille de mettre les expression de plusieurs -mots séparées par des tirets :: - - :tags: mise-a-jour - -et non :: - - :tags: mise a jour - - -Les auteurs -=========== - -Par défaut, vous pouvez indiqué votre nom en tant qu'auteur dans le fichier de configuration. -S'il y a plusieurs auteurs pour le site, vous pouvez le définir manuellement dans -l'article avec la méta-donnée :: - - :author: Guillaume - -La date -======= - -La date se met au format anglophone : **YYYY-MM-DD hh:mm** :: - - :date: 2011-01-31 14:12 - - -Les traductions -=============== - -Pelican permet de générer un blog multilingue assez facilement. Pour cela nous devons : - -* Définir la langue de base du blog ; -* Donner une référence à l'article initial ; -* Définir la langue du fichier traduit et y reporter la référence. - -Pour définir la langue de base nous allons modifier le fichier **settings.py** et y rajouter la ligne suivante :: - - DEFAULT_LANG = "fr" - -Puis ajouter la référence dans notre article d'origine qui deviendra :: - - Préparation de la documentation - ############################### - - :date: 2011-01-27 15:28 - :tags: documentation - :slug: preparation-de-la-documentation - - Il y a quand même pas mal de boulot pour faire une documentation ! - -Nous n'avons plus qu'à créer l'article en anglais :: - - Start of documentation - ###################### - - :slug: preparation-de-la-documention - :lang: en - - There are still a lot of work to documentation ! - -**Il est important de comprendre que la valeur de :slug: deviendra votre url. Ne mettez donc pas un diminutif pour -identifier l'article** - -Rien de plus à savoir pour traduire efficacement des articles. - - -Maintenant que vous avez toutes les clés en main pour créer un article, nous allons passer à la personnalisation -du fichier de configuration. diff --git a/docs/fr/pelican-themes.rst b/docs/fr/pelican-themes.rst deleted file mode 100644 index 810fa785..00000000 --- a/docs/fr/pelican-themes.rst +++ /dev/null @@ -1,172 +0,0 @@ -pelican-themes -############## - - - -Description -=========== - -``pelican-themes`` est un outil en lignes de commandes pour gérer les thèmes de Pelican. - - -Utilisation: -"""""""""""" - -| pelican-themes [-h] [-l] [-i *chemin d'un thème* [*chemin d'un thème* ...]] -| [-r *nom d'un thème* [*nom d'un thème* ...]] -| [-s *chemin d'un thème* [*chemin d'un thème* ...]] [-v] [--version] - -Arguments: -"""""""""" - - --h, --help Afficher l'aide et quitter - --l, --list Montrer les thèmes installés - --i chemin, --install chemin Chemin(s) d'accès d'un ou plusieurs thème à installer - --r nom, --remove nom Noms d'un ou plusieurs thèmes à installer - --s chemin, --symlink chemin Fonctionne de la même façon que l'option ``--install``, mais crée un lien symbolique au lieu d'effectuer une copie du thème vers le répertoire des thèmes. - Utile pour le développement de thèmes. - --v, --verbose Sortie détaillée - ---version Affiche la version du script et quitte - - - -Exemples -======== - - -Lister les thèmes installés -""""""""""""""""""""""""""" - -``pelican-themes`` peut afficher les thèmes disponibles. - -Pour cela, vous pouvez utiliser l'option ``-l`` ou ``--list``, comme ceci: - -.. code-block:: console - - $ pelican-themes -l - notmyidea - two-column@ - simple - $ pelican-themes --list - notmyidea - two-column@ - simple - -Dans cet exemple, nous voyons qu'il y a trois thèmes d'installés: ``notmyidea``, ``simple`` and ``two-column``. - -``two-column`` est suivi d'un ``@`` par ce que c'est un lien symbolique (voir `Créer des liens symboliques`_). - -Notez que vous pouvez combiner l'option ``--list`` avec l'option ``--verbose``, pour afficher plus de détails: - -.. code-block:: console - - $ pelican-themes -v -l - /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/notmyidea - /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/two-column (symbolic link to `/home/skami/Dev/Python/pelican-themes/two-column') - /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/simple - - -Installer des thèmes -"""""""""""""""""""" - -Vous pouvez installer un ou plusieurs thèmes en utilisant l'option ``-i`` ou ``--install``. - -Cette option prends en argument le(s) chemin(s) d'accès du ou des thème(s) que vous voulez installer, et peut se combiner avec l'option ``--verbose``: - -.. code-block:: console - - # pelican-themes --install ~/Dev/Python/pelican-themes/notmyidea-cms --verbose - -.. code-block:: console - - # pelican-themes --install ~/Dev/Python/pelican-themes/notmyidea-cms\ - ~/Dev/Python/pelican-themes/martyalchin \ - --verbose - -.. code-block:: console - - # pelican-themes -vi ~/Dev/Python/pelican-themes/two-column - - -Supprimer des thèmes -"""""""""""""""""""" - -``pelican-themes`` peut aussi supprimer des thèmes précédemment installés grâce à l'option ``-r`` ou ``--remove``. - -Cette option prends en argument le ou les nom(s) des thèmes que vous voulez installer, et peux se combiner avec l'option ``--verbose``: - -.. code-block:: console - - # pelican-themes --remove two-column - -.. code-block:: console - - # pelican-themes -r martyachin notmyidea-cmd -v - - - - - -Créer des liens symboliques -""""""""""""""""""""""""""" - - -L'option ``-s`` ou ``--symlink`` de ``pelican-themes`` permet de lier symboliquement un thème. - -Cette option s'utilise exactement comme l'option ``--install``: - -.. code-block:: console - - # pelican-themes --symlink ~/Dev/Python/pelican-themes/two-column - -Dans l'exemple ci dessus, un lien symbolique pointant vers le thème ``two-column`` a été installé dans le répertoire des thèmes de Pelican, toute modification sur le thème ``two-column`` prendra donc effet immédiatement. - -Cela peut être pratique pour le développement de thèmes - -.. code-block:: console - - $ 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 - $ pelican ~/Blog/content -o /tmp/out -t two-column - $ cp /tmp/bg.png ~/Dev/Pelican/pelican-themes/two-coumn/static/img/bg.png - $ pelican ~/Blog/content -o /tmp/out -t two-column - $ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html - $ pelican ~/Blog/content -o /tmp/out -t two-column - - -Notez que cette fonctionnalité nécessite d'avoir un système d'exploitation et un système de fichiers supportant les liens symboliques, elle n'est donc pas disponible sous Micro$oft®©™ Fenêtre®©™. - -Faire plusieurs choses à la fois -"""""""""""""""""""""""""""""""" - - -Les options ``--install``, ``--remove`` et ``--symlink`` peuvent être employées en même temps, ce qui permets de réaliser plusieurs opérations en même temps: - -.. code-block:: console - - # pelican-themes --remove notmyidea-cms two-column \ - --install ~/Dev/Python/pelican-themes/notmyidea-cms-fr \ - --symlink ~/Dev/Python/pelican-themes/two-column \ - --verbose - -Dans cette exemple, le thème ``notmyidea-cms`` sera remplacé par le thème ``notmyidea-cms-fr`` et le thème ``two-column`` sera lié symboliquement... - - - -À voir également -================ - -- http://docs.notmyidea.org/alexis/pelican/ -- ``/usr/share/doc/pelican/`` si vous avez installé Pelican par le `dépôt APT `_ - - - diff --git a/docs/fr/themes.rst b/docs/fr/themes.rst deleted file mode 100644 index 20d9d41f..00000000 --- a/docs/fr/themes.rst +++ /dev/null @@ -1,171 +0,0 @@ -.. _theming-pelican: - -Cette page est une traduction de la documentation originale, en anglais et -disponible `ici <../themes.html>`_. - -Comment créer des thèmes pour Pelican -##################################### - -Pelican utlise le très bon moteur de template `jinja2 `_ -pour produire de l'HTML. La syntaxe de jinja2 est vraiment très simple. Si vous -voulez créer votre propre thème, soyez libre de prendre inspiration sur le theme -"simple" qui est disponible `ici -`_ - -Structure -========= - -Pour réaliser votre propre thème vous devez respecter la structure suivante :: - - ├── static - │   ├── css - │   └── images - └── templates - ├── archives.html // pour afficher les archives - ├── article.html // généré pour chaque article - ├── categories.html // doit lister toutes les catégories - ├── category.html // généré pour chaque catégorie - ├── index.html // la page d'index, affiche tous les articles - ├── page.html // généré pour chaque page - ├── tag.html // généré pour chaque tag - └── tags.html // doit lister tous les tags. Peut être un nuage de tag. - - -* `static` contient tout le contenu statique. Il sera copié dans le dossier - `theme/static`. J'ai mis un dossier css et un image, mais ce sont juste des - exemples. Mettez ce dont vous avez besoin ici. - -* `templates` contient tous les templates qui vont être utiliser pour générer les - pages. J'ai juste mis les templates obligatoires ici, vous pouvez définir les - vôtres si cela vous aide à vous organiser pendant que vous réaliser le thème. - Vous pouvez par exemple utiliser les directives {% include %} et {% extends %} - de jinja2. - -Templates et variables -====================== - -Cela utilise une syntaxe simple, que vous pouvez insérer dans vos pages HTML. -Ce document décrit les templates qui doivent exister dans un thème, et quelles -variables seront passées à chaque template, au moment de le générer. - -Tous les templates recevront les variables définies dans votre fichier de -configuration, si elles sont en capitales. Vous pouvez y accéder directement. - -Variables communes ------------------- - -Toutes ces variables seront passées à chaque template. - -============= =================================================== -Variable Description -============= =================================================== -articles C'est la liste des articles, ordonnée décroissante - par date. Tous les éléments de la liste sont des - objets `Article`, vous pouvez donc accéder à leurs - propriétés (exemple : title, summary, author, etc). -dates La même liste d'articles, ordonnée croissante par - date. -tags Un dictionnaire contenant tous les tags (clés), et - la liste des articles correspondants à chacun - d'entre eux (valeur). -categories Un dictionnaire contenant toutes les catégories - (clés), et la liste des articles correspondants à - chacune d'entre elles (valeur). -pages La liste des pages. -============= =================================================== - -index.html ----------- - -La page d'accueil de votre blog, sera générée dans output/index.html. - -Si la pagination est activée, les pages suivantes seront à l'adresse -output/index`n`.html. - -=================== =================================================== -Variable Description -=================== =================================================== -articles_paginator Un objet paginator de la liste d'articles. -articles_page La page actuelle d'articles. -dates_paginator Un objet paginator de la liste d'articles, ordonné - par date croissante. -dates_pages La page actuelle d'articles, ordonnée par date - croissante. -page_name 'index'. -=================== =================================================== - -category.html -------------- - -Ce template sera généré pour chaque catégorie existante, et se retrouvera -finalement à output/category/`nom de la catégorie`.html. - -Si la pagination est activée, les pages suivantes seront disponibles à -l'adresse output/category/`nom de la catégorie``n`.html. - -=================== =================================================== -Variable Description -=================== =================================================== -category La catégorie qui est en train d'être générée. -articles Les articles dans cette catégorie. -dates Les articles dans cette catégorie, ordonnés par - date croissante. -articles_paginator Un objet paginator de la liste d'articles. -articles_page La page actuelle d'articles. -dates_paginator Un objet paginator de la liste d'articles, ordonné - par date croissante. -dates_pages La page actuelle d'articles, ordonnée par date - croissante. -page_name 'category/`nom de la catégorie`'. -=================== =================================================== - -article.html -------------- - -Ce template sera généré pour chaque article. Les fichiers .html seront -disponibles à output/`nom de l'article`.html. - -============= =================================================== -Variable Description -============= =================================================== -article L'objet article à afficher. -category Le nom de la catégorie de l'article actuel. -============= =================================================== - -page.html ---------- - -Pour chaque page ce template sera généré à l'adresse -output/`nom de la page`.html - -============= =================================================== -Variable Description -============= =================================================== -page L'objet page à afficher. Vous pouvez accéder à son - titre (title), slug, et son contenu (content). -============= =================================================== - -tag.html --------- - -Ce template sera généré pour chaque tag. Cela créera des fichiers .html à -l'adresse output/tag/`nom du tag`.html. - -Si la pagination est activée, les pages suivantes seront disponibles à -l'adresse output/tag/`nom du tag``n`.html - -=================== =================================================== -Variable Description -=================== =================================================== -tag Nom du tag à afficher. -articles Une liste des articles contenant ce tag. -dates Une liste des articles contenant ce tag, ordonnée - par date croissante. -articles_paginator Un objet paginator de la liste d'articles. -articles_page La page actuelle d'articles. -dates_paginator Un objet paginator de la liste d'articles, ordonné - par date croissante. -dates_pages La page actuelle d'articles, ordonnée par date - croissante. -page_name 'tag/`nom du tag`'. -=================== =================================================== diff --git a/docs/index.rst b/docs/index.rst index c2deb6de..aa30b1f0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -63,8 +63,6 @@ someone will almost always respond to your inquiry. Documentation ------------- -A French version of the documentation is available at :doc:`fr/index`. - .. toctree:: :maxdepth: 2 From 6d387f63f01b59166baa380718109871ab670976 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Fri, 9 May 2014 18:14:24 +0200 Subject: [PATCH 62/82] Fix intrasite links substitions in content The Content.__eq__ method would indirectly call _update_content too soon, resulting in failed intrasite links substitution This effectively reverts fd779267000ac539ee0a9ba5856d103fbbc7cd7c for pelican/contents.py, it was unnecessary anyways. Thanks to Strom for finding this. --- pelican/contents.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index c02047b8..615a7fd8 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -325,13 +325,6 @@ class Content(object): os.path.abspath(self.settings['PATH'])) ) - def __eq__(self, other): - """Compare with metadata and content of other Content object""" - return other and self.metadata == other.metadata and self.content == other.content - - # keep basic hashing functionality for caching to work - __hash__ = object.__hash__ - class Page(Content): mandatory_properties = ('title',) From f1a0af54ecd2b450abbda1a231f5b3faadf0fd52 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sat, 10 May 2014 17:38:58 +0200 Subject: [PATCH 63/82] Fix typo in command-line option description. --- pelican/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index d6417391..e9fef163 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -265,7 +265,7 @@ def parse_arguments(): parser.add_argument('-D', '--debug', action='store_const', const=logging.DEBUG, dest='verbosity', - help='Show all message, including debug messages.') + help='Show all messages, including debug messages.') parser.add_argument('--version', action='version', version=__version__, help='Print the pelican version and exit.') From 3b78e1525258c80b56e0502232c25eeb9880f92f Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 11 May 2014 18:14:58 -0700 Subject: [PATCH 64/82] Prepare for splitting up Getting Started docs --- docs/{getting_started.rst => content.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{getting_started.rst => content.rst} (100%) diff --git a/docs/getting_started.rst b/docs/content.rst similarity index 100% rename from docs/getting_started.rst rename to docs/content.rst From bb38f66e4ac942274935828f489faeff6f690284 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 12 May 2014 07:48:37 -0700 Subject: [PATCH 65/82] Split Getting Started docs into separate sections The "Getting Started" docs became overly long and unwieldy over time. This splits it into separate sections, including: * Quickstart * Installation * Writing content * Publish your site --- docs/content.rst | 307 ++-------------------------------------- docs/faq.rst | 12 +- docs/importer.rst | 6 +- docs/index.rst | 22 +-- docs/install.rst | 122 ++++++++++++++++ docs/pelican-themes.rst | 9 -- docs/publish.rst | 174 +++++++++++++++++++++++ docs/quickstart.rst | 73 ++++++++++ docs/settings.rst | 10 +- docs/themes.rst | 18 ++- 10 files changed, 420 insertions(+), 333 deletions(-) create mode 100644 docs/install.rst create mode 100644 docs/publish.rst create mode 100644 docs/quickstart.rst diff --git a/docs/content.rst b/docs/content.rst index 8ee37162..24fc6e9b 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -1,289 +1,8 @@ -Getting started +Writing content ############### -Installing Pelican -================== - -Pelican currently runs best on Python 2.7.x; earlier versions of Python are -not supported. There is provisional support for Python 3.3, although there may -be rough edges, particularly with regards to optional 3rd-party components. - -You can install Pelican via several different methods. The simplest is via -`pip `_:: - - $ pip install pelican - -If you don't have ``pip`` installed, an alternative method is -``easy_install``:: - - $ easy_install pelican - -(Keep in mind that operating systems will often require you to prefix the above -commands with ``sudo`` in order to install Pelican system-wide.) - -While the above is the simplest method, the recommended approach is to create -a virtual environment for Pelican via virtualenv_ before installing Pelican. -Assuming you have virtualenv_ installed, you can then open a new terminal -session and create a new virtual environment for Pelican:: - - $ virtualenv ~/virtualenvs/pelican - $ cd ~/virtualenvs/pelican - $ . bin/activate - -Once the virtual environment has been created and activated, Pelican can be -be installed via ``pip install pelican`` as noted above. Alternatively, if -you have the project source, you can install Pelican using the distutils -method:: - - $ cd path-to-Pelican-source - $ python setup.py install - -If you have Git installed and prefer to install the latest bleeding-edge -version of Pelican rather than a stable release, use the following command:: - - $ pip install -e git+https://github.com/getpelican/pelican.git#egg=pelican - -If you plan on using Markdown as a markup format, you'll need to install the -Markdown library as well:: - - $ pip install Markdown - -If you want to use AsciiDoc_ you need to install it from `source -`_ or use your operating -system's package manager. - -Basic usage ------------ - -Once Pelican is installed, you can use it to convert your Markdown or reST -content into HTML via the ``pelican`` command, specifying the path to your -content and (optionally) the path to your settings file:: - -$ pelican /path/to/your/content/ [-s path/to/your/settings.py] - -The above command will generate your site and save it in the ``output/`` -folder, using the default theme to produce a simple site. The default theme -consists of very simple HTML without styling and is provided so folks may use -it as a basis for creating their own themes. - -You can also tell Pelican to watch for your modifications, instead of -manually re-running it every time you want to see your changes. To enable this, -run the ``pelican`` command with the ``-r`` or ``--autoreload`` option. - -Pelican has other command-line switches available. Have a look at the help to -see all the options you can use:: - - $ pelican --help - -Continue reading below for more detail, and check out the Pelican wiki's -`Tutorials `_ page for -links to community-published tutorials. - -Viewing the generated files ---------------------------- - -The files generated by Pelican are static files, so you don't actually need -anything special to view them. You can use your browser to open the generated -HTML files directly:: - - firefox output/index.html - -Because the above method may have trouble locating your CSS and other linked -assets, running a simple web server using Python will often provide a more -reliable previewing experience:: - - cd output && python -m SimpleHTTPServer - -Once the ``SimpleHTTPServer`` has been started, you can preview your site at -http://localhost:8000/ - -Upgrading ---------- - -If you installed a stable Pelican release via ``pip`` or ``easy_install`` and -wish to upgrade to the latest stable release, you can do so by adding -``--upgrade`` to the relevant command. For pip, that would be:: - - $ pip install --upgrade pelican - -If you installed Pelican via distutils or the bleeding-edge method, simply -perform the same step to install the most recent version. - -Dependencies ------------- - -When Pelican is installed, the following dependent Python packages should be -automatically installed without any action on your part: - -* `feedgenerator `_, to generate the - Atom feeds -* `jinja2 `_, for templating support -* `pygments `_, for syntax highlighting -* `docutils `_, for supporting - reStructuredText as an input format -* `pytz `_, for timezone definitions -* `blinker `_, an object-to-object and - broadcast signaling system -* `unidecode `_, for ASCII - transliterations of Unicode text -* `six `_, for Python 2 and 3 compatibility - utilities -* `MarkupSafe `_, for a markup safe - string implementation -* `python-dateutil `_, to read - the date metadata - -If you want the following optional packages, you will need to install them -manually via ``pip``: - -* `markdown `_, for supporting Markdown as - an input format -* `typogrify `_, for - typographical enhancements - -Kickstart your site -=================== - -Once Pelican has been installed, you can create a skeleton project via the -``pelican-quickstart`` command, which begins by asking some questions about -your site:: - - $ pelican-quickstart - -Once you finish answering all the questions, your project will consist of the -following hierarchy (except for "pages", which you can optionally add yourself -if you plan to create non-chronological content):: - - yourproject/ - ├── content - │   └── (pages) - ├── output - ├── develop_server.sh - ├── fabfile.py - ├── Makefile - ├── pelicanconf.py # Main settings file - └── publishconf.py # Settings to use when ready to publish - -The next step is to begin to adding content to the *content* folder that has -been created for you. (See the **Writing content using Pelican** section below -for more information about how to format your content.) - -Once you have written some content to generate, you can use the ``pelican`` -command to generate your site, which will be placed in the output folder. - -Automation tools -================ - -While the ``pelican`` command is the canonical way to generate your site, -automation tools can be used to streamline the generation and publication -flow. One of the questions asked during the ``pelican-quickstart`` process -described above pertains to whether you want to automate site generation and -publication. If you answered "yes" to that question, a ``fabfile.py`` and -``Makefile`` will be generated in the root of your project. These files, -pre-populated with certain information gleaned from other answers provided -during the ``pelican-quickstart`` process, are meant as a starting point and -should be customized to fit your particular needs and usage patterns. If you -find one or both of these automation tools to be of limited utility, these -files can deleted at any time and will not affect usage of the canonical -``pelican`` command. - -Following are automation tools that "wrap" the ``pelican`` command and can -simplify the process of generating, previewing, and uploading your site. - -Fabric ------- - -The advantage of Fabric_ is that it is written in Python and thus can be used -in a wide range of environments. The downside is that it must be installed -separately. Use the following command to install Fabric, prefixing with -``sudo`` if your environment requires it:: - - $ pip install Fabric - -Take a moment to open the ``fabfile.py`` file that was generated in your -project root. You will see a number of commands, any one of which can be -renamed, removed, and/or customized to your liking. Using the out-of-the-box -configuration, you can generate your site via:: - - $ fab build - -If you'd prefer to have Pelican automatically regenerate your site every time a -change is detected (which is handy when testing locally), use the following -command instead:: - - $ fab regenerate - -To serve the generated site so it can be previewed in your browser at -http://localhost:8000/:: - - $ fab serve - -If during the ``pelican-quickstart`` process you answered "yes" when asked -whether you want to upload your site via SSH, you can use the following command -to publish your site via rsync over SSH:: - - $ fab publish - -These are just a few of the commands available by default, so feel free to -explore ``fabfile.py`` and see what other commands are available. More -importantly, don't hesitate to customize ``fabfile.py`` to suit your specific -needs and preferences. - -Make ----- - -A ``Makefile`` is also automatically created for you when you say "yes" to -the relevant question during the ``pelican-quickstart`` process. The advantage -of this method is that the ``make`` command is built into most POSIX systems -and thus doesn't require installing anything else in order to use it. The -downside is that non-POSIX systems (e.g., Windows) do not include ``make``, -and installing it on those systems can be a non-trivial task. - -If you want to use ``make`` to generate your site, run:: - - $ make html - -If you'd prefer to have Pelican automatically regenerate your site every time a -change is detected (which is handy when testing locally), use the following -command instead:: - - $ make regenerate - -To serve the generated site so it can be previewed in your browser at -http://localhost:8000/:: - - $ make serve - -Normally you would need to run ``make regenerate`` and ``make serve`` in two -separate terminal sessions, but you can run both at once via:: - - $ make devserver - -The above command will simultaneously run Pelican in regeneration mode as well -as serve the output at http://localhost:8000. Once you are done testing your -changes, you should stop the development server via:: - - $ ./develop_server.sh stop - -When you're ready to publish your site, you can upload it via the method(s) you -chose during the ``pelican-quickstart`` questionnaire. For this example, we'll -use rsync over ssh:: - - $ make rsync_upload - -That's it! Your site should now be live. - -(The default ``Makefile`` and ``devserver.sh`` scripts use the ``python`` and -``pelican`` executables to complete its tasks. If you want to use different -executables, such as ``python3``, you can set the ``PY`` and ``PELICAN`` -environment variables, respectively, to override the default executable names.) - - -Writing content using Pelican -============================= - Articles and pages ------------------- +================== Pelican considers "articles" to be chronological content, such as posts on a blog, and thus associated with a date. @@ -295,7 +14,7 @@ pages). .. _internal_metadata: File metadata -------------- +============= Pelican tries to be smart enough to get the information it needs from the file system (for instance, about the category of your articles), but some @@ -400,7 +119,7 @@ Please note that the metadata available inside your files takes precedence over the metadata extracted from the filename. Pages ------ +===== If you create a folder named ``pages`` inside the content folder, all the files in it will be used to generate static pages, such as **About** or @@ -416,7 +135,7 @@ things like making error pages that fit the generated theme of your site. .. _ref-linking-to-internal-content: Linking to internal content ---------------------------- +=========================== From Pelican 3.1 onwards, it is now possible to specify intra-site links to files in the *source content* hierarchy instead of files in the *generated* @@ -494,14 +213,14 @@ curly braces (``{}``). For example: ``|filename|an_article.rst``, ``|tag|tagname``, ``|category|foobar``. The syntax was changed from ``||`` to ``{}`` to avoid collision with Markdown extensions or reST directives. -Importing an existing blog --------------------------- +Importing an existing site +========================== -It is possible to import your blog from Dotclear, WordPress, and RSS feeds using -a simple script. See :ref:`import`. +It is possible to import your site from WordPress, Tumblr, Dotclear, and RSS +feeds using a simple script. See :ref:`import`. Translations ------------- +============ It is possible to translate articles. To do so, you need to add a ``lang`` meta attribute to your articles/pages and set a ``DEFAULT_LANG`` setting (which is @@ -559,7 +278,7 @@ which posts are translations:: .. _internal_pygments_options: Syntax highlighting -------------------- +=================== Pelican is able to provide colorized syntax highlighting for your code blocks. To do so, you have to use the following conventions inside your content files. @@ -641,14 +360,12 @@ If specified, settings for individual code blocks will override the defaults in your settings file. Publishing drafts ------------------ +================= If you want to publish an article as a draft (for friends to review before publishing, for example), you can add a ``Status: draft`` attribute to its metadata. That article will then be output to the ``drafts`` folder and not listed on the index page nor on any category or tag page. -.. _virtualenv: http://www.virtualenv.org/ .. _W3C ISO 8601: http://www.w3.org/TR/NOTE-datetime -.. _Fabric: http://fabfile.org/ .. _AsciiDoc: http://www.methods.co.nz/asciidoc/ diff --git a/docs/faq.rst b/docs/faq.rst index bf468c51..3a5cfec5 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -65,9 +65,11 @@ How do I create my own theme? Please refer to :ref:`theming-pelican`. -I'm using Markdown and getting ``No valid files found in content`` errors. +I want to use Markdown, but I got an error. ========================================================================== +If you try to generate Markdown content without first installing the Markdown +library, may see a message that says ``No valid files found in content``. Markdown is not a hard dependency for Pelican, so if you have content in Markdown format, you will need to explicitly install the Markdown library. You can do so by typing the following command, prepending ``sudo`` if @@ -75,10 +77,6 @@ permissions require it:: pip install markdown -If you don't have ``pip`` installed, consider installing it via:: - - easy_install pip - Can I use arbitrary metadata in my templates? ============================================== @@ -157,8 +155,8 @@ disable all feed generation, you only need to specify the following settings:: CATEGORY_FEED_ATOM = None TRANSLATION_FEED_ATOM = None -Please note that ``None`` and ``''`` are not the same thing. The word ``None`` -should not be surrounded by quotes. +The word ``None`` should not be surrounded by quotes. Please note that ``None`` +and ``''`` are not the same thing. I'm getting a warning about feeds generated without SITEURL being set properly ============================================================================== diff --git a/docs/importer.rst b/docs/importer.rst index b1d1b926..309ca144 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -1,9 +1,7 @@ .. _import: -================================= - Import from other blog software -================================= - +Importing an existing site +########################## Description =========== diff --git a/docs/index.rst b/docs/index.rst index aa30b1f0..36a3282b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,30 +10,32 @@ Pelican |release| Were you looking for version |last_stable| documentation? -Pelican is a static site generator, written in Python_. +Pelican is a static site generator, written in Python_. Highlights include: -* Write your content directly with your editor of choice (vim!) +* Write your content directly with your editor of choice in reStructuredText_, Markdown_, or AsciiDoc_ formats * Includes a simple CLI tool to (re)generate your site * Easy to interface with distributed version control systems and web hooks * Completely static output is easy to host anywhere +Ready to get started? Check out the :doc:`Quickstart` guide. + Features -------- Pelican |version| currently supports: * Articles (e.g., blog posts) and pages (e.g., "About", "Projects", "Contact") -* Comments, via an external service (Disqus). (Please note that while - useful, Disqus is an external service, and thus the comment data will be - somewhat outside of your control and potentially subject to data loss.) +* Comments, via an external service (Disqus). If you prefer to have more + control over your comment data, self-hosted comments are another option. + Check out the `Pelican Plugins`_ repository for more details. * Theming support (themes are created using Jinja2_ templates) * Publication of articles in multiple languages * Atom/RSS feeds * Code syntax highlighting * Import from WordPress, Dotclear, or RSS feeds * Integration with external tools: Twitter, Google Analytics, etc. (optional) -* Fast rebuild times thanks to content caching and selective output writing. +* Fast rebuild times thanks to content caching and selective output writing Why the name "Pelican"? ----------------------- @@ -66,16 +68,19 @@ Documentation .. toctree:: :maxdepth: 2 - getting_started + quickstart + install + content + publish settings themes plugins - internals pelican-themes importer faq tips contribute + internals report changelog @@ -88,5 +93,6 @@ Documentation .. _Jinja2: http://jinja.pocoo.org/ .. _`Pelican documentation`: http://docs.getpelican.com/latest/ .. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html +.. _`Pelican plugins`: https://github.com/getpelican/pelican-plugins .. _`#pelican on Freenode`: irc://irc.freenode.net/pelican .. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4 diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..34cd33ea --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,122 @@ +Installing Pelican +################## + +Pelican currently runs best on Python 2.7.x; earlier versions of Python are +not supported. There is provisional support for Python 3.3+, although there may +be rough edges, particularly with regards to optional 3rd-party components. + +You can install Pelican via several different methods. The simplest is via +`pip `_:: + + pip install pelican + +(Keep in mind that operating systems will often require you to prefix the above +command with ``sudo`` in order to install Pelican system-wide.) + +While the above is the simplest method, the recommended approach is to create +a virtual environment for Pelican via virtualenv_ before installing Pelican. +Assuming you have virtualenv_ installed, you can then open a new terminal +session and create a new virtual environment for Pelican:: + + virtualenv ~/virtualenvs/pelican + cd ~/virtualenvs/pelican + source bin/activate + +Once the virtual environment has been created and activated, Pelican can be +be installed via ``pip install pelican`` as noted above. Alternatively, if +you have the project source, you can install Pelican using the distutils +method:: + + cd path-to-Pelican-source + python setup.py install + +If you have Git installed and prefer to install the latest bleeding-edge +version of Pelican rather than a stable release, use the following command:: + + pip install -e "git+https://github.com/getpelican/pelican.git#egg=pelican" + +Once Pelican is installed, you can run ``pelican --help`` to see basic usage +options. For more detail, refer to the :doc:`Publish` section. + +Optional packages +----------------- + +If you plan on using `Markdown `_ as a +markup format, you'll need to install the Markdown library:: + + pip install Markdown + +Typographical enhancements can be enabled in your settings file, but first the +requisite `Typogrify `_ library must be +installed:: + + pip install typogrify + +If you want to use AsciiDoc_ you need to install it from `source +`_ or use your operating +system's package manager. + +Dependencies +------------ + +When Pelican is installed, the following dependent Python packages should be +automatically installed without any action on your part: + +* `feedgenerator `_, to generate the + Atom feeds +* `jinja2 `_, for templating support +* `pygments `_, for syntax highlighting +* `docutils `_, for supporting + reStructuredText as an input format +* `pytz `_, for timezone definitions +* `blinker `_, an object-to-object and + broadcast signaling system +* `unidecode `_, for ASCII + transliterations of Unicode text +* `six `_, for Python 2 and 3 compatibility + utilities +* `MarkupSafe `_, for a markup safe + string implementation +* `python-dateutil `_, to read + the date metadata + +Upgrading +--------- + +If you installed a stable Pelican release via ``pip`` or ``easy_install`` and +wish to upgrade to the latest stable release, you can do so by adding +``--upgrade`` to the relevant command. For pip, that would be:: + + pip install --upgrade pelican + +If you installed Pelican via distutils or the bleeding-edge method, simply +perform the same step to install the most recent version. + +Kickstart your site +=================== + +Once Pelican has been installed, you can create a skeleton project via the +``pelican-quickstart`` command, which begins by asking some questions about +your site:: + + pelican-quickstart + +Once you finish answering all the questions, your project will consist of the +following hierarchy (except for *pages* — shown in parentheses below — which you +can optionally add yourself if you plan to create non-chronological content):: + + yourproject/ + ├── content + │   └── (pages) + ├── output + ├── develop_server.sh + ├── fabfile.py + ├── Makefile + ├── pelicanconf.py # Main settings file + └── publishconf.py # Settings to use when ready to publish + +The next step is to begin to adding content to the *content* folder that has +been created for you. + +.. _virtualenv: http://www.virtualenv.org/ +.. _AsciiDoc: http://www.methods.co.nz/asciidoc/ diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst index 23be8355..7090c648 100644 --- a/docs/pelican-themes.rst +++ b/docs/pelican-themes.rst @@ -153,12 +153,3 @@ The ``--install``, ``--remove`` and ``--symlink`` option are not mutually exclus --verbose In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr`` - - - - -See also -======== - -- http://docs.notmyidea.org/alexis/pelican/ -- ``/usr/share/doc/pelican/`` if you have installed Pelican using the `APT repository `_ diff --git a/docs/publish.rst b/docs/publish.rst new file mode 100644 index 00000000..266009e4 --- /dev/null +++ b/docs/publish.rst @@ -0,0 +1,174 @@ +Publish your site +################# + +Site generation +=============== + +Once Pelican is installed and you have some content (e.g., in Markdown or reST +format), you can convert your content into HTML via the ``pelican`` command, +specifying the path to your content and (optionally) the path to your +:doc:`settings` file:: + + pelican /path/to/your/content/ [-s path/to/your/settings.py] + +The above command will generate your site and save it in the ``output/`` +folder, using the default theme to produce a simple site. The default theme +consists of very simple HTML without styling and is provided so folks may use +it as a basis for creating their own themes. + +You can also tell Pelican to watch for your modifications, instead of +manually re-running it every time you want to see your changes. To enable this, +run the ``pelican`` command with the ``-r`` or ``--autoreload`` option. + +Pelican has other command-line switches available. Have a look at the help to +see all the options you can use:: + + pelican --help + +Viewing the generated files +--------------------------- + +The files generated by Pelican are static files, so you don't actually need +anything special to view them. You can use your browser to open the generated +HTML files directly:: + + firefox output/index.html + +Because the above method may have trouble locating your CSS and other linked +assets, running a simple web server using Python will often provide a more +reliable previewing experience:: + + cd output + python -m SimpleHTTPServer + +Once the ``SimpleHTTPServer`` has been started, you can preview your site at +http://localhost:8000/ + +Deployment +========== + +After you have generated your site, previewed it in your local development +environment, and are ready to deploy it to production, you might first +re-generate your site with any production-specific settings (e.g., analytics +feeds, etc.) that you may have defined:: + + pelican content -s publishconf.py + +The steps for deploying your site will depend on where it will be hosted. +If you have SSH access to a server running Nginx or Apache, you might use the +``rsync`` tool to transmit your site files:: + + rsync --avc --delete output/ host.example.com:/var/www/your-site/ + +There are many other deployment options, some of which can be configured when +first setting up your site via the ``pelican-quickstart`` command. See the +:doc:`Tips` page for detail on publishing via GitHub Pages. + +Automation +========== + +While the ``pelican`` command is the canonical way to generate your site, +automation tools can be used to streamline the generation and publication +flow. One of the questions asked during the ``pelican-quickstart`` process +pertains to whether you want to automate site generation and publication. +If you answered "yes" to that question, a ``fabfile.py`` and +``Makefile`` will be generated in the root of your project. These files, +pre-populated with certain information gleaned from other answers provided +during the ``pelican-quickstart`` process, are meant as a starting point and +should be customized to fit your particular needs and usage patterns. If you +find one or both of these automation tools to be of limited utility, these +files can deleted at any time and will not affect usage of the canonical +``pelican`` command. + +Following are automation tools that "wrap" the ``pelican`` command and can +simplify the process of generating, previewing, and uploading your site. + +Fabric +------ + +The advantage of Fabric_ is that it is written in Python and thus can be used +in a wide range of environments. The downside is that it must be installed +separately. Use the following command to install Fabric, prefixing with +``sudo`` if your environment requires it:: + + pip install Fabric + +Take a moment to open the ``fabfile.py`` file that was generated in your +project root. You will see a number of commands, any one of which can be +renamed, removed, and/or customized to your liking. Using the out-of-the-box +configuration, you can generate your site via:: + + fab build + +If you'd prefer to have Pelican automatically regenerate your site every time a +change is detected (which is handy when testing locally), use the following +command instead:: + + fab regenerate + +To serve the generated site so it can be previewed in your browser at +http://localhost:8000/:: + + fab serve + +If during the ``pelican-quickstart`` process you answered "yes" when asked +whether you want to upload your site via SSH, you can use the following command +to publish your site via rsync over SSH:: + + fab publish + +These are just a few of the commands available by default, so feel free to +explore ``fabfile.py`` and see what other commands are available. More +importantly, don't hesitate to customize ``fabfile.py`` to suit your specific +needs and preferences. + +Make +---- + +A ``Makefile`` is also automatically created for you when you say "yes" to +the relevant question during the ``pelican-quickstart`` process. The advantage +of this method is that the ``make`` command is built into most POSIX systems +and thus doesn't require installing anything else in order to use it. The +downside is that non-POSIX systems (e.g., Windows) do not include ``make``, +and installing it on those systems can be a non-trivial task. + +If you want to use ``make`` to generate your site, run:: + + make html + +If you'd prefer to have Pelican automatically regenerate your site every time a +change is detected (which is handy when testing locally), use the following +command instead:: + + make regenerate + +To serve the generated site so it can be previewed in your browser at +http://localhost:8000/:: + + make serve + +Normally you would need to run ``make regenerate`` and ``make serve`` in two +separate terminal sessions, but you can run both at once via:: + + make devserver + +The above command will simultaneously run Pelican in regeneration mode as well +as serve the output at http://localhost:8000. Once you are done testing your +changes, you should stop the development server via:: + + ./develop_server.sh stop + +When you're ready to publish your site, you can upload it via the method(s) you +chose during the ``pelican-quickstart`` questionnaire. For this example, we'll +use rsync over ssh:: + + make rsync_upload + +That's it! Your site should now be live. + +(The default ``Makefile`` and ``devserver.sh`` scripts use the ``python`` and +``pelican`` executables to complete its tasks. If you want to use different +executables, such as ``python3``, you can set the ``PY`` and ``PELICAN`` +environment variables, respectively, to override the default executable names.) + +.. _Fabric: http://fabfile.org/ diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 00000000..44f99dd2 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,73 @@ +Quickstart +########## + +Reading through all the documentation is highly recommended, but for the truly +impatient, following are some quick steps to get started. + +Installation +------------ + +Install Pelican on Python 2.7.x or Python 3.3+ by running the following command +in your preferred terminal, prefixing with ``sudo`` if permissions warrant:: + + pip install pelican markdown + +Create a project +---------------- + +First, choose a name for your project, create an appropriately-named directory +for your it, and switch to that directory:: + + mkdir -p ~/projects/yoursite + cd ~/projects/yoursite + +Create a skeleton project via the ``pelican-quickstart`` command, which begins +by asking some questions about your site:: + + pelican-quickstart + +For questions that have default values denoted in brackets, feel free to use +the Return key to accept those default values. When asked for your URL prefix, +enter your domain name as indicated (e.g., ``http://example.com``). + +Create an article +----------------- + +You cannot run Pelican until you have created some content. Use your preferred +text editor to create your first article with the following content:: + + Title: My First Review + Date: 2010-12-03 10:20 + Category: Review + + Following is a review of my favorite mechanical keyboard. + +Given that this example article is in Markdown format, save it as +``~/projects/yoursite/content/keyboard-review.md``. + +Generate your site +------------------ + +From your project directory, run the ``pelican`` command to generate your site:: + + pelican content + +Your site has now been generated inside the ``output`` directory. (You may see a +warning related to feeds, but that is normal when developing locally and can be +ignored for now.) + +Preview your site +----------------- + +Open a new terminal session and run the following commands to switch to your +``output`` directory and launch Python's built-in web server:: + + cd ~/projects/yoursite/output + python -m SimpleHTTPServer + +Preview your site by navigating to http://localhost:8000/ in your browser. + +Continue reading the other documentation sections for more detail, and check out +the Pelican wiki's Tutorials_ page for links to community-published tutorials. + +.. _Tutorials: https://github.com/getpelican/pelican/wiki/Tutorials diff --git a/docs/settings.rst b/docs/settings.rst index 2782977c..7a69784e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1,10 +1,10 @@ Settings ######## -Pelican is configurable thanks to a configuration file you can pass to +Pelican is configurable thanks to a settings file you can pass to the command line:: - $ pelican content -s path/to/your/settingsfile.py + pelican content -s path/to/your/settingsfile.py (If you used the ``pelican-quickstart`` command, your primary settings file will be named ``pelicanconf.py`` by default.) @@ -201,8 +201,8 @@ for URL formation: *relative* and *absolute*. Relative URLs are useful when testing locally, and absolute URLs are reliable and most useful when publishing. One method of supporting both is to have one Pelican configuration file for local development and another for publishing. To see an example of this -type of setup, use the ``pelican-quickstart`` script as described at the top of -the :doc:`Getting Started ` page, which will produce two separate +type of setup, use the ``pelican-quickstart`` script as described in the +:doc:`Installation ` section, which will produce two separate configuration files for local development and publishing, respectively. You can customize the URLs and locations where files will be saved. The @@ -603,7 +603,7 @@ For example:: Translations ============ -Pelican offers a way to translate articles. See the :doc:`Getting Started ` section for +Pelican offers a way to translate articles. See the :doc:`Content ` section for more information. ======================================================== ===================================================== diff --git a/docs/themes.rst b/docs/themes.rst index b029e816..7fcba671 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -1,13 +1,21 @@ .. _theming-pelican: -How to create themes for Pelican -################################ +Creating themes +############### -Pelican uses the great `Jinja2 `_ templating engine to -generate its HTML output. Jinja2 syntax is really simple. If you want to -create your own theme, feel free to take inspiration from the `"simple" theme +To generate its HTML output, Pelican uses the `Jinja `_ +templating engine due to its flexibility and straightforward syntax. If you want +to create your own theme, feel free to take inspiration from the `"simple" theme `_. +To generate your site using a theme you have created (or downloaded manually and +then modified), you can specify that theme via the ``-t`` flag:: + + pelican content -s pelicanconf.py -t /projects/your-site/themes/your-theme + +If you'd rather not specify the theme on every invocation, you can define +``THEME`` in your settings to point to the location of your preferred theme. + Structure ========= From cb55efd998cbd03a8696cbb8e7d54f1b2b99e9d8 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 13 May 2014 07:18:33 -0700 Subject: [PATCH 66/82] If PATH is undefined, Pelican uses PWD as content --- docs/settings.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 7a69784e..4701e92d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -94,7 +94,7 @@ Setting name (followed by default value, if any) ``READERS = {}`` A dictionary of file extensions / Reader classes for Pelican to process or ignore. For example, to avoid processing .html files, set: ``READERS = {'html': None}``. To add a custom reader for the - `foo` extension, set: ``READERS = {'foo': FooReader}`` + ``foo`` extension, set: ``READERS = {'foo': FooReader}`` ``IGNORE_FILES = ['.#*']`` A list of file globbing patterns to match against the source files to be ignored by the processor. For example, the default ``['.#*']`` will ignore emacs lock files. @@ -108,10 +108,12 @@ Setting name (followed by default value, if any) include them explicitly and enumerate the full list of desired Markdown extensions.) ``OUTPUT_PATH = 'output/'`` Where to output the generated files. -``PATH = None`` Path to content directory to be processed by Pelican. -``PAGE_DIR = 'pages'`` Directory to look at for pages, relative to `PATH`. +``PATH`` Path to content directory to be processed by Pelican. If undefined, + and content path is not specified via an argument to the ``pelican`` + command, Pelican will use the current working directory. +``PAGE_DIR = 'pages'`` Directory to look at for pages, relative to ``PATH``. ``PAGE_EXCLUDES = ()`` A list of directories to exclude when looking for pages. -``ARTICLE_DIR = ''`` Directory to look at for articles, relative to `PATH`. +``ARTICLE_DIR = ''`` Directory to look at for articles, relative to ``PATH``. ``ARTICLE_EXCLUDES = ('pages',)`` A list of directories to exclude when looking for articles. ``OUTPUT_SOURCES = False`` Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the From 0d0ee07a2782c3b0460df8e60cc4270bc2e979ee Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Mon, 21 Apr 2014 11:36:17 +0200 Subject: [PATCH 67/82] move {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS Instead of one path a list can be given. This is due to popular request. Should help people not wanting to use Pelican for blogging. Maintain backward compatibility though. Thanks to @ingwinlu for pointing out the change in StaticGenerator. --- docs/settings.rst | 4 +-- pelican/generators.py | 60 +++++++++++++++----------------- pelican/settings.py | 26 ++++++++++---- pelican/tests/test_generators.py | 2 +- 4 files changed, 51 insertions(+), 41 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 4701e92d..33117a7f 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -111,9 +111,9 @@ Setting name (followed by default value, if any) ``PATH`` Path to content directory to be processed by Pelican. If undefined, and content path is not specified via an argument to the ``pelican`` command, Pelican will use the current working directory. -``PAGE_DIR = 'pages'`` Directory to look at for pages, relative to ``PATH``. +``PAGE_PATHS = ['pages']`` A list of directories to look at for pages, relative to ``PATH``. ``PAGE_EXCLUDES = ()`` A list of directories to exclude when looking for pages. -``ARTICLE_DIR = ''`` Directory to look at for articles, relative to ``PATH``. +``ARTICLE_PATHS = ['']`` A list of directories to look at for articles, relative to ``PATH``. ``ARTICLE_EXCLUDES = ('pages',)`` A list of directories to exclude when looking for articles. ``OUTPUT_SOURCES = False`` Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the diff --git a/pelican/generators.py b/pelican/generators.py index 7c6ba66b..deb237a2 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -110,29 +110,30 @@ class Generator(object): return True return False - def get_files(self, path, exclude=[], extensions=None): + def get_files(self, paths, exclude=[], extensions=None): """Return a list of files to use, based on rules - :param path: the path to search (relative to self.path) + :param paths: the list pf paths to search (relative to self.path) :param exclude: the list of path to exclude :param extensions: the list of allowed extensions (if False, all extensions are allowed) """ files = [] - root = os.path.join(self.path, path) + for path in paths: + root = os.path.join(self.path, path) - if os.path.isdir(root): - for dirpath, dirs, temp_files in os.walk(root, followlinks=True): - for e in exclude: - if e in dirs: - dirs.remove(e) - reldir = os.path.relpath(dirpath, self.path) - for f in temp_files: - fp = os.path.join(reldir, f) - if self._include_path(fp, extensions): - files.append(fp) - elif os.path.exists(root) and self._include_path(path, extensions): - files.append(path) # can't walk non-directories + if os.path.isdir(root): + for dirpath, dirs, temp_files in os.walk(root, followlinks=True): + for e in exclude: + if e in dirs: + dirs.remove(e) + reldir = os.path.relpath(dirpath, self.path) + for f in temp_files: + fp = os.path.join(reldir, f) + if self._include_path(fp, extensions): + files.append(fp) + elif os.path.exists(root) and self._include_path(path, extensions): + files.append(path) # can't walk non-directories return files def add_source_path(self, content): @@ -462,7 +463,7 @@ class ArticlesGenerator(CachingGenerator): all_articles = [] all_drafts = [] for f in self.get_files( - self.settings['ARTICLE_DIR'], + self.settings['ARTICLE_PATHS'], exclude=self.settings['ARTICLE_EXCLUDES']): article = self.get_cached_data(f, None) if article is None: @@ -586,7 +587,7 @@ class PagesGenerator(CachingGenerator): all_pages = [] hidden_pages = [] for f in self.get_files( - self.settings['PAGE_DIR'], + self.settings['PAGE_PATHS'], exclude=self.settings['PAGE_EXCLUDES']): page = self.get_cached_data(f, None) if page is None: @@ -660,20 +661,17 @@ class StaticGenerator(Generator): def generate_context(self): self.staticfiles = [] - - # walk static paths - for static_path in self.settings['STATIC_PATHS']: - for f in self.get_files( - static_path, extensions=False): - static = self.readers.read_file( - base_path=self.path, path=f, content_class=Static, - fmt='static', context=self.context, - preread_signal=signals.static_generator_preread, - preread_sender=self, - context_signal=signals.static_generator_context, - context_sender=self) - self.staticfiles.append(static) - self.add_source_path(static) + for f in self.get_files(self.settings['STATIC_PATHS'], + extensions=False): + static = self.readers.read_file( + base_path=self.path, path=f, content_class=Static, + fmt='static', context=self.context, + preread_signal=signals.static_generator_preread, + preread_sender=self, + context_signal=signals.static_generator_context, + context_sender=self) + self.staticfiles.append(static) + self.add_source_path(static) self._update_context(('staticfiles',)) signals.static_generator_finalized.send(self) diff --git a/pelican/settings.py b/pelican/settings.py index f759ff9e..219ebbd0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -29,9 +29,9 @@ DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'themes', 'notmyidea') DEFAULT_CONFIG = { 'PATH': os.curdir, - 'ARTICLE_DIR': '', + 'ARTICLE_PATHS': [''], 'ARTICLE_EXCLUDES': ('pages',), - 'PAGE_DIR': 'pages', + 'PAGE_PATHS': ['pages'], 'PAGE_EXCLUDES': (), 'THEME': DEFAULT_THEME, 'OUTPUT_PATH': 'output', @@ -311,6 +311,16 @@ def configure_settings(settings): key=lambda r: r[0], ) + # move {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS + for key in ['ARTICLE', 'PAGE']: + old_key = key + '_DIR' + new_key = key + '_PATHS' + if old_key in settings: + logger.warning('Deprecated setting {}, moving it to {} list'.format( + old_key, new_key)) + settings[new_key] = [settings[old_key]] # also make a list + del settings[old_key] + # Save people from accidentally setting a string rather than a list path_keys = ( 'ARTICLE_EXCLUDES', @@ -324,13 +334,15 @@ def configure_settings(settings): 'PLUGINS', 'STATIC_PATHS', 'THEME_STATIC_PATHS', + 'ARTICLE_PATHS', + 'PAGE_PATHS', ) for PATH_KEY in filter(lambda k: k in settings, path_keys): - if isinstance(settings[PATH_KEY], six.string_types): - logger.warning("Detected misconfiguration with %s setting " - "(must be a list), falling back to the default" - % PATH_KEY) - settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY] + if isinstance(settings[PATH_KEY], six.string_types): + logger.warning("Detected misconfiguration with %s setting " + "(must be a list), falling back to the default" + % PATH_KEY) + settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY] for old, new, doc in [ ('LESS_GENERATOR', 'the Webassets plugin', None), diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 7b79e8f3..668a0093 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -372,8 +372,8 @@ class TestPageGenerator(unittest.TestCase): def test_generate_context(self): settings = get_settings(filenames={}) - settings['PAGE_DIR'] = 'TestPages' # relative to CUR_DIR settings['CACHE_PATH'] = self.temp_cache + settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR settings['DEFAULT_DATE'] = (1970, 1, 1) generator = PagesGenerator( From fe10d58aa423e88ed9bcfaa1f24c00db0764267c Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Wed, 14 May 2014 14:06:58 +0200 Subject: [PATCH 68/82] Fix #1344 move PLUGIN_PATH -> PLUGIN_PATHS Pelican uses *_PATHS names for settings that represent a list of paths. --- docs/plugins.rst | 6 +++--- docs/settings.rst | 1 + pelican/__init__.py | 4 ++-- pelican/settings.py | 20 ++++++++++++-------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index 717019a8..a13d9dce 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -21,10 +21,10 @@ Alternatively, another method is to import them and add them to the list:: PLUGINS = [myplugin,] If your plugins are not in an importable path, you can specify a list of paths -via the ``PLUGIN_PATH`` setting. As shown in the following example, paths in -the ``PLUGIN_PATH`` list can be absolute or relative to the settings file:: +via the ``PLUGIN_PATHS`` setting. As shown in the following example, paths in +the ``PLUGIN_PATHS`` list can be absolute or relative to the settings file:: - PLUGIN_PATH = ["plugins", "/srv/pelican/plugins"] + PLUGIN_PATHS = ["plugins", "/srv/pelican/plugins"] PLUGINS = ["assets", "liquid_tags", "sitemap"] Where to find plugins diff --git a/docs/settings.rst b/docs/settings.rst index 33117a7f..34245ff4 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -125,6 +125,7 @@ Setting name (followed by default value, if any) not. Only set this to ``True`` when developing/testing and only if you fully understand the effect it can have on links/feeds. ``PLUGINS = []`` The list of plugins to load. See :ref:`plugins`. +``PLUGIN_PATHS = []`` A list of directories where to look for plugins. See :ref:`plugins`. ``SITENAME = 'A Pelican Blog'`` Your site name ``SITEURL`` Base URL of your website. Not defined by default, so it is best to specify your SITEURL; if you do not, feeds diff --git a/pelican/__init__.py b/pelican/__init__.py index e9fef163..082e5a58 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -63,9 +63,9 @@ class Pelican(object): def init_plugins(self): self.plugins = [] - logger.debug('Temporarily adding PLUGIN_PATH to system path') + logger.debug('Temporarily adding PLUGIN_PATHS to system path') _sys_path = sys.path[:] - for pluginpath in self.settings['PLUGIN_PATH']: + for pluginpath in self.settings['PLUGIN_PATHS']: sys.path.insert(0, pluginpath) for plugin in self.settings['PLUGINS']: # if it's a string, then import it diff --git a/pelican/settings.py b/pelican/settings.py index 219ebbd0..c93050ad 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -114,7 +114,7 @@ DEFAULT_CONFIG = { 'ARTICLE_PERMALINK_STRUCTURE': '', 'TYPOGRIFY': False, 'SUMMARY_MAX_LENGTH': 50, - 'PLUGIN_PATH': [], + 'PLUGIN_PATHS': [], 'PLUGINS': [], 'PYGMENTS_RST_OPTIONS': {}, 'TEMPLATE_PAGES': {}, @@ -147,13 +147,17 @@ def read_settings(path=None, override=None): if p not in ('THEME') or os.path.exists(absp): local_settings[p] = absp - if isinstance(local_settings['PLUGIN_PATH'], six.string_types): - logger.warning("Defining %s setting as string has been deprecated (should be a list)" % 'PLUGIN_PATH') - local_settings['PLUGIN_PATH'] = [local_settings['PLUGIN_PATH']] - else: - if 'PLUGIN_PATH' in local_settings and local_settings['PLUGIN_PATH'] is not None: - local_settings['PLUGIN_PATH'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath))) - if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATH']] + if 'PLUGIN_PATH' in local_settings: + logger.warning('PLUGIN_PATH setting has been replaced by ' + 'PLUGIN_PATHS, moving it to the new setting name.') + local_settings['PLUGIN_PATHS'] = local_settings['PLUGIN_PATH'] + del local_settings['PLUGIN_PATH'] + if isinstance(local_settings['PLUGIN_PATHS'], six.string_types): + logger.warning("Defining %s setting as string has been deprecated (should be a list)" % 'PLUGIN_PATHS') + local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']] + elif local_settings['PLUGIN_PATHS'] is not None: + local_settings['PLUGIN_PATHS'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath))) + if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATHS']] else: local_settings = copy.deepcopy(DEFAULT_CONFIG) From 5b68a3e6334a51078fd6cda0bd825ebf885996e0 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Wed, 14 May 2014 14:30:21 +0200 Subject: [PATCH 69/82] Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES automatically This makes it easier for someone to change PAGE_PATHS without the need to change ARTICLE_EXCLUDES accordingly. --- docs/settings.rst | 6 ++++-- pelican/settings.py | 16 ++++++++++++++-- pelican/tests/test_settings.py | 5 ++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 34245ff4..fd5f488b 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -112,9 +112,11 @@ Setting name (followed by default value, if any) and content path is not specified via an argument to the ``pelican`` command, Pelican will use the current working directory. ``PAGE_PATHS = ['pages']`` A list of directories to look at for pages, relative to ``PATH``. -``PAGE_EXCLUDES = ()`` A list of directories to exclude when looking for pages. +``PAGE_EXCLUDES = []`` A list of directories to exclude when looking for pages in addition + to ``ARTICLE_PATHS``. ``ARTICLE_PATHS = ['']`` A list of directories to look at for articles, relative to ``PATH``. -``ARTICLE_EXCLUDES = ('pages',)`` A list of directories to exclude when looking for articles. +``ARTICLE_EXCLUDES = []`` A list of directories to exclude when looking for articles in addition + to ``PAGE_PATHS``. ``OUTPUT_SOURCES = False`` Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the specified ``OUTPUT_PATH``. diff --git a/pelican/settings.py b/pelican/settings.py index c93050ad..69ade05f 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -30,9 +30,9 @@ DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), DEFAULT_CONFIG = { 'PATH': os.curdir, 'ARTICLE_PATHS': [''], - 'ARTICLE_EXCLUDES': ('pages',), + 'ARTICLE_EXCLUDES': [], 'PAGE_PATHS': ['pages'], - 'PAGE_EXCLUDES': (), + 'PAGE_EXCLUDES': [], 'THEME': DEFAULT_THEME, 'OUTPUT_PATH': 'output', 'READERS': {}, @@ -348,6 +348,18 @@ def configure_settings(settings): % PATH_KEY) settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY] + # Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES + mutually_exclusive = ('ARTICLE', 'PAGE') + for type_1, type_2 in [mutually_exclusive, mutually_exclusive[::-1]]: + try: + includes = settings[type_1 + '_PATHS'] + excludes = settings[type_2 + '_EXCLUDES'] + for path in includes: + if path not in excludes: + excludes.append(path) + except KeyError: + continue # setting not specified, nothing to do + for old, new, doc in [ ('LESS_GENERATOR', 'the Webassets plugin', None), ('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA', diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 930e0fea..260eff05 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -43,7 +43,10 @@ class TestSettingsConfiguration(unittest.TestCase): # Providing no file should return the default values. settings = read_settings(None) expected = copy.deepcopy(DEFAULT_CONFIG) - expected['FEED_DOMAIN'] = '' # Added by configure settings + # Added by configure settings + expected['FEED_DOMAIN'] = '' + expected['ARTICLE_EXCLUDES'] = ['pages'] + expected['PAGE_EXCLUDES'] = [''] self.maxDiff = None self.assertDictEqual(settings, expected) From 1cb190df7f8597c4e6f48e5274657dac25a69bc9 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 25 May 2014 09:12:35 +0200 Subject: [PATCH 70/82] Fix RstReader authors metadata processing The reader would return a list of authors already, but METADATA_PROCESSORS['authors'] expects a string. Added a test case for this (only the HTMLReader had it). --- pelican/readers.py | 1 + pelican/tests/test_generators.py | 1 + pelican/tests/test_readers.py | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/pelican/readers.py b/pelican/readers.py index c63b8981..60df8551 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -145,6 +145,7 @@ class RstReader(BaseReader): elif element.tagname == 'authors': # author list name = element.tagname value = [element.astext() for element in element.children] + value = ','.join(value) # METADATA_PROCESSORS expects a string else: # standard fields (e.g. address) name = element.tagname value = element.astext() diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 668a0093..dfa9a36b 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -109,6 +109,7 @@ class TestArticlesGenerator(unittest.TestCase): ['This is an article with category !', 'published', 'yeah', 'article'], ['This is an article with multiple authors!', 'published', 'Default', 'article'], + ['This is an article with multiple authors!', 'published', 'Default', 'article'], ['This is an article without category !', 'published', 'Default', 'article'], ['This is an article without category !', 'published', diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index fd30e9b9..3533cd31 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -150,6 +150,15 @@ class RstReaderTest(ReaderTest): except ImportError: return unittest.skip('need the typogrify distribution') + def test_article_with_multiple_authors(self): + page = self.read_file(path='article_with_multiple_authors.rst') + expected = { + 'authors': ['First Author', 'Second Author'] + } + + for key, value in expected.items(): + self.assertEqual(value, page.metadata[key], key) + class MdReaderTest(ReaderTest): From fdfa738be3df2b772b5c8a5433534002dcf845ab Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 27 May 2014 12:15:50 -0400 Subject: [PATCH 71/82] Docs update for *_{previous|next}_page variables --- docs/themes.rst | 56 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/docs/themes.rst b/docs/themes.rst index 7fcba671..4be9a8e5 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -119,17 +119,25 @@ This is the home page of your blog, generated at output/index.html. If pagination is active, subsequent pages will reside in output/index`n`.html. -=================== =================================================== +====================== =================================================== Variable Description -=================== =================================================== +====================== =================================================== articles_paginator A paginator object for the list of articles articles_page The current page of articles +articles_previous_page The previous page of articles (``None`` if page does + not exist) +articles_next_page The next page of articles (``None`` if page does + not exist) dates_paginator A paginator object for the article list, ordered by date, ascending. dates_page The current page of articles, ordered by date, ascending. +dates_previous_page The previous page of articles, ordered by date, + ascending (``None`` if page does not exist) +dates_next_page The next page of articles, ordered by date, + ascending (``None`` if page does not exist) page_name 'index' -- useful for pagination links -=================== =================================================== +====================== =================================================== author.html ------------- @@ -140,22 +148,30 @@ output generated at output/author/`author_name`.html. If pagination is active, subsequent pages will reside as defined by setting AUTHOR_SAVE_AS (`Default:` output/author/`author_name'n'`.html). -=================== =================================================== +====================== =================================================== Variable Description -=================== =================================================== +====================== =================================================== author The name of the author being processed articles Articles by this author dates Articles by this author, but ordered by date, ascending articles_paginator A paginator object for the list of articles articles_page The current page of articles +articles_previous_page The previous page of articles (``None`` if page does + not exist) +articles_next_page The next page of articles (``None`` if page does + not exist) dates_paginator A paginator object for the article list, ordered by date, ascending. dates_page The current page of articles, ordered by date, ascending. +dates_previous_page The previous page of articles, ordered by date, + ascending (``None`` if page does not exist) +dates_next_page The next page of articles, ordered by date, + ascending (``None`` if page does not exist) page_name AUTHOR_URL where everything after `{slug}` is removed -- useful for pagination links -=================== =================================================== +====================== =================================================== category.html ------------- @@ -166,22 +182,30 @@ output generated at output/category/`category_name`.html. If pagination is active, subsequent pages will reside as defined by setting CATEGORY_SAVE_AS (`Default:` output/category/`category_name'n'`.html). -=================== =================================================== +====================== =================================================== Variable Description -=================== =================================================== +====================== =================================================== category The name of the category being processed articles Articles for this category dates Articles for this category, but ordered by date, ascending articles_paginator A paginator object for the list of articles articles_page The current page of articles +articles_previous_page The previous page of articles (``None`` if page does + not exist) +articles_next_page The next page of articles (``None`` if page does + not exist) dates_paginator A paginator object for the list of articles, ordered by date, ascending dates_page The current page of articles, ordered by date, ascending +dates_previous_page The previous page of articles, ordered by date, + ascending (``None`` if page does not exist) +dates_next_page The next page of articles, ordered by date, + ascending (``None`` if page does not exist) page_name CATEGORY_URL where everything after `{slug}` is removed -- useful for pagination links -=================== =================================================== +====================== =================================================== article.html ------------- @@ -244,22 +268,30 @@ saved as output/tag/`tag_name`.html. If pagination is active, subsequent pages will reside as defined in setting TAG_SAVE_AS (`Default:` output/tag/`tag_name'n'`.html). -=================== =================================================== +====================== =================================================== Variable Description -=================== =================================================== +====================== =================================================== tag The name of the tag being processed articles Articles related to this tag dates Articles related to this tag, but ordered by date, ascending articles_paginator A paginator object for the list of articles articles_page The current page of articles +articles_previous_page The previous page of articles (``None`` if page does + not exist) +articles_next_page The next page of articles (``None`` if page does + not exist) dates_paginator A paginator object for the list of articles, ordered by date, ascending dates_page The current page of articles, ordered by date, ascending +dates_previous_page The previous page of articles, ordered by date, + ascending (``None`` if page does not exist) +dates_next_page The next page of articles, ordered by date, + ascending (``None`` if page does not exist) page_name TAG_URL where everything after `{slug}` is removed -- useful for pagination links -=================== =================================================== +====================== =================================================== period_archives.html -------------------- From 8af9ecc719aed4048ba436b0b4635f399a333e42 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 8 Jun 2014 11:32:10 +0200 Subject: [PATCH 72/82] catch arbitrary exceptions during cache unpickling It is hard to forsee what could be raised. --- pelican/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/utils.py b/pelican/utils.py index 2af34ecf..84b3a41e 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -577,7 +577,7 @@ class FileDataCacher(object): 'run). Proceeding with empty cache.\n{}').format( self._cache_path, err)) self._cache = {} - except pickle.UnpicklingError as err: + except Exception as err: logger.warning(('Cannot unpickle cache {}, cache may be using ' 'an incompatible protocol (see pelican caching docs). ' 'Proceeding with empty cache.\n{}').format( From c319030259a69dd2066970c46114b81ad81329e7 Mon Sep 17 00:00:00 2001 From: OGINO Masanori Date: Tue, 10 Jun 2014 08:28:10 +0900 Subject: [PATCH 73/82] Require six version 1.4.0 or later. six.moves.urllib.parse is available since version 1.4.0. Signed-off-by: OGINO Masanori --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e989d549..a2bcaeaa 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,8 @@ from setuptools import setup requires = ['feedgenerator >= 1.6', 'jinja2 >= 2.7', 'pygments', 'docutils', - 'pytz >= 0a', 'blinker', 'unidecode', 'six', 'python-dateutil'] + 'pytz >= 0a', 'blinker', 'unidecode', 'six >= 1.4', + 'python-dateutil'] entry_points = { 'console_scripts': [ From b7c23dc16006757b5d5073f0cae4774f20ee146e Mon Sep 17 00:00:00 2001 From: OGINO Masanori Date: Tue, 10 Jun 2014 17:30:17 +0900 Subject: [PATCH 74/82] Use six.moves.urllib. Signed-off-by: OGINO Masanori --- pelican/contents.py | 8 +------- pelican/tools/pelican_import.py | 9 +++------ pelican/writers.py | 4 +--- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 615a7fd8..220db611 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function import six -from six.moves.urllib.parse import unquote +from six.moves.urllib.parse import (unquote, urlparse, urlunparse) import copy import locale @@ -11,14 +11,8 @@ import os import re import sys -try: - from urlparse import urlparse, urlunparse -except ImportError: - from urllib.parse import urlparse, urlunparse - from datetime import datetime - from pelican import signals from pelican.settings import DEFAULT_CONFIG from pelican.utils import (slugify, truncate_html_words, memoized, strftime, diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 27e47754..7c8662c9 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -6,15 +6,9 @@ import argparse try: # py3k import from html.parser import HTMLParser - from urllib.request import urlretrieve - from urllib.parse import urlparse - from urllib.error import URLError except ImportError: # py2 import from HTMLParser import HTMLParser # NOQA - from urllib import urlretrieve - from urlparse import urlparse - from urllib2 import URLError import os import re import subprocess @@ -23,6 +17,9 @@ import time import logging from codecs import open +from six.moves.urllib.error import URLError +from six.moves.urllib.parse import urlparse +from six.moves.urllib.request import urlretrieve from pelican.utils import slugify from pelican.log import init diff --git a/pelican/writers.py b/pelican/writers.py index a92feee4..3e01ee6c 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -8,12 +8,10 @@ import logging if not six.PY3: from codecs import open - from urlparse import urlparse -else: - from urllib.parse import urlparse from feedgenerator import Atom1Feed, Rss201rev2Feed from jinja2 import Markup +from six.moves.urllib.parse import urlparse from pelican.paginator import Paginator from pelican.utils import (get_relative_path, path_to_url, set_date_tzinfo, From 24aca13b3ee2c1aa03e87f7ea36d6417db0eedcf Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 10 Jun 2014 18:05:29 -0400 Subject: [PATCH 75/82] Remove AsciiDocReader from core. Fixes #1355 --- .travis.yml | 2 - docs/content.rst | 5 ++- docs/index.rst | 3 +- docs/install.rst | 5 --- docs/internals.rst | 4 +- docs/settings.rst | 2 - pelican/readers.py | 39 ------------------- pelican/settings.py | 1 - .../content/article_with_asc_extension.asc | 12 ------ .../content/article_with_asc_options.asc | 9 ----- pelican/tests/test_readers.py | 36 ----------------- 11 files changed, 6 insertions(+), 112 deletions(-) delete mode 100644 pelican/tests/content/article_with_asc_extension.asc delete mode 100644 pelican/tests/content/article_with_asc_options.asc diff --git a/.travis.yml b/.travis.yml index 41ad82b2..54dcf0ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,8 @@ python: - "3.4" before_install: - sudo apt-get update -qq - - sudo apt-get install -qq --no-install-recommends asciidoc - sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then ln -s /usr/share/asciidoc/asciidocapi.py ~/virtualenv/python2.7/lib/python2.7/site-packages/; fi - pip install . - pip install -r dev_requirements.txt - pip install nose-cov diff --git a/docs/content.rst b/docs/content.rst index 24fc6e9b..ad81bed1 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -57,8 +57,8 @@ pattern:: This is the content of my super blog post. -Conventions for AsciiDoc_ posts, which should have an ``.asc`` extension, can -be found on the AsciiDoc_ site. +Readers for additional formats (such as AsciiDoc_) are available via plugins. +Refer to `pelican-plugins`_ repository for those. Pelican can also process HTML files ending in ``.html`` and ``.htm``. Pelican interprets the HTML in a very straightforward manner, reading metadata from @@ -369,3 +369,4 @@ listed on the index page nor on any category or tag page. .. _W3C ISO 8601: http://www.w3.org/TR/NOTE-datetime .. _AsciiDoc: http://www.methods.co.nz/asciidoc/ +.. _pelican-plugins: http://github.com/getpelican/pelican-plugins diff --git a/docs/index.rst b/docs/index.rst index 36a3282b..2beb8b20 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,7 @@ Pelican |release| Pelican is a static site generator, written in Python_. Highlights include: * Write your content directly with your editor of choice - in reStructuredText_, Markdown_, or AsciiDoc_ formats + in reStructuredText_ or Markdown_ formats * Includes a simple CLI tool to (re)generate your site * Easy to interface with distributed version control systems and web hooks * Completely static output is easy to host anywhere @@ -89,7 +89,6 @@ Documentation .. _Python: http://www.python.org/ .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Markdown: http://daringfireball.net/projects/markdown/ -.. _AsciiDoc: http://www.methods.co.nz/asciidoc/index.html .. _Jinja2: http://jinja.pocoo.org/ .. _`Pelican documentation`: http://docs.getpelican.com/latest/ .. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html diff --git a/docs/install.rst b/docs/install.rst index 34cd33ea..418c8ca6 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -52,10 +52,6 @@ installed:: pip install typogrify -If you want to use AsciiDoc_ you need to install it from `source -`_ or use your operating -system's package manager. - Dependencies ------------ @@ -119,4 +115,3 @@ The next step is to begin to adding content to the *content* folder that has been created for you. .. _virtualenv: http://www.virtualenv.org/ -.. _AsciiDoc: http://www.methods.co.nz/asciidoc/ diff --git a/docs/internals.rst b/docs/internals.rst index f69a9bb8..303a327f 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -13,7 +13,7 @@ Overall structure ================= What Pelican does is take a list of files and process them into some sort of -output. Usually, the input files are reStructuredText, Markdown and AsciiDoc +output. Usually, the input files are reStructuredText and Markdown files, and the output is a blog, but both input and output can be anything you want. @@ -23,7 +23,7 @@ The logic is separated into different classes and concepts: on. Since those operations are commonly used, the object is created once and then passed to the generators. -* **Readers** are used to read from various formats (AsciiDoc, HTML, Markdown and +* **Readers** are used to read from various formats (HTML, Markdown and reStructuredText for now, but the system is extensible). Given a file, they return metadata (author, tags, category, etc.) and content (HTML-formatted). diff --git a/docs/settings.rst b/docs/settings.rst index fd5f488b..86424ec5 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -162,8 +162,6 @@ Setting name (followed by default value, if any) Can be used to separate templates from the theme. Example: projects, resume, profile ... These templates need to use ``DIRECT_TEMPLATES`` setting. -``ASCIIDOC_OPTIONS = []`` A list of options to pass to AsciiDoc. See the `manpage - `_. ``WITH_FUTURE_DATES = True`` If disabled, content with dates in the future will get a default status of ``draft``. See :ref:`reading_only_modified_content` for caveats. diff --git a/pelican/readers.py b/pelican/readers.py index 60df8551..431e6937 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -17,11 +17,6 @@ try: from markdown import Markdown except ImportError: Markdown = False # NOQA -try: - from asciidocapi import AsciiDocAPI - asciidoc = True -except ImportError: - asciidoc = False try: from html import escape except ImportError: @@ -349,40 +344,6 @@ class HTMLReader(BaseReader): return parser.body, metadata -class AsciiDocReader(BaseReader): - """Reader for AsciiDoc files""" - - enabled = bool(asciidoc) - file_extensions = ['asc', 'adoc', 'asciidoc'] - default_options = ["--no-header-footer", "-a newline=\\n"] - - def read(self, source_path): - """Parse content and metadata of asciidoc files""" - from cStringIO import StringIO - with pelican_open(source_path) as source: - text = StringIO(source) - content = StringIO() - ad = AsciiDocAPI() - - options = self.settings['ASCIIDOC_OPTIONS'] - if isinstance(options, (str, unicode)): - options = [m.strip() for m in options.split(',')] - options = self.default_options + options - for o in options: - ad.options(*o.split()) - - ad.execute(text, content, backend="html4") - content = content.getvalue() - - metadata = {} - for name, value in ad.asciidoc.document.attributes.items(): - name = name.lower() - metadata[name] = self.process_metadata(name, value) - if 'doctitle' in metadata: - metadata['title'] = metadata['doctitle'] - return content, metadata - - class Readers(FileStampDataCacher): """Interface for all readers. diff --git a/pelican/settings.py b/pelican/settings.py index 69ade05f..c04cc5d0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -98,7 +98,6 @@ DEFAULT_CONFIG = { 'PELICAN_CLASS': 'pelican.Pelican', 'DEFAULT_DATE_FORMAT': '%a %d %B %Y', 'DATE_FORMATS': {}, - 'ASCIIDOC_OPTIONS': [], 'MD_EXTENSIONS': ['codehilite(css_class=highlight)', 'extra'], 'JINJA_EXTENSIONS': [], 'JINJA_FILTERS': {}, diff --git a/pelican/tests/content/article_with_asc_extension.asc b/pelican/tests/content/article_with_asc_extension.asc deleted file mode 100644 index 9ce2166c..00000000 --- a/pelican/tests/content/article_with_asc_extension.asc +++ /dev/null @@ -1,12 +0,0 @@ -Test AsciiDoc File Header -========================= -:Author: Author O. Article -:Email: -:Date: 2011-09-15 09:05 -:Category: Blog -:Tags: Linux, Python, Pelican - -Used for pelican test ---------------------- - -The quick brown fox jumped over the lazy dog's back. diff --git a/pelican/tests/content/article_with_asc_options.asc b/pelican/tests/content/article_with_asc_options.asc deleted file mode 100644 index bafb3a4a..00000000 --- a/pelican/tests/content/article_with_asc_options.asc +++ /dev/null @@ -1,9 +0,0 @@ -Test AsciiDoc File Header -========================= - -Used for pelican test ---------------------- - -version {revision} - -The quick brown fox jumped over the lazy dog's back. diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 3533cd31..6228989b 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -333,42 +333,6 @@ class MdReaderTest(ReaderTest): self.assertEqual(value, page.metadata[key], key) -class AdReaderTest(ReaderTest): - - @unittest.skipUnless(readers.asciidoc, "asciidoc isn't installed") - def test_article_with_asc_extension(self): - # Ensure the asc extension is being processed by the correct reader - page = self.read_file( - path='article_with_asc_extension.asc') - expected = ('
    \n

    ' - 'Used for pelican test

    \n' - '

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

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

    Used for' - ' pelican test

    \n

    version 1.0.42

    \n' - '

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

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

    Archives for Alexis' log

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

    blogroll

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

    Other articles

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

    + Page 1 / 3 + » +

    +
    +
    +
    +

    blogroll

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

      Oh yeah !

      +
      + +
      +
      +

      Why not ?

      +

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

      +alternate text +
      + + read more +

      There are comments.

      +
    5. +
    +

    + « + Page 2 / 3 + » +

    +
    +
    +
    +

    blogroll

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

    + « + Page 3 / 3 +

    +
    +
    +
    +

    blogroll

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

    Authors on Alexis' log

    + +
    + +
    +
    +

    blogroll

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

    blogroll

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

    blogroll

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

    Other articles

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

    + Page 1 / 1 +

    +
    +
    +
    +

    blogroll

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

    Other articles

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

    + Page 1 / 1 +

    +
    +
    +
    +

    blogroll

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

    blogroll

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

    + A draft article

    +
    + +
    +

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

    + +
    + +
    +
    +
    +
    +

    blogroll

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

    Other articles

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

    + Page 1 / 3 + » +

    +
    +
    +
    +

    blogroll

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

      Oh yeah !

      +
      + +
      +
      +

      Why not ?

      +

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

      +alternate text +
      + + read more +

      There are comments.

      +
    5. +
    +

    + « + Page 2 / 3 + » +

    +
    +
    +
    +

    blogroll

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

    + « + Page 3 / 3 +

    +
    +
    +
    +

    blogroll

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

    blogroll

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

    + Trop bien !

    +
    + +
    +

    Et voila du contenu en français

    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    Override url/save_as

    + +

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

    + +
    +
    +
    +

    blogroll

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

    This is a test hidden page

    + +

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

    + +
    +
    +
    +

    blogroll

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

    This is a test page

    + +

    Just an image.

    +alternate text + +
    +
    +
    +

    blogroll

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

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

    +
    +
    +
    +

    + This is a super article !

    +
    + +
    +

    Some content here !

    +
    +

    This is a simple title

    +

    And here comes the cool stuff.

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

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

    +
    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + Unbelievable !

    +
    + +
    +

    Or completely awesome. Depends the needs.

    +

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

    +
    +

    Testing sourcecode directive

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

    Testing another case

    +

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

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

    Lovely.

    +
    +
    +

    Testing more sourcecode directives

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

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

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

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

    Lovely.

    +
    +
    +

    Testing even more sourcecode directives

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

    Lovely.

    +
    +
    +

    Testing overriding config defaults

    +

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

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

    Lovely.

    +
    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + Oh yeah !

    +
    + +
    +
    +

    Why not ?

    +

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

    +alternate text +
    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + A markdown powered article

    +
    + +
    +

    You're mutually oblivious.

    +

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

    +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + Article 1

    +
    + +
    +

    Article 1

    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + Article 2

    +
    + +
    +

    Article 2

    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + Article 3

    +
    + +
    +

    Article 3

    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + Second article

    +
    + +
    +

    This is some article, in english

    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + FILENAME_METADATA example

    +
    + +
    +

    Some cool stuff!

    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    + Deuxième article

    +
    + +
    +

    Ceci est un article, en français.

    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    Other articles

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

      Oh yeah !

      +
      + +
      +
      +

      Why not ?

      +

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

      +alternate text +
      + + read more +

      There are comments.

      +
    3. +
    +

    + Page 1 / 1 +

    +
    +
    +
    +

    blogroll

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

    + The baz tag

    +
    + +
    +

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

    + +
    +
    +

    Comments !

    +
    + + +
    + +
    +
    +
    +
    +

    blogroll

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

    Other articles

    +
    +
      + +
    1. +
    +

    + Page 1 / 1 +

    +
    +
    +
    +

    blogroll

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

    blogroll

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

    Oh Oh Oh

    + +

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

    + +
    +
    +
    +

    blogroll

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

    blogroll

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

    Tags for Alexis' log

    + +
    + +
    +
    +

    blogroll

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

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

    I have a lot to test

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

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

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

    Summary with inline markup ' 'should be supported.

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

    Archives for Alexis' log

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

    FILENAME_METADATA example