diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index f1ec6c3d..00000000
--- a/.editorconfig
+++ /dev/null
@@ -1,15 +0,0 @@
-root = true
-
-[*]
-charset = utf-8
-end_of_line = lf
-indent_size = 2
-indent_style = space
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[*.py]
-max_line_length = 88
-
-[*.{yml,yaml}]
-indent_size = 2
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..dfe07704
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
index c2658d7d..1ae0e9f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,15 @@
-node_modules/
+*.egg-info
+.*.swp
+.*.swo
+*.pyc
+.DS_Store
+docs/_build
+docs/fr/_build
+build
+dist
+tags
+.tox
+.coverage
+htmlcov
+six-*.egg/
+*.orig
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 00000000..a0f6b7c5
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,9 @@
+syntax: glob
+output/*
+*.pyc
+MANIFEST
+build
+dist
+docs/_build
+Paste-*
+*.egg-info
diff --git a/.hgtags b/.hgtags
new file mode 100644
index 00000000..6d4c8d99
--- /dev/null
+++ b/.hgtags
@@ -0,0 +1,29 @@
+7acafbf7e47b1287525026ad8b4f1efe443d5403 1.2
+7acafbf7e47b1287525026ad8b4f1efe443d5403 1.2
+ae850ab0fd62a98a98da7ce74ac794319c6a5066 1.2
+54a0309f79d6c5b54d8e1e3b5e3f744856b68a73 1.1
+8f5e0eb037768351eb08840e588a4364266a69b3 1.1.1
+bb986ed591734ca469f726753cbc48ebbfce0dcc 1.2.1
+8a3dad99cbfa6bb5d0ef073213d0d86e0b4c5dba 1.2.2
+4a20105a242ab154f6202aa6651979bfbb4cf95e 1.2.3
+803aa0976cca3dd737777c640722988b1f3769fe 1.2.4
+703c4511105fd9c8b85afda951a294c194e7cf3e 1.2.5
+6e46a40aaa850a979f5d09dd95d02791ec7ab0ef 2.0
+bf14d1a5c1fae9475447698f0f9b8d35c551f732 2.1
+da86343ebd543e5865050e47ecb0937755528d13 2.1.1
+760187f048bb23979402f950ecb5d3c5493995b1 2.2
+20aa16fe4daa3b70f6c063f170edc916b49837ed 2.3
+f9c1d94081504f21f5b2ba147a38099e45db1769 2.4
+e65199a0b2706d2fb48f7a3c015e869716e0bec1 2.4.1
+89dbd7b6f114508eae62fc821326f4797dfc8b23 2.4.2
+979b4473af56a191a278c83058bc9c8fa1fde30e 2.4.3
+26a444fbb78becae358afa0a5b47587db8739b21 2.4.4
+3542b65fd1963ae7065b6a3bc912fbb6c150e98c 2.4.5
+87745dfdd51b96bf18eaaf6c402effa902c1b856 2.5.0
+294a2830a393d5a97671dc211dbdb5254a15e604 2.5.1
+294a2830a393d5a97671dc211dbdb5254a15e604 2.5.1
+92b31e41134cb2c1a156ce623338cf634d2ebc3e 2.5.1
+7d728f8e771cbbc802ce81e424e08a8eecbd48dc 2.5.2
+7d728f8e771cbbc802ce81e424e08a8eecbd48dc 2.5.2
+6d368a1739a4ce48d2d04b00db04fa538e2bf90a 2.5.2
+1f9dd44b546425216b1fa35fd88d3d532da8916b 2.5.3
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 00000000..8dd40fb6
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,24 @@
+Alexis Métaireau
+Alexis Métaireau
+Alexis Métaireau
+Axel Haustant
+Axel Haustant
+Dave Mankoff
+Feth Arezki
+Guillaume
+Guillaume
+Guillaume B
+Guillermo López
+Guillermo López
+Jomel Imperio
+Justin Mayer
+Justin Mayer
+Marco Milanesi
+Massimo Santini
+Rémy HUBSCHER
+Simon Conseil
+Simon Liedtke
+Skami18
+Stuart Colville
+Stéphane Bunel
+tBunnyMan
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..918fd3f9
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: python
+python:
+ - "2.7"
+ - "3.3"
+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 mock
+ - pip install .
+ - pip install Markdown
+script: python -m unittest discover
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 00000000..b28b22a3
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,40 @@
+Contribution submission guidelines
+==================================
+
+* Consider whether your new feature might be better suited as a plugin_. Folks
+ are usually available in the `#pelican IRC channel`_ if help is needed to
+ make that determination.
+* `Create a new git branch`_ specific to your change (as opposed to making
+ your commits in the master branch).
+* **Don't put multiple fixes/features in the same branch / pull request.**
+ For example, if you're hacking on a new feature and find a bugfix that
+ doesn't *require* your new feature, **make a new distinct branch and pull
+ request** for the bugfix.
+* Adhere to PEP8 coding standards whenever possible.
+* Check for unnecessary whitespace via ``git diff --check`` before committing.
+* **Add docs and tests for your changes**.
+* `Run all the tests`_ **on both Python 2.7 and 3.3** to ensure nothing was
+ accidentally broken.
+* First line of your commit message should start with present-tense verb, be 50
+ characters or less, and include the relevant issue number(s) if applicable.
+ *Example:* ``Ensure proper PLUGIN_PATH behavior. Refs #428.`` If the commit
+ *completely fixes* an existing bug report, please use ``Fixes #585`` or ``Fix
+ #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).
+* 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
+ 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
+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
+.. _`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
diff --git a/LICENSE b/LICENSE
index 789896df..dba13ed2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,20 +1,661 @@
-MIT License
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
-Copyright (c) 2024 Oliver Ladner
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
+ Preamble
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+ .
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..dcf9ea45
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include *.rst
+recursive-include pelican *.html *.css *png *.in *.rst *.md *.mkd *.xml *.py
+include LICENSE THANKS docs/changelog.rst
diff --git a/README.md b/README.md
deleted file mode 100644
index f3fe9563..00000000
--- a/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# lugh Pelican theme
-
-This theme is based on the [simple theme](https://github.com/getpelican/pelican/tree/main/pelican/themes/simple/templates).
-It's heavily customized to what I need here, so no efforts have been made to
-keep it useful for others. Amongst other things, I:
-
-- removed translations
-- changed the structure (HTML `` etc.)
-
-## Docs
-
-- [Pelican: how to create your own theme](https://docs.getpelican.com/en/stable/themes.html)
-- [Tailwind CSS quick start](https://tailwindcss.com/docs/installation)
-
-## Doing
-
-### Prepare Pelican development server config
-
-Adapt Pelican's `publishconf.py` for local development.
-E.g. `RELATIVE_URLS = False`
-
-### Install Tailwind CSS Typography plugin
-
-Typography enables sane defaults for longer texts. In this case, we use it for
-the body content only, which is always Markdown. Typography is a bit of a beast
-to configure/align to standard Tailwind.
-
-```shell
-npm install -D @tailwindcss/typography
-```
-
-### Run the Tailwind build process
-
-```shell
-npx tailwindcss -i static/css/in.css -o static/css/out.css --watch
-```
-
-### Run Pelican dev server
-
-```shell
-conda activate pelican
-./devserver.sh
-```
diff --git a/README.rst b/README.rst
new file mode 100644
index 00000000..20c3f217
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,68 @@
+Pelican
+=======
+
+.. image:: https://secure.travis-ci.org/getpelican/pelican.png?branch=master
+ :target: http://travis-ci.org/getpelican/pelican
+ :alt: Travis-ci: continuous integration status.
+
+Pelican is a static site generator, written in Python_.
+
+* Write your weblog entries directly with your editor of choice (vim!)
+ in reStructuredText_ or Markdown_
+* Includes a simple CLI tool to (re)generate the weblog
+* Easy to interface with DVCSes and web hooks
+* Completely static output is easy to host anywhere
+
+Features
+--------
+
+Pelican currently supports:
+
+* Blog articles and pages
+* Comments, via an external service (Disqus). (Please note that while
+ useful, Disqus is an external service, and thus the comment data will be
+ somewhat outside of your control and potentially subject to data loss.)
+* Theming support (themes are created using Jinja2_ templates)
+* PDF generation of the articles/pages (optional)
+* 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)
+
+Have a look at the `Pelican documentation`_ for more information.
+
+Why the name "Pelican"?
+-----------------------
+
+"Pelican" is an anagram for *calepin*, which means "notebook" in French. ;)
+
+Source code
+-----------
+
+You can access the source code at: https://github.com/getpelican/pelican
+
+If you feel hackish, have a look at the explanation of `Pelican's internals`_.
+
+Feedback / Contact us
+---------------------
+
+If you want to see new features in Pelican, don't hesitate to offer
+suggestions, clone the repository, etc. There are many ways to contribute_.
+That's open source, dude!
+
+Send a message to "authors at getpelican dot com" with any requests/feedback! You
+can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC
+client handy, use the webchat_ for quick feedback.
+
+.. Links
+
+.. _Python: http://www.python.org/
+.. _reStructuredText: http://docutils.sourceforge.net/rst.html
+.. _Markdown: http://daringfireball.net/projects/markdown/
+.. _Jinja2: http://jinja.pocoo.org/
+.. _`Pelican documentation`: http://docs.getpelican.com/latest/
+.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
+.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican
+.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
+.. _contribute: http://docs.getpelican.com/en/latest/contribute.html
diff --git a/THANKS b/THANKS
new file mode 100644
index 00000000..e4eed231
--- /dev/null
+++ b/THANKS
@@ -0,0 +1,157 @@
+Pelican is a project originally created by Alexis Métaireau
+ , but there are a large number of people that have
+contributed or implemented key features over time. We do our best to keep this
+list up-to-date, but you can also have a look at the nice contributor graphs
+produced by GitHub: https://github.com/getpelican/pelican/graphs/contributors
+
+If you want to contibute, check the documentation section about how to do so:
+
+
+Aaron Kavlie
+Abhishek L
+Albrecht Mühlenschulte
+Aldiantoro Nugroho
+Alen Mujezinovic
+Alessandro Martin
+Alexander Artemenko
+Alexandre RODIERE
+Alexis Daboville
+Alexis Métaireau
+Allan Whatmough
+Andrea Crotti
+Andrew Laski
+Andrew Spiers
+Arnaud BOS
+asselinpaul
+Axel Haustant
+Benoît HERVIER
+Borgar
+Brandon W Maister
+Brendan Wholihan
+Brian C. Lane
+Brian Hsu
+Brian St. Pierre
+Bruno Binet
+BunnyMan
+Chenguang Wang
+Chris Elston
+Chris McDonald (Wraithan)
+Chris Streeter
+Christophe Chauvet
+Clint Howarth
+Colin Dunklau
+Dafydd Crosby
+Dana Woodman
+Dave King
+Dave Mankoff
+David Beitey
+David Marble
+Deniz Turgut (Avaris)
+derdon
+Dirkjan Ochtman
+Dirk Makowski
+draftcode
+Edward Delaporte
+Emily Strickland
+epatters
+Eric Case
+Erik Hetzner
+FELD Boris
+Feth Arezki
+Florian Jacob
+Florian Preinstorfer
+Félix Delval
+Freeculture
+George V. Reilly
+Guillaume
+Guillaume B
+Guillermo López
+guillermooo
+Ian Cordasco
+Igor Kalnitsky
+Irfan Ahmad
+Iuri de Silvio
+Ivan Dyedov
+James King
+James Rowe
+jawher
+Jered Boxman
+Jerome
+Jiachen Yang
+Jochen Breuer
+joe di castro
+John Kristensen
+John Mastro
+Jökull Sólberg Auðunsson
+Jomel Imperio
+Joseph Reagle
+Joshua Adelman
+Julian Berman
+Justin Mayer
+Kyle Fuller
+Laureline Guerin
+Leonard Huang
+Leroy Jiang
+Marcel Hellkamp
+Marco Milanesi
+Marcus Fredriksson
+Mario Rodas
+Mark Caudill
+Martin Brochhaus
+Massimo Santini
+Matt Bowcock
+Matt Layman
+Meir Kriheli
+Michael Guntsche
+Michael Reneer
+Michael Yanovich
+Mike Yumatov
+Mikhail Korobov
+m-r-r
+mviera
+Nico Di Rocco
+Nicolas Duhamel
+Nicolas Perriault
+Nicolas Steinmetz
+Paul Asselin
+Pavel Puchkin
+Perry Roper
+Peter Desmet
+Philippe Pepiot
+Rachid Belaid
+Randall Degges
+Ranjhith Kalisamy
+Remi Rampin
+Rémy HUBSCHER
+renhbo
+Richard Duivenvoorde
+Rogdham
+Roman Skvazh
+Ronny Pfannschmidt
+Rory McCann
+Rıdvan Örsvuran
+saghul
+sam
+Samrat Man Singh
+Simon Conseil
+Simon Liedtke
+Skami18
+solsTiCe d'Hiver
+Steve Schwarz
+Stéphane Bunel
+Stéphane Raimbault
+Stuart Colville
+Talha Mansoor
+Tarek Ziade
+Thanos Lefteris
+Thomas Thurman
+Tobias
+Tomi Pieviläinen
+Trae Blain
+Tshepang Lekhonkhobe
+Valentin-Costel Hăloiu
+Vlad Niculae
+William Light
+Wladislaw Merezhko
+W. Trevor King
+Zoresvit
diff --git a/dev_requirements.txt b/dev_requirements.txt
new file mode 100644
index 00000000..fa2634a0
--- /dev/null
+++ b/dev_requirements.txt
@@ -0,0 +1,8 @@
+# Tests
+mock
+
+# Optional Packages
+Markdown
+BeautifulSoup4
+lxml
+typogrify
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..bf49b542
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,130 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+ @echo "Please use \`make ' where is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Raclette.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Raclette.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/Raclette"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Raclette"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ make -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/_static/overall.png b/docs/_static/overall.png
new file mode 100644
index 00000000..1240a148
Binary files /dev/null and b/docs/_static/overall.png differ
diff --git a/docs/_static/pelican.gif b/docs/_static/pelican.gif
new file mode 100644
index 00000000..d9208590
Binary files /dev/null and b/docs/_static/pelican.gif differ
diff --git a/docs/_static/pelican.png b/docs/_static/pelican.png
new file mode 100644
index 00000000..c2d61c88
Binary files /dev/null and b/docs/_static/pelican.png differ
diff --git a/docs/_static/theme-basic.zip b/docs/_static/theme-basic.zip
new file mode 100644
index 00000000..d1e4754a
Binary files /dev/null and b/docs/_static/theme-basic.zip differ
diff --git a/docs/_static/uml.jpg b/docs/_static/uml.jpg
new file mode 100644
index 00000000..03655d7e
Binary files /dev/null and b/docs/_static/uml.jpg differ
diff --git a/docs/_themes/.gitignore b/docs/_themes/.gitignore
new file mode 100644
index 00000000..66b6e4c2
--- /dev/null
+++ b/docs/_themes/.gitignore
@@ -0,0 +1,3 @@
+*.pyc
+*.pyo
+.DS_Store
diff --git a/docs/_themes/pelican/layout.html b/docs/_themes/pelican/layout.html
new file mode 100644
index 00000000..aa1716aa
--- /dev/null
+++ b/docs/_themes/pelican/layout.html
@@ -0,0 +1,22 @@
+{% extends "basic/layout.html" %}
+{% block header %}
+ {{ super() }}
+ {% if pagename == 'index' %}
+
+ {% endif %}
+{% endblock %}
+{% block footer %}
+ {% if pagename == 'index' %}
+
+ {% endif %}
+{% endblock %}
+{# do not display relbars #}
+{% block relbar1 %}{% endblock %}
+{% block relbar2 %}
+ {% if theme_github_fork %}
+
+ {% 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
new file mode 100644
index 00000000..3cb2a3c1
--- /dev/null
+++ b/docs/_themes/pelican/static/pelican.css_t
@@ -0,0 +1,254 @@
+/*
+ * 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
new file mode 100644
index 00000000..ffbb7945
--- /dev/null
+++ b/docs/_themes/pelican/theme.conf
@@ -0,0 +1,10 @@
+[theme]
+inherit = basic
+stylesheet = pelican.css
+nosidebar = true
+pygments_style = fruity
+
+[options]
+index_logo_height = 120px
+index_logo =
+github_fork =
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644
index 00000000..0804c8b0
--- /dev/null
+++ b/docs/changelog.rst
@@ -0,0 +1,190 @@
+Release history
+###############
+
+3.3 (XXXX-XX-XX)
+================
+
+* Rename signals for better consistency (some plugins may need to be updated)
+* Move metadata extraction from generators to readers; metadata extraction no
+ longer article-specific
+* Deprecate ``FILES_TO_COPY`` in favor of ``STATIC_PATHS`` and
+ ``EXTRA_PATH_METADATA``
+
+3.2.1 and 3.2.2
+===============
+
+* Facilitate inclusion in FreeBSD Ports Collection
+
+3.2 (2013-04-24)
+================
+
+* Support for Python 3!
+* Override page save-to location from meta-data (enables using a static page as
+ the site's home page, for example)
+* Time period archives (per-year, per-month, and per-day archives of posts)
+* Posterous blog import
+* Improve WordPress blog import
+* Migrate plugins to separate repository
+* Improve HTML parser
+* Provide ability to show or hide categories from menu using
+ ``DISPLAY_CATEGORIES_ON_MENU`` option
+* Auto-regeneration can be told to ignore files via ``IGNORE_FILES`` setting
+* Improve post-generation feedback to user
+* For multilingual posts, use meta-data to designate which is the original
+ and which is the translation
+* Add ``.mdown`` to list of supported Markdown file extensions
+* Document-relative URL generation (``RELATIVE_URLS``) is now off by default
+
+3.1 (2012-12-04)
+================
+
+* Importer now stores slugs within files by default. This can be disabled with
+ the ``--disable-slugs`` option.
+* Improve handling of links to intra-site resources
+* Ensure WordPress import adds paragraphs for all types of line endings
+ in post content
+* Decode HTML entities within WordPress post titles on import
+* Improve appearance of LinkedIn icon in default theme
+* Add GitHub and Google+ social icons support in default theme
+* Optimize social icons
+* Add ``FEED_ALL_ATOM`` and ``FEED_ALL_RSS`` to generate feeds containing all posts regardless of their language
+* Split ``TRANSLATION_FEED`` into ``TRANSLATION_FEED_ATOM`` and ``TRANSLATION_FEED_RSS``
+* Different feeds can now be enabled/disabled individually
+* Allow for blank author: if ``AUTHOR`` setting is not set, author won't
+ default to ``${USER}`` anymore, and a post won't contain any author
+ information if the post author is empty
+* Move LESS and Webassets support from Pelican core to plugin
+* The ``DEFAULT_DATE`` setting now defaults to ``None``, which means that
+ articles won't be generated unless date metadata is specified
+* Add ``FILENAME_METADATA`` setting to support metadata extraction from filename
+* Add ``gzip_cache`` plugin to compress common text files into a ``.gz``
+ file within the same directory as the original file, preventing the server
+ (e.g. Nginx) from having to compress files during an HTTP call
+* Add support for AsciiDoc-formatted content
+* Add ``USE_FOLDER_AS_CATEGORY`` setting so that feature can be toggled on/off
+* Support arbitrary Jinja template files
+* Restore basic functional tests
+* New signals: ``generator_init``, ``get_generators``, and
+ ``article_generate_preread``
+
+3.0 (2012-08-08)
+================
+
+* Refactored the way URLs are handled
+* Improved the English documentation
+* Fixed packaging using ``setuptools`` entrypoints
+* Added ``typogrify`` support
+* Added a way to disable feed generation
+* Added support for ``DIRECT_TEMPLATES``
+* Allow multiple extensions for content files
+* Added LESS support
+* Improved the import script
+* Added functional tests
+* Rsync support in the generated Makefile
+* Improved feed support (easily pluggable with Feedburner for instance)
+* Added support for ``abbr`` in reST
+* Fixed a bunch of bugs :-)
+
+2.8 (2012-02-28)
+==================
+
+* Dotclear importer
+* Allow the usage of Markdown extensions
+* Themes are now easily extensible
+* Don't output pagination information if there is only one page
+* Add a page per author, with all their articles
+* Improved the test suite
+* Made the themes easier to extend
+* Removed Skribit support
+* Added a ``pelican-quickstart`` script
+* Fixed timezone-related issues
+* Added some scripts for Windows support
+* Date can be specified in seconds
+* Never fail when generating posts (skip and continue)
+* Allow the use of future dates
+* Support having different timezones per language
+* Enhanced the documentation
+
+2.7 (2011-06-11)
+==================
+
+* Use ``logging`` rather than echoing to stdout
+* Support custom Jinja filters
+* Compatibility with Python 2.5
+* Added a theme manager
+* Packaged for Debian
+* Added draft support
+
+2.6 (2011-03-08)
+==================
+
+* Changes in the output directory structure
+* Makes templates easier to work with / create
+* Added RSS support (was Atom-only)
+* Added tag support for the feeds
+* Enhance the documentation
+* Added another theme (brownstone)
+* Added translations
+* Added a way to use cleaner URLs with a rewrite url module (or equivalent)
+* Added a tag cloud
+* Added an autoreloading feature: the blog is automatically regenerated each time a modification is detected
+* Translate the documentation into French
+* Import a blog from an RSS feed
+* Pagination support
+* Added Skribit support
+
+2.5 (2010-11-20)
+==================
+
+* Import from Wordpress
+* Added some new themes (martyalchin / wide-notmyidea)
+* First bug report!
+* Linkedin support
+* Added a FAQ
+* Google Analytics support
+* Twitter support
+* Use relative URLs, not static ones
+
+2.4 (2010-11-06)
+================
+
+* Minor themes changes
+* Add Disqus support (so we have comments)
+* Another code refactoring
+* Added config settings about pages
+* Blog entries can also be generated in PDF
+
+2.3 (2010-10-31)
+================
+
+* Markdown support
+
+2.2 (2010-10-30)
+================
+
+* Prettify output
+* Manages static pages as well
+
+2.1 (2010-10-30)
+================
+
+* Make notmyidea the default theme
+
+2.0 (2010-10-30)
+================
+
+* Refactoring to be more extensible
+* Change into the setting variables
+
+1.2 (2010-09-28)
+================
+
+* Added a debug option
+* Added per-category feeds
+* Use filesystem to get dates if no metadata is provided
+* Add Pygments support
+
+1.1 (2010-08-19)
+================
+
+* First working version
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 00000000..40de84c7
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+import sys, os
+
+sys.path.append(os.path.abspath(os.pardir))
+
+from pelican import __version__, __major__
+
+# -- General configuration -----------------------------------------------------
+templates_path = ['_templates']
+extensions = ['sphinx.ext.autodoc',]
+source_suffix = '.rst'
+master_doc = 'index'
+project = 'Pelican'
+copyright = '2010, Alexis Metaireau and contributors'
+exclude_patterns = ['_build']
+version = __version__
+release = __major__
+
+# -- 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_static_path = ['_static']
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Pelicandoc'
+
+# -- Options for LaTeX output --------------------------------------------------
+latex_documents = [
+ ('index', 'Pelican.tex', 'Pelican Documentation',
+ 'Alexis Métaireau', 'manual'),
+]
+
+# -- Options for manual page output --------------------------------------------
+man_pages = [
+ ('index', 'pelican', 'pelican documentation',
+ ['Alexis Métaireau'], 1),
+ ('pelican-themes', 'pelican-themes', 'A theme manager for Pelican',
+ ['Mickaël Raybaud'], 1),
+ ('themes', 'pelican-theming', 'How to create themes for Pelican',
+ ['The Pelican contributors'], 1)
+]
diff --git a/docs/contribute.rst b/docs/contribute.rst
new file mode 100644
index 00000000..80d07644
--- /dev/null
+++ b/docs/contribute.rst
@@ -0,0 +1,144 @@
+How to contribute
+#################
+
+There are many ways to contribute to Pelican. You can improve the
+documentation, add missing features, and fix bugs (or just report them). You
+can also help out by reviewing and commenting on
+`existing issues `_.
+
+Don't hesitate to fork Pelican and submit a pull request on GitHub. When doing
+so, please adhere to the following guidelines.
+
+.. include:: ../CONTRIBUTING.rst
+
+Setting up the development environment
+======================================
+
+While there are many ways to set up one's development environment, following
+is a method that uses `virtualenv `_. If you don't
+have ``virtualenv`` installed, you can install it via::
+
+ $ pip install virtualenv
+
+Virtual environments allow you to work on Python projects which are isolated
+from one another so you can use different packages (and package versions) with
+different projects.
+
+To create and activate a virtual environment, use the following syntax::
+
+ $ virtualenv ~/virtualenvs/pelican
+ $ cd ~/virtualenvs/pelican
+ $ . bin/activate
+
+To clone the Pelican source::
+
+ $ git clone https://github.com/getpelican/pelican.git src/pelican
+
+To install the development dependencies::
+
+ $ cd src/pelican
+ $ pip install -r dev_requirements.txt
+
+To install Pelican and its dependencies::
+
+ $ python setup.py develop
+
+Or using ``pip``::
+
+ $ pip install -e .
+
+Coding standards
+================
+
+Try to respect what is described in the `PEP8 specification
+ `_ when making contributions. This
+can be eased via the `pep8 `_ or `flake8
+ `_ tools, the latter of which in
+particular will give you some useful hints about ways in which the
+code/formatting can be improved.
+
+Building the docs
+=================
+
+If you make changes to the documentation, you should preview your changes
+before committing them::
+
+ $ pip install sphinx
+ $ cd src/pelican/docs
+ $ make html
+
+Open ``_build/html/index.html`` in your browser to preview the documentation.
+
+Running the test suite
+======================
+
+Each time you add a feature, there are two things to do regarding tests:
+check that the existing tests pass, and add tests for the new feature
+or bugfix.
+
+The tests live in ``pelican/tests`` and you can run them using the
+"discover" feature of ``unittest``::
+
+ $ python -m unittest discover
+
+After making your changes and running the tests, you may see a test failure
+mentioning that "some generated files differ from the expected functional tests
+output." If you have made changes that affect the HTML output generated by
+Pelican, and the changes to that output are expected and deemed correct given
+the nature of your changes, then you should update the output used by the
+functional tests. To do so, you can use the following two commands::
+
+ $ pelican -o pelican/tests/output/custom/ -s samples/pelican.conf.py \
+ samples/content/
+ $ pelican -o pelican/tests/output/basic/ samples/content/
+
+Testing on Python 2 and 3
+-------------------------
+
+Testing on Python 3 currently requires some extra steps: installing
+Python 3-compatible versions of dependent packages and plugins.
+
+Tox_ is a useful tool to run tests on both versions. It will install the
+Python 3-compatible version of dependent packages.
+
+.. _Tox: http://testrun.org/tox/latest/
+
+Python 3 development tips
+=========================
+
+Here are some tips that may be useful when doing some code for both Python 2.7
+and Python 3 at the same time:
+
+- Assume every string and literal is unicode (import unicode_literals):
+
+ - Do not use prefix ``u'``.
+ - Do not encode/decode strings in the middle of sth. Follow the code to the
+ source (or target) of a string and encode/decode at the first/last possible
+ point.
+ - In other words, write your functions to expect and to return unicode.
+ - Encode/decode strings if e.g. the source is a Python function that is known
+ to handle this badly, e.g. strftime() in Python 2.
+
+- Use new syntax: print function, "except ... *as* e" (not comma) etc.
+- Refactor method calls like ``dict.iteritems()``, ``xrange()`` etc. in a way
+ that runs without code change in both Python versions.
+- Do not use magic method ``__unicode()__`` in new classes. Use only ``__str()__``
+ and decorate the class with ``@python_2_unicode_compatible``.
+- Do not start int literals with a zero. This is a syntax error in Py3k.
+- Unfortunately I did not find an octal notation that is valid in both
+ Pythons. Use decimal instead.
+- use six, e.g.:
+
+ - ``isinstance(.., basestring) -> isinstance(.., six.string_types)``
+ - ``isinstance(.., unicode) -> isinstance(.., six.text_type)``
+
+- ``setlocale()`` in Python 2 bails when we give the locale name as unicode,
+ and since we are using ``from __future__ import unicode_literals``, we do
+ that everywhere! As a workaround, I enclosed the localename with ``str()``;
+ in Python 2 this casts the name to a byte string, in Python 3 this should do
+ nothing, because the locale name already had been unicode.
+
+- Kept range() almost everywhere as-is (2to3 suggests list(range())), just
+ changed it where I felt necessary.
+
+- Changed xrange() back to range(), so it is valid in both Python versions.
diff --git a/docs/faq.rst b/docs/faq.rst
new file mode 100644
index 00000000..a8043e07
--- /dev/null
+++ b/docs/faq.rst
@@ -0,0 +1,189 @@
+Frequently Asked Questions (FAQ)
+################################
+
+Here are some frequently asked questions about Pelican.
+
+What's the best way to communicate a problem, question, or suggestion?
+======================================================================
+
+If you have a problem, question, or suggestion, please start by striking up a
+conversation on `#pelican on Freenode `_.
+Those who don't have an IRC client handy can jump in immediately via
+`IRC webchat `_. Because
+of differing time zones, you may not get an immediate response to your
+question, but please be patient and stay logged into IRC — someone will almost
+always respond if you wait long enough (it may take a few hours).
+
+If you're unable to resolve your issue or if you have a feature request, please
+refer to the `issue tracker `_.
+
+How can I help?
+================
+
+There are several ways to help out. First, you can report any Pelican
+suggestions or problems you might have via IRC or the `issue tracker
+`_. If submitting an issue
+report, please check the existing issue list first in order to avoid submitting
+a duplicate issue.
+
+If you want to contribute, please fork `the git repository
+ `_, create a new feature branch, make
+your changes, and issue a pull request. Someone will review your changes as
+soon as possible. Please refer to the :doc:`How to Contribute `
+section for more details.
+
+You can also contribute by creating themes and improving the documentation.
+
+Is it mandatory to have a configuration file?
+=============================================
+
+Configuration files are optional and are just an easy way to configure Pelican.
+For basic operations, it's possible to specify options while invoking Pelican
+via the command line. See ``pelican --help`` for more information.
+
+I'm creating my own theme. How do I use Pygments for syntax highlighting?
+=========================================================================
+
+Pygments adds some classes to the generated content. These classes are used by
+themes to style code syntax highlighting via CSS. Specifically, you can
+customize the appearance of your syntax highlighting via the ``.highlight pre``
+class in your theme's CSS file. To see how various styles can be used to render
+Django code, for example, use the style selector drop-down at top-right on the
+`Pygments project demo site `_.
+
+You can use the following example commands to generate a starting CSS file from
+a Pygments built-in style (in this case, "monokai") and then copy the generated
+CSS file to your new theme::
+
+ pygmentize -S monokai -f html -a .highlight > pygment.css
+ cp pygment.css path/to/theme/static/css/
+
+Don't forget to import your ``pygment.css`` file from your main CSS file.
+
+How do I create my own theme?
+==============================
+
+Please refer to :ref:`theming-pelican`.
+
+I want to use Markdown, but I got an error.
+===========================================
+
+Markdown is not a hard dependency for Pelican, so you will need to explicitly
+install it. You can do so by typing the following command, prepending ``sudo``
+if 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?
+==============================================
+
+Yes. For example, to include a modified date in a Markdown post, one could
+include the following at the top of the article::
+
+ Modified: 2012-08-08
+
+For reStructuredText, this metadata should of course be prefixed with a colon::
+
+ :Modified: 2012-08-08
+
+This metadata can then be accessed in the template::
+
+ {% if article.modified %}
+ Last modified: {{ article.modified }}
+ {% endif %}
+
+How do I assign custom templates on a per-page basis?
+=====================================================
+
+It's as simple as adding an extra line of metadata to any page or article that
+you want to have its own template. For example, this is how it would be handled
+for content in reST format::
+
+ :template: template_name
+
+For content in Markdown format::
+
+ Template: template_name
+
+Then just make sure your theme contains the relevant template file (e.g.
+``template_name.html``).
+
+How can I override the generated URL of a specific page or article?
+===================================================================
+
+Include ``url`` and ``save_as`` metadata in any pages or articles that you want
+to override the generated URL. Here is an example page in reST format::
+
+ Override url/save_as page
+ #########################
+
+ :url: override/url/
+ :save_as: override/url/index.html
+
+With this metadata, the page will be written to ``override/url/index.html``
+and Pelican will use url ``override/url/`` to link to this page.
+
+How can I use a static page as my home page?
+============================================
+
+The override feature mentioned above can be used to specify a static page as
+your home page. The following Markdown example could be stored in
+``content/pages/home.md``::
+
+ Title: Welcome to My Site
+ URL:
+ save_as: index.html
+
+ Thank you for visiting. Welcome!
+
+What if I want to disable feed generation?
+==========================================
+
+To disable feed generation, all feed settings should be set to ``None``.
+All but three feed settings already default to ``None``, so if you want to
+disable all feed generation, you only need to specify the following settings::
+
+ FEED_ALL_ATOM = None
+ 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.
+
+I'm getting a warning about feeds generated without SITEURL being set properly
+==============================================================================
+
+`RSS and Atom feeds require all URL links to be absolute
+`_.
+In order to properly generate links in Pelican you will need to set ``SITEURL``
+to the full path of your site.
+
+Feeds are still generated when this warning is displayed, but links within may
+be malformed and thus the feed may not validate.
+
+My feeds are broken since I upgraded to Pelican 3.x
+===================================================
+
+Starting in 3.0, some of the FEED setting names were changed to more explicitly
+refer to the Atom feeds they inherently represent (much like the FEED_RSS
+setting names). Here is an exact list of the renamed settings::
+
+ FEED -> FEED_ATOM
+ TAG_FEED -> TAG_FEED_ATOM
+ CATEGORY_FEED -> CATEGORY_FEED_ATOM
+
+Starting in 3.1, the new feed ``FEED_ALL_ATOM`` has been introduced: this
+feed will aggregate all posts regardless of their language. This setting
+generates ``'feeds/all.atom.xml'`` by default and ``FEED_ATOM`` now defaults to
+``None``. The following feed setting has also been renamed::
+
+ TRANSLATION_FEED -> TRANSLATION_FEED_ATOM
+
+Older themes that referenced the old setting names may not link properly.
+In order to rectify this, please update your theme for compatibility by changing
+the relevant values in your template files. For an example of complete feed
+headers and usage please check out the ``simple`` theme.
diff --git a/docs/fr/astuces.rst b/docs/fr/astuces.rst
new file mode 100644
index 00000000..3f9a3987
--- /dev/null
+++ b/docs/fr/astuces.rst
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 00000000..7a6fd118
--- /dev/null
+++ b/docs/fr/bases.rst
@@ -0,0 +1,58 @@
+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
new file mode 100644
index 00000000..5388dae3
--- /dev/null
+++ b/docs/fr/configuration.rst
@@ -0,0 +1,165 @@
+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
new file mode 100644
index 00000000..bf88c07e
--- /dev/null
+++ b/docs/fr/conventions.rst
@@ -0,0 +1,18 @@
+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
new file mode 100644
index 00000000..d945f447
--- /dev/null
+++ b/docs/fr/faq.rst
@@ -0,0 +1,40 @@
+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
new file mode 100644
index 00000000..2deb5050
--- /dev/null
+++ b/docs/fr/index.rst
@@ -0,0 +1,57 @@
+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
new file mode 100644
index 00000000..da327725
--- /dev/null
+++ b/docs/fr/installation.rst
@@ -0,0 +1,67 @@
+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
new file mode 100644
index 00000000..a3d25b55
--- /dev/null
+++ b/docs/fr/parametres_article.rst
@@ -0,0 +1,106 @@
+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
new file mode 100644
index 00000000..810fa785
--- /dev/null
+++ b/docs/fr/pelican-themes.rst
@@ -0,0 +1,172 @@
+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
new file mode 100644
index 00000000..20d9d41f
--- /dev/null
+++ b/docs/fr/themes.rst
@@ -0,0 +1,171 @@
+.. _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/getting_started.rst b/docs/getting_started.rst
new file mode 100644
index 00000000..383acdc4
--- /dev/null
+++ b/docs/getting_started.rst
@@ -0,0 +1,486 @@
+Getting started
+###############
+
+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://github.com/getpelican/pelican#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 either use your browser to open the
+files on your disk::
+
+ firefox output/index.html
+
+Or run a simple web server using Python::
+
+ cd output && python -m SimpleHTTPServer
+
+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
+
+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
+ ├── 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 *Writing articles 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.
+Alternatively, you can use automation tools that "wrap" the ``pelican`` command
+to simplify the process of generating, previewing, and uploading your site. One
+such tool is the ``Makefile`` that's automatically created for you when you use
+``pelican-quickstart`` to create a skeleton project. 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.
+
+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.
+
+The idea behind "pages" is that they are usually not temporal in nature and are
+used for content that does not change very often (e.g., "About" or "Contact"
+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
+information you need to provide in the form of metadata inside your files.
+
+If you are writing your content in reStructuredText format, you can provide
+this metadata in text files via the following syntax (give your file the
+``.rst`` extension)::
+
+ My super title
+ ##############
+
+ :date: 2010-10-03 10:20
+ :tags: thats, awesome
+ :category: yeah
+ :slug: my-super-post
+ :author: Alexis Metaireau
+ :summary: Short version for index and feeds
+
+Pelican implements an extension to reStructuredText to enable support for the
+``abbr`` HTML tag. To use it, write something like this in your post::
+
+ This will be turned into :abbr:`HTML (HyperText Markup Language)`.
+
+You can also use Markdown syntax (with a file ending in ``.md``,
+``.markdown``, ``.mkd``, or ``.mdown``). Markdown generation requires that you
+first explicitly install the ``Markdown`` package, which can be done via ``pip
+install Markdown``. Metadata syntax for Markdown posts should follow this
+pattern::
+
+ Title: My super title
+ Date: 2010-12-03 10:20
+ Category: Python
+ Tags: pelican, publishing
+ Slug: my-super-post
+ Author: Alexis Metaireau
+ Summary: Short version for index and feeds
+
+ This is the content of my super blog post.
+
+Pelican can also process HTML files ending in ``.html`` and ``.htm``. Pelican
+interprets the HTML in a very straightforward manner, reading metadata from
+``meta`` tags, the title from the ``title`` tag, and the body out from the
+``body`` tag::
+
+
+
+ My super title
+
+
+
+
+
+
+
+ This is the content of my super blog post.
+
+
+
+With HTML, there is one simple exception to the standard metadata: ``tags`` can
+be specified either via the ``tags`` metadata, as is standard in Pelican, or
+via the ``keywords`` metadata, as is standard in HTML. The two can be used
+interchangeably.
+
+Note that, aside from the title, none of this article metadata is mandatory:
+if the date is not specified and ``DEFAULT_DATE`` is set to ``fs``, Pelican
+will rely on the file's "mtime" timestamp, and the category can be determined
+by the directory in which the file resides. For example, a file located at
+``python/foobar/myfoobar.rst`` will have a category of ``foobar``. If you would
+like to organize your files in other ways where the name of the subfolder would
+not be a good category name, you can set the setting ``USE_FOLDER_AS_CATEGORY``
+to ``False``. When parsing dates given in the page metadata, Pelican supports
+the W3C's `suggested subset ISO 8601`__.
+
+__ `W3C ISO 8601`_
+
+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.
+
+You can also extract any metadata from the filename through a regular
+expression to be set in the ``FILENAME_METADATA`` setting. All named groups
+that are matched will be set in the metadata object. The default value for the
+``FILENAME_METADATA`` setting will only extract the date from the filename. For
+example, if you would like to extract both the date and the slug, you could set
+something like: ``'(?P\d{4}-\d{2}-\d{2})_(?P.*)'``
+
+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
+**Contact** pages. (See example filesystem layout below.)
+
+You can use the ``DISPLAY_PAGES_ON_MENU`` setting to control whether all those
+pages are displayed in the primary navigation menu. (Default is ``True``.)
+
+If you want to exclude any pages from being linked to or listed in the menu
+then add a ``status: hidden`` attribute to its metadata. This is useful for
+things like making error pages that fit the generated theme of your site.
+
+Linking to internal content
+---------------------------
+
+From Pelican 3.1 onwards, it is now possible to specify intra-site links to
+files in the *source content* hierarchy instead of files in the *generated*
+hierarchy. This makes it easier to link from the current post to other posts
+and images that may be sitting alongside the current post (instead of having
+to determine where those resources will be placed after site generation).
+
+To link to internal content (files in the ``content`` directory), use the
+following syntax: ``|filename|path/to/file``::
+
+
+ website/
+ ├── content
+ │ ├── article1.rst
+ │ ├── cat/
+ │ │ └── article2.md
+ │ └── pages
+ │ └── about.md
+ └── pelican.conf.py
+
+In this example, ``article1.rst`` could look like::
+
+ The first article
+ #################
+
+ :date: 2012-12-01 10:02
+
+ See below intra-site link examples in reStructuredText format.
+
+ `a link relative to content root <|filename|/cat/article2.md>`_
+ `a link relative to current file <|filename|cat/article2.md>`_
+
+and ``article2.md``::
+
+ Title: The second article
+ Date: 2012-12-01 10:02
+
+ See below intra-site link examples in Markdown format.
+
+ [a link relative to content root](|filename|/article1.rst)
+ [a link relative to current file](|filename|../article1.rst)
+
+Embedding non-article or non-page content is slightly different in that the
+directories need to be specified in ``pelicanconf.py`` file. The ``images``
+directory is configured for this by default but others will need to be added
+manually::
+
+ content
+ ├── images
+ │ └── han.jpg
+ └── misc
+ └── image-test.md
+
+And ``image-test.md`` would include::
+
+ 
+
+Any content can be linked in this way. What happens is that the ``images``
+directory gets copied to ``output/static/`` upon publishing. This is
+because ``images`` is in the ``settings["STATIC_PATHS"]`` list by default. If
+you want to have another directory, say ``pdfs`` you would need to add the
+following to ``pelicanconf.py``::
+
+ STATIC_PATHS = ['images', 'pdfs']
+
+And then the ``pdfs`` directory would also be copied to ``output/static/``.
+
+Importing an existing blog
+--------------------------
+
+It is possible to import your blog from Dotclear, WordPress, 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
+English [en] by default). With those settings in place, only articles with the
+default language will be listed, and each article will be accompanied by a list
+of available translations for that article.
+
+Pelican uses the article's URL "slug" to determine if two or more articles are
+translations of one another. The slug can be set manually in the file's
+metadata; if not set explicitly, Pelican will auto-generate the slug from the
+title of the article.
+
+Here is an example of two articles, one in English and the other in French.
+
+The English article::
+
+ Foobar is not dead
+ ##################
+
+ :slug: foobar-is-not-dead
+ :lang: en
+
+ That's true, foobar is still alive!
+
+And the French version::
+
+ Foobar n'est pas mort !
+ #######################
+
+ :slug: foobar-is-not-dead
+ :lang: fr
+
+ Oui oui, foobar est toujours vivant !
+
+Post content quality notwithstanding, you can see that only item in common
+between the two articles is the slug, which is functioning here as an
+identifier. If you'd rather not explicitly define the slug this way, you must
+then instead ensure that the translated article titles are identical, since the
+slug will be auto-generated from the article title.
+
+If you do not want the original version of one specific article to be detected
+by the ``DEFAULT_LANG`` setting, use the ``translation`` metadata to specify
+which posts are translations::
+
+ Foobar is not dead
+ ##################
+
+ :slug: foobar-is-not-dead
+ :lang: en
+ :translation: true
+
+ That's true, foobar is still alive!
+
+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.
+
+For reStructuredText, use the code-block directive::
+
+ .. code-block:: identifier
+
+
+
+For Markdown, include the language identifier just above the code block,
+indenting both the identifier and code::
+
+ A block of text.
+
+ :::identifier
+
+
+The specified identifier (e.g. ``python``, ``ruby``) should be one that
+appears on the `list of available lexers `_.
+
+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 page.
+
+.. _virtualenv: http://www.virtualenv.org/
+.. _W3C ISO 8601: http://www.w3.org/TR/NOTE-datetime
diff --git a/docs/importer.rst b/docs/importer.rst
new file mode 100644
index 00000000..9a0c513e
--- /dev/null
+++ b/docs/importer.rst
@@ -0,0 +1,107 @@
+.. _import:
+
+=================================
+ Import from other blog software
+=================================
+
+
+Description
+===========
+
+``pelican-import`` is a command-line tool for converting articles from other
+software to reStructuredText or Markdown. The supported import formats are:
+
+- WordPress XML export
+- Dotclear export
+- Posterous API
+- RSS/Atom feed
+
+The conversion from HTML to reStructuredText or Markdown relies on `Pandoc`_.
+For Dotclear, if the source posts are written with Markdown syntax, they will
+not be converted (as Pelican also supports Markdown).
+
+
+Dependencies
+============
+
+``pelican-import`` has some dependencies not required by the rest of Pelican:
+
+- *BeautifulSoup4* and *lxml*, for WordPress and Dotclear import. Can be installed like
+ any other Python package (``pip install BeautifulSoup4 lxml``).
+- *Feedparser*, for feed import (``pip install feedparser``).
+- *Pandoc*, see the `Pandoc site`_ for installation instructions on your
+ operating system.
+
+.. _Pandoc: http://johnmacfarlane.net/pandoc/
+.. _Pandoc site: http://johnmacfarlane.net/pandoc/installing.html
+
+
+Usage
+=====
+
+::
+
+ pelican-import [-h] [--wpfile] [--dotclear] [--posterous] [--feed] [-o OUTPUT]
+ [-m MARKUP] [--dir-cat] [--dir-page] [--strip-raw] [--disable-slugs]
+ [-e EMAIL] [-p PASSWORD]
+ input|api_token
+
+Positional arguments
+--------------------
+
+ input The input file to read
+ api_token [Posterous only] api_token can be obtained from http://posterous.com/api/
+
+Optional arguments
+------------------
+
+ -h, --help Show this help message and exit
+ --wpfile WordPress XML export (default: False)
+ --dotclear Dotclear export (default: False)
+ --posterous Posterous API (default: False)
+ --feed Feed to parse (default: False)
+ -o OUTPUT, --output OUTPUT
+ Output path (default: output)
+ -m MARKUP, --markup MARKUP
+ Output markup format (supports rst & markdown)
+ (default: rst)
+ --dir-cat Put files in directories with categories name
+ (default: False)
+ --dir-page Put files recognised as pages in "pages/" sub-
+ directory (wordpress import only) (default: False)
+ --strip-raw Strip raw HTML code that can't be converted to markup
+ such as flash embeds or iframes (wordpress import
+ only) (default: False)
+ --disable-slugs Disable storing slugs from imported posts within
+ output. With this disabled, your Pelican URLs may not
+ be consistent with your original posts. (default:
+ False)
+ -e EMAIL, --email=EMAIL
+ Email used to authenticate Posterous API
+ -p PASSWORD, --password=PASSWORD
+ Password used to authenticate Posterous API
+
+
+Examples
+========
+
+For WordPress::
+
+ $ pelican-import --wpfile -o ~/output ~/posts.xml
+
+For Dotclear::
+
+ $ pelican-import --dotclear -o ~/output ~/backup.txt
+
+for Posterous::
+
+ $ pelican-import --posterous -o ~/output --email= --password=
+
+
+Tests
+=====
+
+To test the module, one can use sample files:
+
+- for WordPress: http://wpcandy.com/made/the-sample-post-collection
+- for Dotclear: http://themes.dotaddict.org/files/public/downloads/lorem-backup.txt
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 00000000..eceb407f
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,81 @@
+Pelican
+=======
+
+Pelican is a static site generator, written in Python_.
+
+* Write your content directly with your editor of choice (vim!)
+ 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
+
+Features
+--------
+
+Pelican 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.)
+* Theming support (themes are created using Jinja2_ templates)
+* Publication of articles in multiple languages
+* Atom/RSS feeds
+* Code syntax highlighting
+* PDF generation of the articles/pages (optional)
+* Import from WordPress, Dotclear, or RSS feeds
+* Integration with external tools: Twitter, Google Analytics, etc. (optional)
+
+Why the name "Pelican"?
+-----------------------
+
+"Pelican" is an anagram for *calepin*, which means "notebook" in French. ;)
+
+Source code
+-----------
+
+You can access the source code at: https://github.com/getpelican/pelican
+
+Feedback / Contact us
+---------------------
+
+If you want to see new features in Pelican, don't hesitate to offer suggestions,
+clone the repository, etc. There are many ways to :doc:`contribute`.
+That's open source, dude!
+
+Send a message to "authors at getpelican dot com" with any requests/feedback! You
+can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC
+client handy, use the webchat_ for quick feedback.
+
+Documentation
+-------------
+
+A French version of the documentation is available at :doc:`fr/index`.
+
+.. toctree::
+ :maxdepth: 2
+
+ getting_started
+ settings
+ themes
+ plugins
+ internals
+ pelican-themes
+ importer
+ faq
+ tips
+ contribute
+ report
+ changelog
+
+.. Links
+
+.. _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
+.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican
+.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
diff --git a/docs/internals.rst b/docs/internals.rst
new file mode 100644
index 00000000..704122ba
--- /dev/null
+++ b/docs/internals.rst
@@ -0,0 +1,90 @@
+Pelican internals
+#################
+
+This section describe how Pelican works internally. As you'll see, it's
+quite simple, but a bit of documentation doesn't hurt. :)
+
+You can also find in the :doc:`report` section an excerpt of a report the
+original author wrote with some software design information.
+
+.. _report: :doc:`report`
+
+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
+files, and the output is a blog, but both input and output can be anything you
+want.
+
+The logic is separated into different classes and concepts:
+
+* **Writers** are responsible for writing files: .html files, RSS feeds, and so
+ 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
+ reStructuredText for now, but the system is extensible). Given a file, they
+ return metadata (author, tags, category, etc.) and content (HTML-formatted).
+
+* **Generators** generate the different outputs. For instance, Pelican comes with
+ ``ArticlesGenerator`` and ``PageGenerator``. Given a configuration, they can do
+ whatever they want. Most of the time, it's generating files from inputs.
+
+* Pelican also uses templates, so it's easy to write your own theme. The
+ syntax is `Jinja2 `_ and is very easy to learn, so
+ don't hesitate to jump in and build your own theme.
+
+How to implement a new reader?
+==============================
+
+Is there an awesome markup language you want to add to Pelican?
+Well, the only thing you have to do is to create a class with a ``read``
+method that returns HTML content and some metadata.
+
+Take a look at the Markdown reader::
+
+ class MarkdownReader(Reader):
+ enabled = bool(Markdown)
+
+ def read(self, source_path):
+ """Parse content and metadata of markdown files"""
+ text = pelican_open(source_path)
+ md = Markdown(extensions = ['meta', 'codehilite'])
+ content = md.convert(text)
+
+ metadata = {}
+ for name, value in md.Meta.items():
+ name = name.lower()
+ meta = self.process_metadata(name, value[0])
+ metadata[name] = meta
+ return content, metadata
+
+Simple, isn't it?
+
+If your new reader requires additional Python dependencies, then you should wrap
+their ``import`` statements in a ``try...except`` block. Then inside the reader's
+class, set the ``enabled`` class attribute to mark import success or failure.
+This makes it possible for users to continue using their favourite markup method
+without needing to install modules for formats they don't use.
+
+How to implement a new generator?
+=================================
+
+Generators have two important methods. You're not forced to create
+both; only the existing ones will be called.
+
+* ``generate_context``, that is called first, for all the generators.
+ Do whatever you have to do, and update the global context if needed. This
+ context is shared between all generators, and will be passed to the
+ templates. For instance, the ``PageGenerator`` ``generate_context`` method
+ finds all the pages, transforms them into objects, and populates the context
+ with them. Be careful *not* to output anything using this context at this
+ stage, as it is likely to change by the effect of other generators.
+
+* ``generate_output`` is then called. And guess what is it made for? Oh,
+ generating the output. :) It's here that you may want to look at the context
+ and call the methods of the ``writer`` object that is passed as the first
+ argument of this function. In the ``PageGenerator`` example, this method will
+ look at all the pages recorded in the global context and output a file on
+ the disk (using the writer method ``write_file``) for each page encountered.
diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst
new file mode 100644
index 00000000..23be8355
--- /dev/null
+++ b/docs/pelican-themes.rst
@@ -0,0 +1,164 @@
+pelican-themes
+##############
+
+
+
+Description
+===========
+
+``pelican-themes`` is a command line tool for managing themes for Pelican.
+
+
+Usage
+"""""
+
+| pelican-themes [-h] [-l] [-i theme path [theme path ...]]
+| [-r theme name [theme name ...]]
+| [-s theme path [theme path ...]] [-v] [--version]
+
+Optional arguments:
+"""""""""""""""""""
+
+
+-h, --help Show the help an exit
+
+-l, --list Show the themes already installed
+
+-i theme_path, --install theme_path One or more themes to install
+
+-r theme_name, --remove theme_name One or more themes to remove
+
+-s theme_path, --symlink theme_path Same as "--install", but create a symbolic link instead of copying the theme.
+ Useful for theme development
+
+-v, --verbose Verbose output
+
+--version Print the version of this script
+
+
+
+Examples
+========
+
+
+Listing the installed themes
+""""""""""""""""""""""""""""
+
+With ``pelican-themes``, you can see the available themes by using the ``-l`` or ``--list`` option:
+
+.. code-block:: console
+
+ $ pelican-themes -l
+ notmyidea
+ two-column@
+ simple
+ $ pelican-themes --list
+ notmyidea
+ two-column@
+ simple
+
+In this example, we can see there are three themes available: ``notmyidea``, ``simple``, and ``two-column``.
+
+``two-column`` is prefixed with an ``@`` because this theme is not copied to the Pelican theme path, but is instead just linked to it (see `Creating symbolic links`_ for details about creating symbolic links).
+
+Note that you can combine the ``--list`` option with the ``-v`` or ``--verbose`` option to get more verbose output, like this:
+
+.. 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
+
+
+Installing themes
+"""""""""""""""""
+
+You can install one or more themes using the ``-i`` or ``--install`` option.
+This option takes as argument the path(s) of the theme(s) you want to install, and can be combined with the verbose option:
+
+.. 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
+
+
+Removing themes
+"""""""""""""""
+
+The ``pelican-themes`` command can also remove themes from the Pelican themes path.
+The ``-r`` or ``--remove`` option takes as argument the name(s) of the theme(s) you want to remove, and can be combined with the ``--verbose`` option.
+
+.. code-block:: console
+
+ # pelican-themes --remove two-column
+
+.. code-block:: console
+
+ # pelican-themes -r martyachin notmyidea-cmd -v
+
+
+
+
+
+Creating symbolic links
+"""""""""""""""""""""""
+
+``pelican-themes`` can also install themes by creating symbolic links instead of copying entire themes into the Pelican themes path.
+
+To symbolically link a theme, you can use the ``-s`` or ``--symlink``, which works exactly as the ``--install`` option:
+
+.. code-block:: console
+
+ # pelican-themes --symlink ~/Dev/Python/pelican-themes/two-column
+
+In this example, the ``two-column`` theme is now symbolically linked to the Pelican themes path, so we can use it, but we can also modify it without having to reinstall it after each modification.
+
+This is useful for theme development:
+
+.. 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
+
+
+
+Doing several things at once
+""""""""""""""""""""""""""""
+
+The ``--install``, ``--remove`` and ``--symlink`` option are not mutually exclusive, so you can combine them in the same command line to do more than one operation at time, like this:
+
+
+.. 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
+
+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/plugins.rst b/docs/plugins.rst
new file mode 100644
index 00000000..9bf08ff3
--- /dev/null
+++ b/docs/plugins.rst
@@ -0,0 +1,125 @@
+.. _plugins:
+
+Plugins
+#######
+
+Beginning with version 3.0, Pelican supports plugins. Plugins are a way to add
+features to Pelican without having to directly modify the Pelican core.
+
+How to use plugins
+==================
+
+To load plugins, you have to specify them in your settings file. There are two
+ways to do so. The first method is to specify strings with the path to the
+callables::
+
+ PLUGINS = ['package.myplugin',]
+
+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::
+
+ PLUGIN_PATH = "plugins"
+ PLUGINS = ["list", "of", "plugins"]
+
+Where to find plugins
+=====================
+
+We maintain a separate repository of plugins for people to share and use.
+Please visit the `pelican-plugins`_ repository for a list of available plugins.
+
+.. _pelican-plugins: https://github.com/getpelican/pelican-plugins
+
+Please note that while we do our best to review and maintain these plugins,
+they are submitted by the Pelican community and thus may have varying levels of
+support and interoperability.
+
+How to create plugins
+=====================
+
+Plugins are based on the concept of signals. Pelican sends signals, and plugins
+subscribe to those signals. The list of signals are defined in a subsequent
+section.
+
+The only rule to follow for plugins is to define a ``register`` callable, in
+which you map the signals to your plugin logic. Let's take a simple example::
+
+ from pelican import signals
+
+ def test(sender):
+ print "%s initialized !!" % sender
+
+ def register():
+ signals.initialized.connect(test)
+
+List of signals
+===============
+
+Here is the list of currently implemented signals:
+
+============================= ============================ ===========================================================================
+Signal Arguments Description
+============================= ============================ ===========================================================================
+initialized pelican object
+finalized pelican object invoked after all the generators are executed and just before pelican exits
+ usefull for custom post processing actions, such as:
+ - minifying js/css assets.
+ - notify/ping search engines with an updated sitemap.
+generator_init generator invoked in the Generator.__init__
+article_generate_context article_generator, metadata
+article_generate_preread article_generator invoked before a article is read in ArticlesGenerator.generate_context;
+ use if code needs to do something before every article is parsed
+article_generator_init article_generator invoked in the ArticlesGenerator.__init__
+article_generator_finalized article_generator invoked at the end of ArticlesGenerator.generate_context
+get_generators generators invoked in Pelican.get_generator_classes,
+ can return a Generator, or several
+ generator in a tuple or in a list.
+page_generate_context page_generator, metadata
+page_generator_init page_generator invoked in the PagesGenerator.__init__
+page_generator_finalized page_generator invoked at the end of PagesGenerator.generate_context
+content_object_init content_object invoked at the end of Content.__init__ (see note below)
+============================= ============================ ===========================================================================
+
+The list is currently small, so don't hesitate to add signals and make a pull
+request if you need them!
+
+.. note::
+
+ The signal ``content_object_init`` can send a different type of object as
+ the argument. If you want to register only one type of object then you will
+ need to specify the sender when you are connecting to the signal.
+
+ ::
+
+ from pelican import signals
+ from pelican import contents
+
+ def test(sender, instance):
+ print "%s : %s content initialized !!" % (sender, instance)
+
+ def register():
+ signals.content_object_init.connect(test, sender=contents.Article)
+
+.. note::
+
+ After Pelican 3.2, signal names were standardized. Older plugins
+ may need to be updated to use the new names:
+
+ ========================== ===========================
+ Old name New name
+ ========================== ===========================
+ article_generate_context article_generator_context
+ article_generate_finalized article_generator_finalized
+ article_generate_preread article_generator_preread
+ pages_generate_context page_generator_context
+ pages_generate_preread page_generator_preread
+ pages_generator_finalized page_generator_finalized
+ pages_generator_init page_generator_init
+ static_generate_context static_generator_context
+ static_generate_preread static_generator_preread
+ ========================== ===========================
diff --git a/docs/report.rst b/docs/report.rst
new file mode 100644
index 00000000..f3ddff31
--- /dev/null
+++ b/docs/report.rst
@@ -0,0 +1,121 @@
+Some history about Pelican
+##########################
+
+.. warning::
+
+ This page comes from a report the original author (Alexis Métaireau) wrote
+ right after writing Pelican, in December 2010. The information may not be
+ up-to-date.
+
+Pelican is a simple static blog generator. It parses markup files
+(Markdown or reStructuredText for now) and generates an HTML folder
+with all the files in it.
+I've chosen to use Python to implement Pelican because it seemed to
+be simple and to fit to my needs. I did not wanted to define a class for
+each thing, but still wanted to keep my things loosely coupled.
+It turns out that it was exactly what I wanted. From time to time,
+thanks to the feedback of some users, it took me a very few time to
+provide fixes on it. So far, I've re-factored the Pelican code by two
+times; each time took less than 30 minutes.
+
+Use case
+========
+
+I was previously using WordPress, a solution you can host on a web
+server to manage your blog. Most of the time, I prefer using markup
+languages such as Markdown or reStructuredText to type my articles.
+To do so, I use vim. I think it is important to let the people choose the
+tool they want to write the articles. In my opinion, a blog manager
+should just allow you to take any kind of input and transform it to a
+weblog. That's what Pelican does.
+You can write your articles using the tool you want, and the markup
+language you want, and then generate a static HTML weblog.
+
+.. image:: _static/overall.png
+
+To be flexible enough, Pelican has template support, so you can easily write
+your own themes if you want to.
+
+Design process
+==============
+
+Pelican came from a need I have. I started by creating a single file
+application, and I have make it grow to support what it does by now.
+To start, I wrote a piece of documentation about what I wanted to do.
+Then, I created the content I wanted to parse (the reStructuredText files)
+and started experimenting with the code. Pelican was 200 lines long and
+contained almost ten functions and one class when it was first usable.
+
+I have been facing different problems all over the time and wanted to
+add features to Pelican while using it. The first change I have done was
+to add the support of a settings file. It is possible to pass the options to
+the command line, but can be tedious if there is a lot of them.
+In the same way, I have added the support of different things over
+time: Atom feeds, multiple themes, multiple markup support, etc.
+At some point, it appears that the "only one file" mantra was not good
+enough for Pelican, so I decided to rework a bit all that, and split this in
+multiple different files.
+
+I’ve separated the logic in different classes and concepts:
+
+* *writers* are responsible of all the writing process of the files.
+ They are responsible of writing .html files, RSS feeds and so 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 (Markdown and
+ reStructuredText for now, but the system is extensible). Given a
+ file, they return metadata (author, tags, category, etc) and
+ content (HTML formatted).
+
+* *generators* generate the different outputs. For instance, Pelican
+ comes with an ArticlesGenerator and PagesGenerator, into
+ others. Given a configuration, they can do whatever you want
+ them to do. Most of the time it's generating files from inputs
+ (user inputs and files).
+
+I also deal with contents objects. They can be ``Articles``, ``Pages``,
+``Quotes``, or whatever you want. They are defined in the ``contents.py``
+module and represent some content to be used by the program.
+
+In more detail
+==============
+
+Here is an overview of the classes involved in Pelican.
+
+.. image:: _static/uml.jpg
+
+The interface does not really exist, and I have added it only to clarify the
+whole picture. I do use duck typing and not interfaces.
+
+Internally, the following process is followed:
+
+* First of all, the command line is parsed, and some content from
+ the user is used to initialize the different generator objects.
+
+* A ``context`` is created. It contains the settings from the command
+ line and a settings file if provided.
+* The ``generate_context`` method of each generator is called, updating
+ the context.
+
+* The writer is created and given to the ``generate_output`` method of
+ each generator.
+
+I make two calls because it is important that when the output is
+generated by the generators, the context will not change. In other
+words, the first method ``generate_context`` should modify the context,
+whereas the second ``generate_output`` method should not.
+
+Then, it is up to the generators to do what the want, in the
+``generate_context`` and ``generate_content`` method.
+Taking the ``ArticlesGenerator`` class will help to understand some others
+concepts. Here is what happens when calling the ``generate_context``
+method:
+
+* Read the folder “path”, looking for restructured text files, load
+ each of them, and construct a content object (``Article``) with it. To do so,
+ use ``Reader`` objects.
+* Update the ``context`` with all those articles.
+
+Then, the ``generate_content`` method uses the ``context`` and the ``writer`` to
+generate the wanted output.
diff --git a/docs/settings.rst b/docs/settings.rst
new file mode 100644
index 00000000..78a0ddf7
--- /dev/null
+++ b/docs/settings.rst
@@ -0,0 +1,616 @@
+Settings
+########
+
+Pelican is configurable thanks to a configuration file you can pass to
+the command line::
+
+ $ pelican -s path/to/your/settingsfile.py path
+
+Settings are configured in the form of a Python module (a file). You can see an
+example by looking at `/samples/pelican.conf.py
+`_
+
+All the setting identifiers must be set in all-caps, otherwise they will not be
+processed. Setting values that are numbers (5, 20, etc.), booleans (True,
+False, None, etc.), dictionaries, or tuples should *not* be enclosed in
+quotation marks. All other values (i.e., strings) *must* be enclosed in
+quotation marks.
+
+Unless otherwise specified, settings that refer to paths can be either absolute
+or relative to the configuration file.
+
+The settings you define in the configuration file will be passed to the
+templates, which allows you to use your settings to add site-wide content.
+
+Here is a list of settings for Pelican:
+
+Basic settings
+==============
+
+===================================================================== =====================================================================
+Setting name (default value) What does it do?
+===================================================================== =====================================================================
+`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.
+`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
+ template. Templates may or may not honor this
+ setting.
+`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.
+ 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
+ and pages.
+`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
+ the filename.
+ For example, if you would like to extract both the
+ 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
+ full path relative to the content source directory.
+ See :ref:`path_metadata`.
+`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
+ 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 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.
+ 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
+ here or a single string representing one locale.
+ When providing a list, all the locales will be tried
+ until one works.
+`MARKUP` (``('rst', 'md')``) A list of available markup languages you want
+ to use. For the moment, the only available values
+ are `rst`, `md`, `markdown`, `mkd`, `mdown`, `html`, and `htm`.
+`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
+ will use. Refer to the Python Markdown documentation's
+ `Extensions section `_
+ for a complete list of supported extensions. (Note that
+ defining this in your settings file will override and
+ replace the default values. If your goal is to *add*
+ 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.
+`PDF_GENERATOR` (``False``) Set to ``True`` if you want PDF versions of your documents to be.
+ generated. You will need to install ``rst2pdf``.
+`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.
+ 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
+ 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,
+ 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
+ the blog entries. See :ref:`template_pages`.
+`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
+ generate Atom and RSS feeds. See the *Timezone*
+ 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``
+`DIRECT_TEMPLATES` (``('index', 'tags', 'categories', '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
+ be the default length 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.
+ 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
+ `_
+===================================================================== =====================================================================
+
+.. [#] Default is the system locale.
+
+
+URL settings
+------------
+
+The first thing to understand is that there are currently two supported methods
+for URL formation: *relative* and *absolute*. Document-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
+configuration files for local development and publishing, respectively.
+
+You can customize the URLs and locations where files will be saved. The URLs and
+SAVE_AS variables use Python's format strings. These variables allow you to place
+your articles in a location such as ``{slug}/index.html`` and link to them as
+``{slug}`` for clean URLs. These settings give you the flexibility to place your
+articles and pages anywhere you want.
+
+.. note::
+ 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 at http://bit.ly/cNcJUC for more
+information.
+
+Also, you can use other file metadata attributes as well:
+
+* slug
+* date
+* lang
+* author
+* category
+
+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'``
+
+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/``.
+
+Pelican can optionally create per-year, per-month, and per-day archives of your
+posts. These secondary archives are disabled by default but are automatically
+enabled if you supply format strings for their respective `_SAVE_AS` settings.
+Period archives fit intuitively with the hierarchical model of web URLs and can
+make it easier for readers to navigate through the posts you've written over time.
+
+Example usage:
+
+* 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 posts for
+the month at 'posts/2011/Aug/index.html'.
+
+.. note::
+ Period archives work best when the final path segment is 'index.html'.
+ This way a reader can remove a portion of your URL and automatically
+ 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
+ default language.
+`ARTICLE_LANG_SAVE_AS` (``'{slug}-{lang}.html'``) The place where we will save an article 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
+ your server config.
+`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
+ 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.
+`TAGS_URL` (``'tag/{slug}.html'``) The URL to use for the tag list.
+`TAGS_SAVE_AS` (``'tags.html'``) The location to save the tag list.
+`AUTHOR_URL` (``'author/{slug}.html'``) The URL to use for an author.
+`AUTHOR_SAVE_AS` (``'author/{slug}.html'``) The location to save an author.
+`AUTHORS_URL` (``'authors.html'``) The URL to use for the author list.
+`AUTHORS_SAVE_AS` (``'authors.html'``) The location to save the author list.
+`_SAVE_AS` The location to save content generated from direct
+ templates. Where is the
+ upper case template name.
+`ARCHIVES_SAVE_AS` (``'archives.html'``) The location to save the article archives page.
+`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.
+==================================================== =====================================================
+
+.. note::
+
+ 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 ``False`` to prevent the
+ relevant page from being generated.
+
+Timezone
+--------
+
+If no timezone is defined, UTC is assumed. This means that the generated Atom
+and RSS feeds will contain incorrect date information if your locale is not UTC.
+
+Pelican issues a warning in case this setting is not defined, as it was not
+mandatory in previous versions.
+
+Have a look at `the wikipedia page`_ to get a list of valid timezone values.
+
+.. _the wikipedia page: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+
+
+Date format and locale
+----------------------
+
+If no DATE_FORMATS are set, Pelican will fall back to DEFAULT_DATE_FORMAT. If
+you need to maintain multiple languages with different date formats, you can
+set this dict using the language name (``lang`` metadata in your post content)
+as the key. Regarding available format codes, see `strftime document of python`_ :
+
+.. parsed-literal::
+
+ DATE_FORMATS = {
+ 'en': '%a, %d %b %Y',
+ 'jp': '%Y-%m-%d(%a)',
+ }
+
+You can set locale to further control date format:
+
+.. parsed-literal::
+
+ LOCALE = ('usa', 'jpn', # On Windows
+ 'en_US', 'ja_JP' # On Unix/Linux
+ )
+
+Also, it is possible to set different locale settings for each language. If you
+put (locale, format) tuples in the dict, this will override the LOCALE setting
+above:
+
+.. parsed-literal::
+ # On Unix/Linux
+ DATE_FORMATS = {
+ 'en': ('en_US','%a, %d %b %Y'),
+ 'jp': ('ja_JP','%Y-%m-%d(%a)'),
+ }
+
+ # On Windows
+ DATE_FORMATS = {
+ 'en': ('usa','%a, %d %b %Y'),
+ 'jp': ('jpn','%Y-%m-%d(%a)'),
+ }
+
+This is a list of available `locales on Windows`_ . On Unix/Linux, usually you
+can get a list of available locales via the ``locale -a`` command; see manpage
+`locale(1)`_ for more information.
+
+
+.. _strftime document of python: http://docs.python.org/library/datetime.html#strftime-strptime-behavior
+
+.. _locales on Windows: http://msdn.microsoft.com/en-us/library/cdax410z%28VS.71%29.aspx
+
+.. _locale(1): http://linux.die.net/man/1/locale
+
+
+.. _template_pages:
+
+Template pages
+==============
+
+If you want to generate custom pages besides your blog entries, you can point
+any Jinja2 template file with a path pointing to the file and the destination
+path for the generated file.
+
+For instance, if you have a blog with three static pages — a list of books,
+your resume, and a contact page — you could have::
+
+ TEMPLATE_PAGES = {'src/books.html': 'dest/books.html',
+ 'src/resume.html': 'dest/resume.html',
+ 'src/contact.html': 'dest/contact.html'}
+
+
+.. _path_metadata:
+
+Path metadata
+=============
+
+Not all metadata needs to be `embedded in source file itself`__. For
+example, blog posts are often named following a ``YYYY-MM-DD-SLUG.rst``
+pattern, or nested into ``YYYY/MM/DD-SLUG`` directories. To extract
+metadata from the filename or path, set ``FILENAME_METADATA`` or
+``PATH_METADATA`` to regular expressions that use Python's `group name
+notation`_ ``(?P…)``. If you want to attach additional metadata
+but don't want to encode it in the path, you can set
+``EXTRA_PATH_METADATA``:
+
+.. parsed-literal::
+
+ EXTRA_PATH_METADATA = {
+ 'relative/path/to/file-1': {
+ 'key-1a': 'value-1a',
+ 'key-1b': 'value-1b',
+ },
+ 'relative/path/to/file-2': {
+ 'key-2': 'value-2',
+ },
+ }
+
+This can be a convenient way to shift the installed location of a
+particular file:
+
+.. parsed-literal::
+
+ # Take advantage of the following defaults
+ # STATIC_SAVE_AS = '{path}'
+ # STATIC_URL = '{path}'
+ STATIC_PATHS = [
+ 'extra/robots.txt',
+ ]
+ EXTRA_PATH_METADATA = {
+ 'extra/robots.txt': {'path': 'robots.txt'},
+ }
+
+__ internal_metadata__
+.. _group name notation:
+ http://docs.python.org/3/library/re.html#regular-expression-syntax
+
+Feed settings
+=============
+
+By default, Pelican uses Atom feeds. However, it is also possible to use RSS
+feeds if you prefer.
+
+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
+ 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:
+ 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:
+ 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.
+`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
+ quantity is unrestricted by default.
+================================================ =====================================================
+
+If you don't want to generate some or any of these feeds, set the above variables to ``None``.
+
+.. [2] %s is the name of the category.
+
+FeedBurner
+----------
+
+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).
+
+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`.
+
+Pagination
+==========
+
+The default behaviour of Pelican is to list all the article titles along
+with a short description on the index page. While it works pretty well
+for small-to-medium blogs, for sites with large quantity of articles it would
+be convenient to have a way to paginate the list.
+
+You can use the following settings to configure the pagination.
+
+================================================ =====================================================
+Setting name (default value) What does it do?
+================================================ =====================================================
+`DEFAULT_ORPHANS` (``0``) The minimum number of articles allowed on the
+ last page. Use this when you don't want to
+ have a last page with very few articles.
+`DEFAULT_PAGINATION` (``False``) The maximum number of articles to include on a
+ page, not including orphans. False to disable
+ pagination.
+================================================ =====================================================
+
+Tag cloud
+=========
+
+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?
+================================================ =====================================================
+`TAG_CLOUD_STEPS` (``4``) Count of different font sizes in the tag
+ cloud.
+`TAG_CLOUD_MAX_ITEMS` (``100``) Maximum number of tags in the cloud.
+================================================ =====================================================
+
+The default theme does not support tag clouds, but it is pretty easy to add::
+
+
+ {% for tag in tag_cloud %}
+ {{ tag.0 }}
+ {% endfor %}
+
+
+You should then also define a CSS style with the appropriate classes (tag-0 to tag-N, where
+N matches `TAG_CLOUD_STEPS` -1).
+
+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.
+===================================================== =====================================================
+
+.. [3] %s is the language
+
+Ordering content
+=================
+
+================================================ =====================================================
+Setting name (default value) What does it do?
+================================================ =====================================================
+`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
+ alphabetical order; default lists alphabetically.)
+================================================ =====================================================
+
+Themes
+======
+
+Creating Pelican themes is addressed in a dedicated section (see :ref:`theming-pelican`).
+However, here are the settings that are related to themes.
+
+================================================ =====================================================
+Setting name (default value) What does it do?
+================================================ =====================================================
+`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_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.
+`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:
+
+* notmyidea
+* simple (a synonym for "plain text" :)
+
+There are a number of other themes available at http://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
+and modifying a pre-existing theme. Here is :doc:`a guide on how to create your theme `.
+
+Following are example ways to specify your preferred theme::
+
+ # Specify name of a built-in theme
+ THEME = "notmyidea"
+ # Specify name of a theme installed via the pelican-themes tool
+ THEME = "chunk"
+ # 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"
+
+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 ?
+======================= =======================================================
+`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
+ 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
+ items to appear at the beginning of the main menu.
+`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
+ 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
+ the header.
+`SOCIAL` A list of tuples (Title, URL) to appear in the
+ "social" section.
+`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.
+======================= =======================================================
+
+In addition, you can use the "wide" version of the ``notmyidea`` theme by
+adding the following to your configuration::
+
+ CSS_FILE = "wide.css"
+
+Example settings
+================
+
+.. literalinclude:: ../samples/pelican.conf.py
+ :language: python
+
+
+.. _Jinja custom filters documentation: http://jinja.pocoo.org/docs/api/#custom-filters
diff --git a/docs/themes.rst b/docs/themes.rst
new file mode 100644
index 00000000..ddf509f8
--- /dev/null
+++ b/docs/themes.rst
@@ -0,0 +1,347 @@
+.. _theming-pelican:
+
+How to create themes for Pelican
+################################
+
+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
+`_.
+
+Structure
+=========
+
+To make your own theme, you must follow the following structure::
+
+ ├── static
+ │ ├── css
+ │ └── images
+ └── templates
+ ├── archives.html // to display archives
+ ├── period_archives.html // to display time-period archives
+ ├── article.html // processed for each article
+ ├── author.html // processed for each author
+ ├── authors.html // must list all the authors
+ ├── categories.html // must list all the categories
+ ├── category.html // processed for each category
+ ├── index.html // the index. List all the articles
+ ├── page.html // processed for each page
+ ├── tag.html // processed for each tag
+ └── tags.html // must list all the tags. Can be a tag cloud.
+
+* `static` contains all the static assets, which will be copied to the output
+ `theme` folder. I've put the CSS and image folders here, but they are
+ just examples. Put what you need here.
+
+* `templates` contains all the templates that will be used to generate the content.
+ I've just put the mandatory templates here; you can define your own if it helps
+ you keep things organized while creating your theme.
+
+Templates and variables
+=======================
+
+The idea is to use a simple syntax that you can embed into your HTML pages.
+This document describes which templates should exist in a theme, and which
+variables will be passed to each template at generation time.
+
+All templates will receive the variables defined in your settings file, if they
+are in all-caps. You can access them directly.
+
+Common variables
+----------------
+
+All of these settings will be available to all templates.
+
+============= ===================================================
+Variable Description
+============= ===================================================
+output_file The name of the file currently being generated. For
+ instance, when Pelican is rendering the homepage,
+ output_file will be "index.html".
+articles The list of articles, ordered descending by date
+ All the elements are `Article` objects, so you can
+ access their attributes (e.g. title, summary, author
+ etc.). Sometimes this is shadowed (for instance in
+ the tags page). You will then find info about it
+ in the `all_articles` variable.
+dates The same list of articles, but ordered by date,
+ ascending
+tags A list of (tag, articles) tuples, containing all
+ the tags.
+categories A list of (category, articles) tuples, containing
+ all the categories.
+ and the list of respective articles (values)
+pages The list of pages
+============= ===================================================
+
+Sorting
+-------
+
+URL wrappers (currently categories, tags, and authors), have
+comparison methods that allow them to be easily sorted by name::
+
+ {% for tag, articles in tags|sort %}
+
+If you want to sort based on different criteria, `Jinja's sort
+command`__ has a number of options.
+
+__ http://jinja.pocoo.org/docs/templates/#sort
+
+
+Date Formatting
+---------------
+
+Pelican formats the date with according to your settings and locale
+(``DATE_FORMATS``/``DEFAULT_DATE_FORMAT``) and provides a
+``locale_date`` attribute. On the other hand, ``date`` attribute will
+be a `datetime`_ object. If you need custom formatting for a date
+different than your settings, use the Jinja filter ``strftime``
+that comes with Pelican. Usage is same as Python `strftime`_ format,
+but the filter will do the right thing and format your date according
+to the locale given in your settings::
+
+ {{ article.date|strftime('%d %B %Y') }}
+
+.. _datetime: http://docs.python.org/2/library/datetime.html#datetime-objects
+.. _strftime: http://docs.python.org/2/library/datetime.html#strftime-strptime-behavior
+
+index.html
+----------
+
+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
+dates_paginator A paginator object for the article list, ordered by
+ date, ascending.
+dates_page The current page of articles, ordered by date,
+ ascending.
+page_name 'index' -- useful for pagination links
+=================== ===================================================
+
+author.html
+-------------
+
+This template will be processed for each of the existing authors, with
+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
+dates_paginator A paginator object for the article list, ordered by
+ date, ascending.
+dates_page The current page of articles, ordered by date,
+ ascending.
+page_name AUTHOR_URL where everything after `{slug}` is
+ removed -- useful for pagination links
+=================== ===================================================
+
+category.html
+-------------
+
+This template will be processed for each of the existing categories, with
+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
+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
+page_name CATEGORY_URL where everything after `{slug}` is
+ removed -- useful for pagination links
+=================== ===================================================
+
+article.html
+-------------
+
+This template will be processed for each article, with .html files saved
+as output/`article_name`.html. Here are the specific variables it gets.
+
+============= ===================================================
+Variable Description
+============= ===================================================
+article The article object to be displayed
+category The name of the category for the current article
+============= ===================================================
+
+page.html
+---------
+
+This template will be processed for each page, with corresponding .html files
+saved as output/`page_name`.html.
+
+============= ===================================================
+Variable Description
+============= ===================================================
+page The page object to be displayed. You can access its
+ title, slug, and content.
+============= ===================================================
+
+tag.html
+--------
+
+This template will be processed for each tag, with corresponding .html files
+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
+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
+page_name TAG_URL where everything after `{slug}` is removed
+ -- useful for pagination links
+=================== ===================================================
+
+Feeds
+=====
+
+The feed variables changed in 3.0. Each variable now explicitly lists ATOM or
+RSS in the name. ATOM is still the default. Old themes will need to be updated.
+Here is a complete list of the feed variables::
+
+ FEED_ATOM
+ FEED_RSS
+ FEED_ALL_ATOM
+ FEED_ALL_RSS
+ CATEGORY_FEED_ATOM
+ CATEGORY_FEED_RSS
+ TAG_FEED_ATOM
+ TAG_FEED_RSS
+ TRANSLATION_FEED_ATOM
+ TRANSLATION_FEED_RSS
+
+
+Inheritance
+===========
+
+Since version 3.0, Pelican supports inheritance from the ``simple`` theme, so
+you can re-use the ``simple`` theme templates in your own themes.
+
+If one of the mandatory files in the ``templates/`` directory of your theme is
+missing, it will be replaced by the matching template from the ``simple`` theme.
+So if the HTML structure of a template in the ``simple`` theme is right for you,
+you don't have to write a new template from scratch.
+
+You can also extend templates from the ``simple`` themes in your own themes by using the ``{% extends %}`` directive as in the following example:
+
+.. code-block:: html+jinja
+
+ {% extends "!simple/index.html" %}
+
+ {% extends "index.html" %}
+
+
+Example
+-------
+
+With this system, it is possible to create a theme with just two files.
+
+base.html
+"""""""""
+
+The first file is the ``templates/base.html`` template:
+
+.. code-block:: html+jinja
+
+ {% extends "!simple/base.html" %}
+
+ {% block head %}
+ {{ super() }}
+
+ {% endblock %}
+
+
+1. On the first line, we extend the ``base.html`` template from the ``simple`` theme, so we don't have to rewrite the entire file.
+2. On the third line, we open the ``head`` block which has already been defined in the ``simple`` theme.
+3. On the fourth line, the function ``super()`` keeps the content previously inserted in the ``head`` block.
+4. On the fifth line, we append a stylesheet to the page.
+5. On the last line, we close the ``head`` block.
+
+This file will be extended by all the other templates, so the stylesheet will be linked from all pages.
+
+style.css
+"""""""""
+
+The second file is the ``static/css/style.css`` CSS stylesheet:
+
+.. code-block:: css
+
+ body {
+ font-family : monospace ;
+ font-size : 100% ;
+ background-color : white ;
+ color : #111 ;
+ width : 80% ;
+ min-width : 400px ;
+ min-height : 200px ;
+ padding : 1em ;
+ margin : 5% 10% ;
+ border : thin solid gray ;
+ border-radius : 5px ;
+ display : block ;
+ }
+
+ a:link { color : blue ; text-decoration : none ; }
+ a:hover { color : blue ; text-decoration : underline ; }
+ a:visited { color : blue ; }
+
+ h1 a { color : inherit !important }
+ h2 a { color : inherit !important }
+ h3 a { color : inherit !important }
+ h4 a { color : inherit !important }
+ h5 a { color : inherit !important }
+ h6 a { color : inherit !important }
+
+ pre {
+ margin : 2em 1em 2em 4em ;
+ }
+
+ #menu li {
+ display : inline ;
+ }
+
+ #post-list {
+ margin-bottom : 1em ;
+ margin-top : 1em ;
+ }
+
+Download
+""""""""
+
+You can download this example theme :download:`here <_static/theme-basic.zip>`.
diff --git a/docs/tips.rst b/docs/tips.rst
new file mode 100644
index 00000000..430d6488
--- /dev/null
+++ b/docs/tips.rst
@@ -0,0 +1,85 @@
+Tips
+####
+
+Here are some tips about Pelican that you might find useful.
+
+Publishing to GitHub
+====================
+
+`GitHub Pages `_ offer an easy
+and convenient way to publish Pelican sites. There are `two types of GitHub
+Pages `_:
+*Project Pages* and *User Pages*. Pelican sites can be published as both
+Project Pages and User Pages.
+
+Project Pages
+-------------
+
+To publish a Pelican site as Project Pages you need to *push* the content of
+the ``output`` dir generated by Pelican to a repository's ``gh-pages`` branch
+on GitHub.
+
+The excellent `ghp-import `_, which can
+be installed with ``easy_install`` or ``pip``, makes this process really easy.
+
+For example, if the sources of your Pelican site are contained in a GitHub
+repository, and if you want to publish your Pelican site as Project Pages of
+this repository, you can then use the following::
+
+ $ pelican content -o output -s pelicanconf.py
+ $ ghp-import output
+ $ git push origin gh-pages
+
+The ``ghp-import output`` command updates the local ``gh-pages`` branch with
+the content of the ``output`` directory (creating the branch if it doesn't
+already exist). The ``git push origin gh-pages`` command updates the remote
+``gh-pages`` branch, effectively publishing the Pelican site.
+
+.. note::
+
+ The ``github`` target of the Makefile created by the ``pelican-quickstart``
+ command publishes the Pelican site as Project Pages as described above.
+
+User Pages
+----------
+
+To publish a Pelican site as User Pages you need to *push* the content of the
+``output`` dir generated by Pelican to the ``master`` branch of your
+``.github.com`` repository on GitHub.
+
+Again, you can take advantage of ``ghp-import``::
+
+ $ pelican content -o output -s pelicanconf.py
+ $ ghp-import output
+ $ git push git@github.com:elemoine/elemoine.github.com.git gh-pages:master
+
+The ``git push`` command pushes the local ``gh-pages`` branch (freshly updated
+by the ``ghp-import`` command) to the ``elemoine.github.com`` repository's
+``master`` branch on GitHub.
+
+.. note::
+
+ To publish your Pelican site as User Pages feel free to adjust the the
+ ``github`` target of the Makefile.
+
+Extra Tips
+----------
+
+Tip #1:
+
+To automatically update your Pelican site on each commit you can create
+a post-commit hook. For example, you can add the following to
+``.git/hooks/post-commit``::
+
+ pelican pelican content -o output -s pelicanconf.py && ghp-import output && git push origin gh-pages
+
+Tip #2:
+
+To use a `custom domain
+`_ with
+GitHub Pages you need to have a ``CNAME`` file at the root of your pages. For
+that you will add ``CNAME`` file to your ``content``, dir and use the
+``FILES_TO_COPY`` setting variable to tell Pelican to copy that file
+to the ``output`` dir. For example::
+
+ FILES_TO_COPY = (('extra/CNAME', 'CNAME'),)
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 6f5d9f44..00000000
--- a/package-lock.json
+++ /dev/null
@@ -1,1556 +0,0 @@
-{
- "name": "pelican-theme",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "devDependencies": {
- "@tailwindcss/typography": "^0.5.15",
- "tailwindcss": "^3.4.17"
- }
- },
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@tailwindcss/typography": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.15.tgz",
- "integrity": "sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "lodash.castarray": "^4.4.0",
- "lodash.isplainobject": "^4.0.6",
- "lodash.merge": "^4.6.2",
- "postcss-selector-parser": "6.0.10"
- },
- "peerDependencies": {
- "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20"
- }
- },
- "node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/arg": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/camelcase-css": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/chokidar/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "cssesc": "bin/cssesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/didyoumean": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-glob": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
- "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.4"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fastq": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
- "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/foreground-child": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
- "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.16.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz",
- "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "node_modules/jiti": {
- "version": "1.21.7",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
- "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jiti": "bin/jiti.js"
- }
- },
- "node_modules/lilconfig": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
- "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
- }
- },
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lodash.castarray": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
- "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lodash.isplainobject": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
- "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/mz": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0",
- "object-assign": "^4.0.1",
- "thenify-all": "^1.0.0"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
- "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/postcss": {
- "version": "8.4.49",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
- "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postcss-import": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
- "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.0.0",
- "read-cache": "^1.0.0",
- "resolve": "^1.1.7"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
- "node_modules/postcss-js": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
- "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "camelcase-css": "^2.0.1"
- },
- "engines": {
- "node": "^12 || ^14 || >= 16"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.4.21"
- }
- },
- "node_modules/postcss-load-config": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
- "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "lilconfig": "^3.0.0",
- "yaml": "^2.3.4"
- },
- "engines": {
- "node": ">= 14"
- },
- "peerDependencies": {
- "postcss": ">=8.0.9",
- "ts-node": ">=9.0.0"
- },
- "peerDependenciesMeta": {
- "postcss": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
- }
- },
- "node_modules/postcss-nested": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
- "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^6.1.1"
- },
- "engines": {
- "node": ">=12.0"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
- }
- },
- "node_modules/postcss-nested/node_modules/postcss-selector-parser": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss-selector-parser": {
- "version": "6.0.10",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
- "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/read-cache": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
- "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pify": "^2.3.0"
- }
- },
- "node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.9",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz",
- "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.16.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/string-width-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/sucrase": {
- "version": "3.35.0",
- "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
- "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.2",
- "commander": "^4.0.0",
- "glob": "^10.3.10",
- "lines-and-columns": "^1.1.6",
- "mz": "^2.7.0",
- "pirates": "^4.0.1",
- "ts-interface-checker": "^0.1.9"
- },
- "bin": {
- "sucrase": "bin/sucrase",
- "sucrase-node": "bin/sucrase-node"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/tailwindcss": {
- "version": "3.4.17",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
- "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "arg": "^5.0.2",
- "chokidar": "^3.6.0",
- "didyoumean": "^1.2.2",
- "dlv": "^1.1.3",
- "fast-glob": "^3.3.2",
- "glob-parent": "^6.0.2",
- "is-glob": "^4.0.3",
- "jiti": "^1.21.6",
- "lilconfig": "^3.1.3",
- "micromatch": "^4.0.8",
- "normalize-path": "^3.0.0",
- "object-hash": "^3.0.0",
- "picocolors": "^1.1.1",
- "postcss": "^8.4.47",
- "postcss-import": "^15.1.0",
- "postcss-js": "^4.0.1",
- "postcss-load-config": "^4.0.2",
- "postcss-nested": "^6.2.0",
- "postcss-selector-parser": "^6.1.2",
- "resolve": "^1.22.8",
- "sucrase": "^3.35.0"
- },
- "bin": {
- "tailwind": "lib/cli.js",
- "tailwindcss": "lib/cli.js"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tailwindcss/node_modules/postcss-selector-parser": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/thenify": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
- "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0"
- }
- },
- "node_modules/thenify-all": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
- "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "thenify": ">= 3.1.0 < 4"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/ts-interface-checker": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/wrap-ansi-cjs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yaml": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
- "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14"
- }
- }
- }
-}
diff --git a/package.json b/package.json
deleted file mode 100644
index 969943e4..00000000
--- a/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "devDependencies": {
- "@tailwindcss/typography": "^0.5.15",
- "tailwindcss": "^3.4.17"
- }
-}
diff --git a/pelican/__init__.py b/pelican/__init__.py
new file mode 100644
index 00000000..53216421
--- /dev/null
+++ b/pelican/__init__.py
@@ -0,0 +1,400 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+import six
+
+import os
+import re
+import sys
+import time
+import logging
+import argparse
+import locale
+import collections
+
+from pelican import signals
+
+from pelican.generators import (ArticlesGenerator, PagesGenerator,
+ StaticGenerator, PdfGenerator,
+ SourceFileGenerator, TemplatePagesGenerator)
+from pelican.log import init
+from pelican.settings import read_settings
+from pelican.utils import clean_output_dir, folder_watcher, file_watcher
+from pelican.writers import Writer
+
+__major__ = 3
+__minor__ = 2
+__micro__ = 0
+__version__ = "{0}.{1}.{2}".format(__major__, __minor__, __micro__)
+
+DEFAULT_CONFIG_NAME = 'pelicanconf.py'
+
+
+logger = logging.getLogger(__name__)
+
+
+class Pelican(object):
+ def __init__(self, settings):
+ """
+ Pelican initialisation, performs some checks on the environment before
+ doing anything else.
+ """
+
+ # define the default settings
+ self.settings = settings
+ self._handle_deprecation()
+
+ self.path = settings['PATH']
+ self.theme = settings['THEME']
+ self.output_path = settings['OUTPUT_PATH']
+ self.markup = settings['MARKUP']
+ self.ignore_files = settings['IGNORE_FILES']
+ self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY']
+ self.output_retention = settings['OUTPUT_RETENTION']
+
+ self.init_path()
+ self.init_plugins()
+ signals.initialized.send(self)
+
+ def init_path(self):
+ if not any(p in sys.path for p in ['', os.curdir]):
+ logger.debug("Adding current directory to system path")
+ sys.path.insert(0, '')
+
+ def init_plugins(self):
+ self.plugins = []
+ logger.debug('Temporarily adding PLUGIN_PATH to system path')
+ _sys_path = sys.path[:]
+ sys.path.insert(0, self.settings['PLUGIN_PATH'])
+ for plugin in self.settings['PLUGINS']:
+ # if it's a string, then import it
+ if isinstance(plugin, six.string_types):
+ logger.debug("Loading plugin `{0}`".format(plugin))
+ try:
+ plugin = __import__(plugin, globals(), locals(), str('module'))
+ except ImportError as e:
+ logger.error("Can't find plugin `{0}`: {1}".format(plugin, e))
+ continue
+
+ logger.debug("Registering plugin `{0}`".format(plugin.__name__))
+ plugin.register()
+ self.plugins.append(plugin)
+ logger.debug('Restoring system path')
+ sys.path = _sys_path
+
+ def _handle_deprecation(self):
+
+ if self.settings.get('CLEAN_URLS', False):
+ logger.warning('Found deprecated `CLEAN_URLS` in settings.'
+ ' Modifying the following settings for the'
+ ' same behaviour.')
+
+ self.settings['ARTICLE_URL'] = '{slug}/'
+ self.settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/'
+ self.settings['PAGE_URL'] = 'pages/{slug}/'
+ self.settings['PAGE_LANG_URL'] = 'pages/{slug}-{lang}/'
+
+ for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
+ 'PAGE_LANG_URL'):
+ logger.warning("%s = '%s'" % (setting, self.settings[setting]))
+
+ if self.settings.get('ARTICLE_PERMALINK_STRUCTURE', False):
+ logger.warning('Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in'
+ ' settings. Modifying the following settings for'
+ ' the same behaviour.')
+
+ structure = self.settings['ARTICLE_PERMALINK_STRUCTURE']
+
+ # Convert %(variable) into {variable}.
+ structure = re.sub('%\((\w+)\)s', '{\g<1>}', structure)
+
+ # Convert %x into {date:%x} for strftime
+ structure = re.sub('(%[A-z])', '{date:\g<1>}', structure)
+
+ # Strip a / prefix
+ 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'):
+ self.settings[setting] = os.path.join(structure,
+ self.settings[setting])
+ logger.warning("%s = '%s'" % (setting, self.settings[setting]))
+
+ if self.settings.get('FEED', False):
+ logger.warning('Found deprecated `FEED` in settings. Modify FEED'
+ ' to FEED_ATOM in your settings and theme for the same behavior.'
+ ' Temporarily setting FEED_ATOM for backwards compatibility.')
+ self.settings['FEED_ATOM'] = self.settings['FEED']
+
+ if self.settings.get('TAG_FEED', False):
+ logger.warning('Found deprecated `TAG_FEED` in settings. Modify '
+ ' TAG_FEED to TAG_FEED_ATOM in your settings and theme for the '
+ 'same behavior. Temporarily setting TAG_FEED_ATOM for backwards '
+ 'compatibility.')
+ self.settings['TAG_FEED_ATOM'] = self.settings['TAG_FEED']
+
+ if self.settings.get('CATEGORY_FEED', False):
+ logger.warning('Found deprecated `CATEGORY_FEED` in settings. '
+ 'Modify CATEGORY_FEED to CATEGORY_FEED_ATOM in your settings and '
+ 'theme for the same behavior. Temporarily setting '
+ 'CATEGORY_FEED_ATOM for backwards compatibility.')
+ self.settings['CATEGORY_FEED_ATOM'] =\
+ self.settings['CATEGORY_FEED']
+
+ if self.settings.get('TRANSLATION_FEED', False):
+ logger.warning('Found deprecated `TRANSLATION_FEED` in settings. '
+ 'Modify TRANSLATION_FEED to TRANSLATION_FEED_ATOM in your '
+ 'settings and theme for the same behavior. Temporarily setting '
+ 'TRANSLATION_FEED_ATOM for backwards compatibility.')
+ self.settings['TRANSLATION_FEED_ATOM'] =\
+ self.settings['TRANSLATION_FEED']
+
+ def run(self):
+ """Run the generators and return"""
+ start_time = time.time()
+
+ context = self.settings.copy()
+ context['filenames'] = {} # share the dict between all the generators
+ context['localsiteurl'] = self.settings['SITEURL'] # share
+ generators = [
+ cls(
+ context=context,
+ settings=self.settings,
+ path=self.path,
+ theme=self.theme,
+ output_path=self.output_path,
+ markup=self.markup,
+ ) for cls in self.get_generator_classes()
+ ]
+
+ for p in generators:
+ if hasattr(p, 'generate_context'):
+ p.generate_context()
+
+ # erase the directory if it is not the source and if that's
+ # explicitely asked
+ if (self.delete_outputdir and not
+ os.path.realpath(self.path).startswith(self.output_path)):
+ clean_output_dir(self.output_path, self.output_retention)
+
+ writer = self.get_writer()
+
+ for p in generators:
+ if hasattr(p, 'generate_output'):
+ p.generate_output(writer)
+
+ signals.finalized.send(self)
+
+ articles_generator = next(g for g in generators if isinstance(g, ArticlesGenerator))
+ pages_generator = next(g for g in generators if isinstance(g, PagesGenerator))
+
+ print('Done: Processed {} articles and {} pages in {:.2f} seconds.'.format(
+ len(articles_generator.articles) + len(articles_generator.translations),
+ len(pages_generator.pages) + len(pages_generator.translations),
+ time.time() - start_time))
+
+ def get_generator_classes(self):
+ generators = [StaticGenerator, ArticlesGenerator, PagesGenerator]
+
+ if self.settings['TEMPLATE_PAGES']:
+ generators.append(TemplatePagesGenerator)
+ if self.settings['PDF_GENERATOR']:
+ generators.append(PdfGenerator)
+ if self.settings['OUTPUT_SOURCES']:
+ generators.append(SourceFileGenerator)
+
+ for pair in signals.get_generators.send(self):
+ (funct, value) = pair
+
+ if not isinstance(value, collections.Iterable):
+ value = (value, )
+
+ for v in value:
+ if isinstance(v, type):
+ logger.debug('Found generator: {0}'.format(v))
+ generators.append(v)
+
+ return generators
+
+ def get_writer(self):
+ return Writer(self.output_path, settings=self.settings)
+
+
+def parse_arguments():
+ parser = argparse.ArgumentParser(description="""A tool to generate a
+ static blog, with restructured text input files.""",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+ parser.add_argument(dest='path', nargs='?',
+ help='Path where to find the content files.',
+ default=None)
+
+ parser.add_argument('-t', '--theme-path', dest='theme',
+ help='Path where to find the theme templates. If not specified, it'
+ 'will use the default one included with pelican.')
+
+ parser.add_argument('-o', '--output', dest='output',
+ help='Where to output the generated files. If not specified, a '
+ 'directory will be created, named "output" in the current path.')
+
+ parser.add_argument('-m', '--markup', dest='markup',
+ help='The list of markup language to use (rst or md). Please indicate '
+ 'them separated by commas.')
+
+ parser.add_argument('-s', '--settings', dest='settings',
+ help='The settings of the application, this is automatically set to '
+ '{0} if a file exists with this name.'.format(DEFAULT_CONFIG_NAME))
+
+ parser.add_argument('-d', '--delete-output-directory',
+ dest='delete_outputdir',
+ action='store_true',
+ default=None,
+ help='Delete the output directory.')
+
+ parser.add_argument('-v', '--verbose', action='store_const',
+ const=logging.INFO, dest='verbosity',
+ help='Show all messages.')
+
+ parser.add_argument('-q', '--quiet', action='store_const',
+ const=logging.CRITICAL, dest='verbosity',
+ help='Show only critical errors.')
+
+ parser.add_argument('-D', '--debug', action='store_const',
+ const=logging.DEBUG, dest='verbosity',
+ help='Show all message, including debug messages.')
+
+ parser.add_argument('--version', action='version', version=__version__,
+ help='Print the pelican version and exit.')
+
+ parser.add_argument('-r', '--autoreload', dest='autoreload',
+ action='store_true',
+ help="Relaunch pelican each time a modification occurs"
+ " on the content files.")
+ return parser.parse_args()
+
+
+def get_config(args):
+ config = {}
+ if args.path:
+ config['PATH'] = os.path.abspath(os.path.expanduser(args.path))
+ if args.output:
+ config['OUTPUT_PATH'] = \
+ os.path.abspath(os.path.expanduser(args.output))
+ if args.markup:
+ config['MARKUP'] = [a.strip().lower() for a in args.markup.split(',')]
+ if args.theme:
+ abstheme = os.path.abspath(os.path.expanduser(args.theme))
+ 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
+
+ # argparse returns bytes in Py2. There is no definite answer as to which
+ # encoding argparse (or sys.argv) uses.
+ # "Best" option seems to be locale.getpreferredencoding()
+ # ref: http://mail.python.org/pipermail/python-list/2006-October/405766.html
+ if not six.PY3:
+ enc = locale.getpreferredencoding()
+ for key in config:
+ if key in ('PATH', 'OUTPUT_PATH', 'THEME'):
+ config[key] = config[key].decode(enc)
+ if key == "MARKUP":
+ config[key] = [a.decode(enc) for a in config[key]]
+ return config
+
+
+def get_instance(args):
+
+ config_file = args.settings
+ if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME):
+ config_file = DEFAULT_CONFIG_NAME
+
+ settings = read_settings(config_file, override=get_config(args))
+
+ cls = settings['PELICAN_CLASS']
+ if isinstance(cls, six.string_types):
+ module, cls_name = cls.rsplit('.', 1)
+ module = __import__(module)
+ cls = getattr(module, cls_name)
+
+ return cls(settings)
+
+
+def main():
+ args = parse_arguments()
+ init(args.verbosity)
+ pelican = get_instance(args)
+
+ watchers = {'content': folder_watcher(pelican.path,
+ pelican.markup,
+ pelican.ignore_files),
+ 'theme': folder_watcher(pelican.theme,
+ [''],
+ pelican.ignore_files),
+ 'settings': file_watcher(args.settings)}
+
+ try:
+ if args.autoreload:
+ print(' --- AutoReload Mode: Monitoring `content`, `theme` and `settings`'
+ ' for changes. ---')
+
+ while True:
+ try:
+ # Check source dir for changed files ending with the given
+ # extension in the settings. In the theme dir is no such
+ # restriction; all files are recursively checked if they
+ # have changed, no matter what extension the filenames
+ # have.
+ modified = {k: next(v) for k, v in watchers.items()}
+
+ if modified['settings']:
+ pelican = get_instance(args)
+
+ if any(modified.values()):
+ print('\n-> Modified: {}. re-generating...'.format(
+ ', '.join(k for k, v in modified.items() if v)))
+
+ if modified['content'] is None:
+ logger.warning('No valid files found in content.')
+
+ if modified['theme'] is None:
+ logger.warning('Empty theme folder. Using `basic` theme.')
+
+ pelican.run()
+
+ except KeyboardInterrupt:
+ logger.warning("Keyboard interrupt, quitting.")
+ break
+
+ except Exception as e:
+ if (args.verbosity == logging.DEBUG):
+ logger.critical(e.args)
+ raise
+ logger.warning(
+ 'Caught exception "{0}". Reloading.'.format(e))
+
+ finally:
+ time.sleep(.5) # sleep to avoid cpu load
+
+ else:
+ if next(watchers['content']) is None:
+ logger.warning('No valid files found in content.')
+
+ if next(watchers['theme']) is None:
+ logger.warning('Empty theme folder. Using `basic` theme.')
+
+ pelican.run()
+
+ except Exception as e:
+ # localized systems have errors in native language if locale is set
+ # so convert the message to unicode with the correct encoding
+ msg = str(e)
+ if not six.PY3:
+ msg = msg.decode(locale.getpreferredencoding(False))
+
+ logger.critical(msg)
+
+ if (args.verbosity == logging.DEBUG):
+ raise
+ else:
+ sys.exit(getattr(e, 'exitcode', 1))
diff --git a/pelican/contents.py b/pelican/contents.py
new file mode 100644
index 00000000..b5da4342
--- /dev/null
+++ b/pelican/contents.py
@@ -0,0 +1,327 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+import six
+
+import copy
+import locale
+import logging
+import functools
+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)
+
+# Import these so that they're avalaible when you import from pelican.contents.
+from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA
+
+logger = logging.getLogger(__name__)
+
+
+class Content(object):
+ """Represents a content.
+
+ :param content: the string to parse, containing the original content.
+ :param metadata: the metadata associated to this page (optional).
+ :param settings: the settings dictionary (optional).
+ :param source_path: The location of the source of this content (if any).
+ :param context: The shared context between generators.
+
+ """
+ @deprecated_attribute(old='filename', new='source_path', since=(3, 2, 0))
+ def filename():
+ return None
+
+ def __init__(self, content, metadata=None, settings=None,
+ source_path=None, context=None):
+ if metadata is None:
+ metadata = {}
+ if settings is None:
+ settings = copy.deepcopy(DEFAULT_CONFIG)
+
+ self.settings = settings
+ self._content = content
+ if context is None:
+ context = {}
+ self._context = context
+ self.translations = []
+
+ local_metadata = dict(settings['DEFAULT_METADATA'])
+ local_metadata.update(metadata)
+
+ # set metadata as attributes
+ for key, value in local_metadata.items():
+ if key in ('save_as', 'url'):
+ key = 'override_' + key
+ setattr(self, key.lower(), value)
+
+ # also keep track of the metadata attributes available
+ self.metadata = local_metadata
+
+ #default template if it's not defined in page
+ self.template = self._get_template()
+
+ # First, read the authors from "authors", if not, fallback to "author"
+ # and if not use the settings defined one, if any.
+ if not hasattr(self, 'author') and 'AUTHOR' in settings:
+ self.author = Author(settings['AUTHOR'], settings)
+
+ if not hasattr(self, 'authors') and hasattr(self, 'author'):
+ setattr(self, 'authors', [self.author])
+
+ if hasattr(self, 'authors') and not hasattr(self, 'author'):
+ setattr(self, 'author', self.authors[0])
+
+ # XXX Split all the following code into pieces, there is too much here.
+
+ # manage languages
+ self.in_default_lang = True
+ if 'DEFAULT_LANG' in settings:
+ default_lang = settings['DEFAULT_LANG'].lower()
+ if not hasattr(self, 'lang'):
+ self.lang = default_lang
+
+ 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)
+
+ self.source_path = source_path
+
+ # manage the date format
+ if not hasattr(self, 'date_format'):
+ if hasattr(self, 'lang') and self.lang in settings['DATE_FORMATS']:
+ self.date_format = settings['DATE_FORMATS'][self.lang]
+ else:
+ self.date_format = settings['DEFAULT_DATE_FORMAT']
+
+ if isinstance(self.date_format, tuple):
+ locale_string = self.date_format[0]
+ if sys.version_info < (3, ) and isinstance(locale_string,
+ six.text_type):
+ locale_string = locale_string.encode('ascii')
+ locale.setlocale(locale.LC_ALL, locale_string)
+ self.date_format = self.date_format[1]
+
+ if hasattr(self, 'date'):
+ self.locale_date = strftime(self.date, self.date_format)
+
+ # manage status
+ if not hasattr(self, 'status'):
+ self.status = settings['DEFAULT_STATUS']
+ if not settings['WITH_FUTURE_DATES']:
+ if hasattr(self, 'date') and self.date > datetime.now():
+ self.status = 'draft'
+
+ # store the summary metadata if it is set
+ if 'summary' in metadata:
+ self._summary = metadata['summary']
+
+ signals.content_object_init.send(self)
+
+ def __str__(self):
+ if self.source_path is None:
+ return repr(self)
+ elif six.PY3:
+ return self.source_path or repr(self)
+ else:
+ return str(self.source_path.encode('utf-8', 'replace'))
+
+ def check_properties(self):
+ """Test mandatory properties are set."""
+ for prop in self.mandatory_properties:
+ if not hasattr(self, prop):
+ raise NameError(prop)
+
+ @property
+ def url_format(self):
+ """Returns the URL, formatted with the proper values"""
+ metadata = copy.copy(self.metadata)
+ path = self.metadata.get('path', self.get_relative_source_path())
+ metadata.update({
+ 'path': path_to_url(path),
+ 'slug': getattr(self, 'slug', ''),
+ 'lang': getattr(self, 'lang', 'en'),
+ 'date': getattr(self, 'date', datetime.now()),
+ 'author': getattr(self, 'author', ''),
+ 'category': getattr(self, 'category',
+ self.settings['DEFAULT_CATEGORY']),
+ })
+ return metadata
+
+ def _expand_settings(self, key):
+ fq_key = ('%s_%s' % (self.__class__.__name__, key)).upper()
+ return self.settings[fq_key].format(**self.url_format)
+
+ def get_url_setting(self, key):
+ if hasattr(self, 'override_' + key):
+ return getattr(self, 'override_' + key)
+ key = key if self.in_default_lang else 'lang_%s' % key
+ return self._expand_settings(key)
+
+ def _update_content(self, content, siteurl):
+ """Update the content attribute.
+
+ Change all the relative paths of the content to relative paths
+ suitable for the ouput content.
+
+ :param content: content resource that will be passed to the templates.
+ :param siteurl: siteurl which is locally generated by the writer in
+ case of RELATIVE_URLS.
+ """
+ if not content:
+ return content
+
+ hrefs = re.compile(r"""
+ (?P<\s*[^\>]* # match tag with src and href attr
+ (?:href|src)\s*=)
+
+ (?P["\']) # require value to be quoted
+ (?P\|(?P.*?)\|(?P.*?)) # the url value
+ \2""", re.X)
+
+ def replacer(m):
+ what = m.group('what')
+ value = m.group('value')
+ origin = m.group('path')
+
+ # we support only filename for now. the plan is to support
+ # categories, tags, etc. in the future, but let's keep things
+ # simple for now.
+
+ # XXX Put this in a different location.
+ if what == 'filename':
+ if value.startswith('/'):
+ value = value[1:]
+ else:
+ # relative to the source path of this content
+ value = self.get_relative_source_path(
+ os.path.join(self.relative_dir, value)
+ )
+
+ if value in self._context['filenames']:
+ origin = '/'.join((siteurl,
+ self._context['filenames'][value].url))
+ origin = origin.replace('\\', '/') # Fow windows paths.
+ else:
+ logger.warning("Unable to find {fn}, skipping url"
+ " replacement".format(fn=value))
+
+ return ''.join((m.group('markup'), m.group('quote'), origin,
+ m.group('quote')))
+
+ return hrefs.sub(replacer, content)
+
+ @memoized
+ def get_content(self, siteurl):
+
+ if hasattr(self, '_get_content'):
+ content = self._get_content()
+ else:
+ content = self._content
+ return self._update_content(content, siteurl)
+
+ @property
+ def content(self):
+ return self.get_content(self._context.get('localsiteurl', ''))
+
+ def _get_summary(self):
+ """Returns the summary of an article.
+
+ This is based on the summary metadata if set, otherwise truncate the
+ content.
+ """
+ if hasattr(self, '_summary'):
+ return self._summary
+
+ if self.settings['SUMMARY_MAX_LENGTH'] is None:
+ return self.content
+
+ return truncate_html_words(self.content,
+ self.settings['SUMMARY_MAX_LENGTH'])
+
+ def _set_summary(self, summary):
+ """Dummy function"""
+ pass
+
+ summary = property(_get_summary, _set_summary, "Summary of the article."
+ "Based on the content. Can't be set")
+ url = property(functools.partial(get_url_setting, key='url'))
+ save_as = property(functools.partial(get_url_setting, key='save_as'))
+
+ def _get_template(self):
+ if hasattr(self, 'template') and self.template is not None:
+ return self.template
+ else:
+ return self.default_template
+
+ def get_relative_source_path(self, source_path=None):
+ """Return the relative path (from the content path) to the given
+ source_path.
+
+ If no source path is specified, use the source path of this
+ content object.
+ """
+ if not source_path:
+ source_path = self.source_path
+ if source_path is None:
+ return None
+
+ return os.path.relpath(
+ os.path.abspath(os.path.join(self.settings['PATH'], source_path)),
+ os.path.abspath(self.settings['PATH'])
+ )
+
+ @property
+ def relative_dir(self):
+ return os.path.dirname(os.path.relpath(
+ os.path.abspath(self.source_path),
+ os.path.abspath(self.settings['PATH']))
+ )
+
+
+class Page(Content):
+ mandatory_properties = ('title',)
+ default_template = 'page'
+
+
+class Article(Page):
+ mandatory_properties = ('title', 'date', 'category')
+ default_template = 'article'
+
+
+class Quote(Page):
+ base_properties = ('author', 'date')
+
+
+@python_2_unicode_compatible
+class Static(Page):
+ @deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0))
+ def filepath():
+ return None
+
+ @deprecated_attribute(old='src', new='source_path', since=(3, 2, 0))
+ def src():
+ return None
+
+ @deprecated_attribute(old='dst', new='save_as', since=(3, 2, 0))
+ def dst():
+ return None
+
+
+def is_valid_content(content, f):
+ try:
+ content.check_properties()
+ return True
+ except NameError as e:
+ logger.error("Skipping %s: could not find information about "
+ "'%s'" % (f, e))
+ return False
diff --git a/pelican/generators.py b/pelican/generators.py
new file mode 100644
index 00000000..f8b7ede7
--- /dev/null
+++ b/pelican/generators.py
@@ -0,0 +1,638 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+
+import os
+import math
+import random
+import logging
+import shutil
+
+from codecs import open
+from collections import defaultdict
+from functools import partial
+from itertools import chain, groupby
+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.readers import read_file
+from pelican.utils import copy, process_translations, mkdir_p, DateFormatter
+from pelican import signals
+
+
+logger = logging.getLogger(__name__)
+
+
+class Generator(object):
+ """Baseclass generator"""
+
+ def __init__(self, context, settings, path, theme, output_path, markup,
+ **kwargs):
+ self.context = context
+ self.settings = settings
+ self.path = path
+ self.theme = theme
+ self.output_path = output_path
+ self.markup = markup
+
+ for arg, value in kwargs.items():
+ setattr(self, arg, value)
+
+ # templates cache
+ self._templates = {}
+ self._templates_path = []
+ self._templates_path.append(os.path.expanduser(
+ os.path.join(self.theme, 'templates')))
+ self._templates_path += self.settings['EXTRA_TEMPLATES_PATHS']
+
+ theme_path = os.path.dirname(os.path.abspath(__file__))
+
+ simple_loader = FileSystemLoader(os.path.join(theme_path,
+ "themes", "simple", "templates"))
+ self.env = Environment(
+ trim_blocks=True,
+ loader=ChoiceLoader([
+ FileSystemLoader(self._templates_path),
+ simple_loader, # implicit inheritance
+ PrefixLoader({'!simple': simple_loader}) # explicit one
+ ]),
+ extensions=self.settings['JINJA_EXTENSIONS'],
+ )
+
+ logger.debug('template list: {0}'.format(self.env.list_templates()))
+
+ # provide utils.strftime as a jinja filter
+ self.env.filters.update({'strftime': DateFormatter()})
+
+ # get custom Jinja filters from user settings
+ custom_filters = self.settings['JINJA_FILTERS']
+ self.env.filters.update(custom_filters)
+
+ signals.generator_init.send(self)
+
+ def get_template(self, name):
+ """Return the template by name.
+ Use self.theme to get the templates to use, and return a list of
+ templates ready to use with Jinja2.
+ """
+ if name not in self._templates:
+ try:
+ self._templates[name] = self.env.get_template(name + '.html')
+ except TemplateNotFound:
+ raise Exception(
+ ('[templates] unable to load %s.html from %s'
+ % (name, self._templates_path)))
+ return self._templates[name]
+
+ def _include_path(self, path, extensions=None):
+ """Inclusion logic for .get_files(), returns True/False
+
+ :param path: the path which might be including
+ :param extensions: the list of allowed extensions (if False, all
+ extensions are allowed)
+ """
+ if extensions is None:
+ extensions = tuple(self.markup)
+ basename = os.path.basename(path)
+ if extensions is False or basename.endswith(extensions):
+ return True
+ return False
+
+ def get_files(self, path, exclude=[], extensions=None):
+ """Return a list of files to use, based on rules
+
+ :param path: the path 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)
+
+ 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):
+ location = content.get_relative_source_path()
+ self.context['filenames'][location] = content
+
+ def _update_context(self, items):
+ """Update the context with the given items from the currrent
+ processor.
+ """
+ for item in items:
+ value = getattr(self, item)
+ if hasattr(value, 'items'):
+ value = list(value.items()) # py3k safeguard for iterators
+ self.context[item] = value
+
+
+class _FileLoader(BaseLoader):
+
+ def __init__(self, path, basedir):
+ self.path = path
+ self.fullpath = os.path.join(basedir, path)
+
+ def get_source(self, environment, template):
+ if template != self.path or not os.path.exists(self.fullpath):
+ raise TemplateNotFound(template)
+ mtime = os.path.getmtime(self.fullpath)
+ with open(self.fullpath, 'r', encoding='utf-8') as f:
+ source = f.read()
+ return (source, self.fullpath,
+ lambda: mtime == os.path.getmtime(self.fullpath))
+
+
+class TemplatePagesGenerator(Generator):
+
+ def generate_output(self, writer):
+ for source, dest in self.settings['TEMPLATE_PAGES'].items():
+ self.env.loader.loaders.insert(0, _FileLoader(source, self.path))
+ try:
+ template = self.env.get_template(source)
+ rurls = self.settings['RELATIVE_URLS']
+ writer.write_file(dest, template, self.context, rurls)
+ finally:
+ del self.env.loader.loaders[0]
+
+
+class ArticlesGenerator(Generator):
+ """Generate blog articles"""
+
+ def __init__(self, *args, **kwargs):
+ """initialize properties"""
+ self.articles = [] # only articles in default language
+ self.translations = []
+ self.dates = {}
+ self.tags = defaultdict(list)
+ self.categories = defaultdict(list)
+ self.related_posts = []
+ self.authors = defaultdict(list)
+ self.drafts = []
+ super(ArticlesGenerator, self).__init__(*args, **kwargs)
+ signals.article_generator_init.send(self)
+
+ def generate_feeds(self, writer):
+ """Generate the feeds from the current context, and output files."""
+
+ if self.settings.get('FEED_ATOM'):
+ writer.write_feed(self.articles, self.context,
+ self.settings['FEED_ATOM'])
+
+ if self.settings.get('FEED_RSS'):
+ writer.write_feed(self.articles, self.context,
+ self.settings['FEED_RSS'], feed_type='rss')
+
+ if (self.settings.get('FEED_ALL_ATOM')
+ or self.settings.get('FEED_ALL_RSS')):
+ all_articles = list(self.articles)
+ for article in self.articles:
+ all_articles.extend(article.translations)
+ all_articles.sort(key=attrgetter('date'), reverse=True)
+
+ if self.settings.get('FEED_ALL_ATOM'):
+ writer.write_feed(all_articles, self.context,
+ self.settings['FEED_ALL_ATOM'])
+
+ if self.settings.get('FEED_ALL_RSS'):
+ writer.write_feed(all_articles, self.context,
+ self.settings['FEED_ALL_RSS'],
+ feed_type='rss')
+
+ for cat, arts in self.categories:
+ arts.sort(key=attrgetter('date'), reverse=True)
+ if self.settings.get('CATEGORY_FEED_ATOM'):
+ writer.write_feed(arts, self.context,
+ self.settings['CATEGORY_FEED_ATOM']
+ % cat.slug)
+
+ if self.settings.get('CATEGORY_FEED_RSS'):
+ writer.write_feed(arts, self.context,
+ self.settings['CATEGORY_FEED_RSS']
+ % cat.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():
+ arts.sort(key=attrgetter('date'), reverse=True)
+ if self.settings.get('TAG_FEED_ATOM'):
+ writer.write_feed(arts, self.context,
+ self.settings['TAG_FEED_ATOM']
+ % tag.slug)
+
+ if self.settings.get('TAG_FEED_RSS'):
+ writer.write_feed(arts, self.context,
+ self.settings['TAG_FEED_RSS'] % tag.slug,
+ feed_type='rss')
+
+ if (self.settings.get('TRANSLATION_FEED_ATOM')
+ or self.settings.get('TRANSLATION_FEED_RSS')):
+ translations_feeds = defaultdict(list)
+ for article in chain(self.articles, self.translations):
+ translations_feeds[article.lang].append(article)
+
+ for lang, items in translations_feeds.items():
+ items.sort(key=attrgetter('date'), reverse=True)
+ if self.settings.get('TRANSLATION_FEED_ATOM'):
+ writer.write_feed(items, self.context,
+ self.settings['TRANSLATION_FEED_ATOM'] % lang)
+ if self.settings.get('TRANSLATION_FEED_RSS'):
+ writer.write_feed(items, self.context,
+ self.settings['TRANSLATION_FEED_RSS'] % lang,
+ feed_type='rss')
+
+ def generate_articles(self, write):
+ """Generate the articles."""
+ for article in chain(self.translations, self.articles):
+ write(article.save_as, self.get_template(article.template),
+ self.context, article=article, category=article.category)
+
+ def generate_period_archives(self, write):
+ """Generate per-year, per-month, and per-day archives."""
+ try:
+ template = self.get_template('period_archives')
+ except Exception:
+ template = self.get_template('archives')
+
+ def _generate_period_archives(dates, key, save_as_fmt):
+ """Generate period archives from `dates`, grouped by
+ `key` and written to `save_as`.
+ """
+ # `dates` is already sorted by date
+ for _period, group in groupby(dates, key=key):
+ archive = list(group)
+ # arbitrarily grab the first date so that the usual
+ # format string syntax can be used for specifying the
+ # period archive dates
+ date = archive[0].date
+ save_as = save_as_fmt.format(date=date)
+ write(save_as, template, self.context,
+ dates=archive, blog=True)
+
+ period_save_as = {
+ 'year': self.settings['YEAR_ARCHIVE_SAVE_AS'],
+ 'month': self.settings['MONTH_ARCHIVE_SAVE_AS'],
+ 'day': self.settings['DAY_ARCHIVE_SAVE_AS'],
+ }
+
+ period_date_key = {
+ 'year': attrgetter('date.year'),
+ 'month': attrgetter('date.year', 'date.month'),
+ 'day': attrgetter('date.year', 'date.month', 'date.day')
+ }
+
+ for period in 'year', 'month', 'day':
+ save_as = period_save_as[period]
+ if save_as:
+ key = period_date_key[period]
+ _generate_period_archives(self.dates, key, save_as)
+
+ def generate_direct_templates(self, write):
+ """Generate direct templates pages"""
+ PAGINATED_TEMPLATES = self.settings['PAGINATED_DIRECT_TEMPLATES']
+ for template in self.settings['DIRECT_TEMPLATES']:
+ paginated = {}
+ if template in PAGINATED_TEMPLATES:
+ paginated = {'articles': self.articles, 'dates': self.dates}
+ save_as = self.settings.get("%s_SAVE_AS" % template.upper(),
+ '%s.html' % template)
+ if not save_as:
+ continue
+
+ write(save_as, self.get_template(template),
+ self.context, blog=True, paginated=paginated,
+ page_name=os.path.splitext(save_as)[0])
+
+ def generate_tags(self, write):
+ """Generate Tags pages."""
+ tag_template = self.get_template('tag')
+ for tag, articles in self.tags.items():
+ articles.sort(key=attrgetter('date'), reverse=True)
+ dates = [article for article in self.dates if article in articles]
+ write(tag.save_as, tag_template, self.context, tag=tag,
+ articles=articles, dates=dates,
+ paginated={'articles': articles, 'dates': dates},
+ page_name=tag.page_name, all_articles=self.articles)
+
+ def generate_categories(self, write):
+ """Generate category pages."""
+ category_template = self.get_template('category')
+ for cat, articles in self.categories:
+ dates = [article for article in self.dates if article in articles]
+ write(cat.save_as, category_template, self.context,
+ category=cat, articles=articles, dates=dates,
+ paginated={'articles': articles, 'dates': dates},
+ page_name=cat.page_name, all_articles=self.articles)
+
+ def generate_authors(self, write):
+ """Generate Author pages."""
+ author_template = self.get_template('author')
+ for aut, articles in self.authors:
+ dates = [article for article in self.dates if article in articles]
+ write(aut.save_as, author_template, self.context,
+ author=aut, articles=articles, dates=dates,
+ paginated={'articles': articles, 'dates': dates},
+ page_name=aut.page_name, all_articles=self.articles)
+
+ 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)
+
+ def generate_pages(self, writer):
+ """Generate the pages on the disk"""
+ write = partial(writer.write_file,
+ relative_urls=self.settings['RELATIVE_URLS'])
+
+ # to minimize the number of relative path stuff modification
+ # in writer, articles pass first
+ self.generate_articles(write)
+ self.generate_period_archives(write)
+ self.generate_direct_templates(write)
+
+ # and subfolders after that
+ self.generate_tags(write)
+ self.generate_categories(write)
+ self.generate_authors(write)
+ self.generate_drafts(write)
+
+ def generate_context(self):
+ """Add the articles into the shared context"""
+
+ all_articles = []
+ for f in self.get_files(
+ self.settings['ARTICLE_DIR'],
+ exclude=self.settings['ARTICLE_EXCLUDES']):
+ try:
+ article = read_file(
+ base_path=self.path, path=f, content_class=Article,
+ settings=self.settings, 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
+
+ self.add_source_path(article)
+
+ if article.status == "published":
+ all_articles.append(article)
+ elif article.status == "draft":
+ self.drafts.append(article)
+ else:
+ logger.warning("Unknown status %s for file %s, skipping it." %
+ (repr(article.status),
+ repr(f)))
+
+ self.articles, self.translations = process_translations(all_articles)
+
+ for article in self.articles:
+ # only main articles are listed in categories and tags
+ # not translations
+ self.categories[article.category].append(article)
+ if hasattr(article, 'tags'):
+ for tag in article.tags:
+ self.tags[tag].append(article)
+ # ignore blank authors as well as undefined
+ if hasattr(article, 'authors'):
+ for author in 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)
+ self.dates.sort(key=attrgetter('date'),
+ reverse=self.context['NEWEST_FIRST_ARCHIVES'])
+
+ # create tag cloud
+ tag_cloud = defaultdict(int)
+ for article in self.articles:
+ for tag in getattr(article, 'tags', []):
+ tag_cloud[tag] += 1
+
+ tag_cloud = sorted(tag_cloud.items(), key=itemgetter(1), reverse=True)
+ tag_cloud = tag_cloud[:self.settings.get('TAG_CLOUD_MAX_ITEMS')]
+
+ tags = list(map(itemgetter(1), tag_cloud))
+ if tags:
+ max_count = max(tags)
+ steps = self.settings.get('TAG_CLOUD_STEPS')
+
+ # calculate word sizes
+ self.tag_cloud = [
+ (
+ tag,
+ int(math.floor(steps - (steps - 1) * math.log(count)
+ / (math.log(max_count)or 1)))
+ )
+ for tag, count in tag_cloud
+ ]
+ # put words in chaos
+ random.shuffle(self.tag_cloud)
+
+ # and generate the output :)
+
+ # order the categories per name
+ self.categories = list(self.categories.items())
+ self.categories.sort(
+ reverse=self.settings['REVERSE_CATEGORY_ORDER'])
+
+ self.authors = list(self.authors.items())
+ self.authors.sort()
+
+ self._update_context(('articles', 'dates', 'tags', 'categories',
+ 'tag_cloud', 'authors', 'related_posts'))
+
+ signals.article_generator_finalized.send(self)
+
+ def generate_output(self, writer):
+ self.generate_feeds(writer)
+ self.generate_pages(writer)
+
+
+class PagesGenerator(Generator):
+ """Generate pages"""
+
+ def __init__(self, *args, **kwargs):
+ self.pages = []
+ self.hidden_pages = []
+ self.hidden_translations = []
+ super(PagesGenerator, self).__init__(*args, **kwargs)
+ signals.page_generator_init.send(self)
+
+ def generate_context(self):
+ all_pages = []
+ hidden_pages = []
+ for f in self.get_files(
+ self.settings['PAGE_DIR'],
+ exclude=self.settings['PAGE_EXCLUDES']):
+ try:
+ page = read_file(
+ base_path=self.path, path=f, content_class=Page,
+ settings=self.settings, 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
+
+ self.add_source_path(page)
+
+ if page.status == "published":
+ all_pages.append(page)
+ elif page.status == "hidden":
+ hidden_pages.append(page)
+ else:
+ logger.warning("Unknown status %s for file %s, skipping it." %
+ (repr(page.status),
+ repr(f)))
+
+ self.pages, self.translations = process_translations(all_pages)
+ self.hidden_pages, self.hidden_translations = (
+ process_translations(hidden_pages))
+
+ self._update_context(('pages', ))
+ self.context['PAGES'] = self.pages
+
+ signals.page_generator_finalized.send(self)
+
+ def generate_output(self, writer):
+ for page in chain(self.translations, self.pages,
+ self.hidden_translations, self.hidden_pages):
+ writer.write_file(page.save_as, self.get_template(page.template),
+ self.context, page=page,
+ relative_urls=self.settings['RELATIVE_URLS'])
+
+
+class StaticGenerator(Generator):
+ """copy static paths (what you want to copy, like images, medias etc.
+ to output"""
+
+ def _copy_paths(self, paths, source, destination, output_path,
+ final_path=None):
+ """Copy all the paths from source to destination"""
+ for path in paths:
+ copy(path, source, os.path.join(output_path, destination),
+ final_path, overwrite=True)
+
+ 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 = read_file(
+ base_path=self.path, path=f, content_class=Static,
+ fmt='static',
+ settings=self.settings, 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)
+
+ def generate_output(self, writer):
+ self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme,
+ 'theme', self.output_path, os.curdir)
+ # copy all Static files
+ for sc in self.staticfiles:
+ source_path = os.path.join(self.path, sc.source_path)
+ save_as = os.path.join(self.output_path, sc.save_as)
+ mkdir_p(os.path.dirname(save_as))
+ shutil.copy(source_path, save_as)
+ logger.info('copying {} to {}'.format(sc.source_path, sc.save_as))
+
+
+class PdfGenerator(Generator):
+ """Generate PDFs on the output dir, for all articles and pages coming from
+ rst"""
+ def __init__(self, *args, **kwargs):
+ super(PdfGenerator, self).__init__(*args, **kwargs)
+ try:
+ from rst2pdf.createpdf import RstToPdf
+ pdf_style_path = os.path.join(self.settings['PDF_STYLE_PATH'])
+ pdf_style = self.settings['PDF_STYLE']
+ self.pdfcreator = RstToPdf(breakside=0,
+ stylesheets=[pdf_style],
+ style_path=[pdf_style_path])
+ except ImportError:
+ raise Exception("unable to find rst2pdf")
+
+ def _create_pdf(self, obj, output_path):
+ if obj.source_path.endswith('.rst'):
+ filename = obj.slug + ".pdf"
+ output_pdf = os.path.join(output_path, filename)
+ # print('Generating pdf for', obj.source_path, 'in', output_pdf)
+ with open(obj.source_path) as f:
+ self.pdfcreator.createPdf(text=f.read(), output=output_pdf)
+ logger.info(' [ok] writing %s' % output_pdf)
+
+ def generate_context(self):
+ pass
+
+ def generate_output(self, writer=None):
+ # we don't use the writer passed as argument here
+ # since we write our own files
+ logger.info(' Generating PDF files...')
+ pdf_path = os.path.join(self.output_path, 'pdf')
+ if not os.path.exists(pdf_path):
+ try:
+ os.mkdir(pdf_path)
+ except OSError:
+ logger.error("Couldn't create the pdf output folder in " +
+ pdf_path)
+
+ for article in self.context['articles']:
+ self._create_pdf(article, pdf_path)
+
+ for page in self.context['pages']:
+ self._create_pdf(page, pdf_path)
+
+
+class SourceFileGenerator(Generator):
+ def generate_context(self):
+ self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION']
+
+ def _create_source(self, obj):
+ output_path, _ = os.path.splitext(obj.save_as)
+ dest = os.path.join(self.output_path,
+ output_path + self.output_extension)
+ copy('', obj.source_path, dest)
+
+ def generate_output(self, writer=None):
+ logger.info(' Generating source files...')
+ for obj in chain(self.context['articles'], self.context['pages']):
+ self._create_source(obj)
+ for obj_trans in obj.translations:
+ self._create_source(obj_trans)
diff --git a/pelican/log.py b/pelican/log.py
new file mode 100644
index 00000000..bde8037e
--- /dev/null
+++ b/pelican/log.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+
+__all__ = [
+ 'init'
+]
+
+import os
+import sys
+import logging
+
+from logging import Formatter, getLogger, StreamHandler, DEBUG
+
+
+RESET_TERM = '\033[0;m'
+
+COLOR_CODES = {
+ 'red': 31,
+ 'yellow': 33,
+ 'cyan': 36,
+ 'white': 37,
+ 'bgred': 41,
+ 'bggrey': 100,
+}
+
+
+def ansi(color, text):
+ """Wrap text in an ansi escape sequence"""
+ code = COLOR_CODES[color]
+ return '\033[1;{0}m{1}{2}'.format(code, text, RESET_TERM)
+
+
+class ANSIFormatter(Formatter):
+ """Convert a `logging.LogRecord' object into colored text, using ANSI
+ escape sequences.
+
+ """
+ def format(self, record):
+ msg = record.getMessage()
+ if record.levelname == 'INFO':
+ return ansi('cyan', '-> ') + msg
+ elif record.levelname == 'WARNING':
+ return ansi('yellow', record.levelname) + ': ' + msg
+ elif record.levelname == 'ERROR':
+ return ansi('red', record.levelname) + ': ' + msg
+ elif record.levelname == 'CRITICAL':
+ return ansi('bgred', record.levelname) + ': ' + msg
+ elif record.levelname == 'DEBUG':
+ return ansi('bggrey', record.levelname) + ': ' + msg
+ else:
+ return ansi('white', record.levelname) + ': ' + msg
+
+
+class TextFormatter(Formatter):
+ """
+ Convert a `logging.LogRecord' object into text.
+ """
+
+ def format(self, record):
+ if not record.levelname or record.levelname == 'INFO':
+ return record.getMessage()
+ else:
+ return record.levelname + ': ' + record.getMessage()
+
+
+def init(level=None, logger=getLogger(), handler=StreamHandler()):
+ logger = logging.getLogger()
+
+ if (os.isatty(sys.stdout.fileno())
+ and not sys.platform.startswith('win')):
+ fmt = ANSIFormatter()
+ else:
+ fmt = TextFormatter()
+ handler.setFormatter(fmt)
+ logger.addHandler(handler)
+
+ if level:
+ logger.setLevel(level)
+
+
+if __name__ == '__main__':
+ init(level=DEBUG)
+
+ root_logger = logging.getLogger()
+ root_logger.debug('debug')
+ root_logger.info('info')
+ root_logger.warning('warning')
+ root_logger.error('error')
+ root_logger.critical('critical')
diff --git a/pelican/paginator.py b/pelican/paginator.py
new file mode 100644
index 00000000..067215c2
--- /dev/null
+++ b/pelican/paginator.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+
+# From django.core.paginator
+from math import ceil
+
+
+class Paginator(object):
+ def __init__(self, object_list, per_page, orphans=0):
+ self.object_list = object_list
+ self.per_page = per_page
+ self.orphans = orphans
+ self._num_pages = self._count = None
+
+ def page(self, number):
+ "Returns a Page object for the given 1-based page number."
+ bottom = (number - 1) * self.per_page
+ top = bottom + self.per_page
+ if top + self.orphans >= self.count:
+ top = self.count
+ return Page(self.object_list[bottom:top], number, self)
+
+ def _get_count(self):
+ "Returns the total number of objects, across all pages."
+ if self._count is None:
+ self._count = len(self.object_list)
+ return self._count
+ count = property(_get_count)
+
+ def _get_num_pages(self):
+ "Returns the total number of pages."
+ if self._num_pages is None:
+ hits = max(1, self.count - self.orphans)
+ self._num_pages = int(ceil(hits / (float(self.per_page) or 1)))
+ return self._num_pages
+ num_pages = property(_get_num_pages)
+
+ def _get_page_range(self):
+ """
+ Returns a 1-based range of pages for iterating through within
+ a template for loop.
+ """
+ return list(range(1, self.num_pages + 1))
+ page_range = property(_get_page_range)
+
+
+class Page(object):
+ def __init__(self, object_list, number, paginator):
+ self.object_list = object_list
+ self.number = number
+ self.paginator = paginator
+
+ def __repr__(self):
+ return '' % (self.number, self.paginator.num_pages)
+
+ def has_next(self):
+ return self.number < self.paginator.num_pages
+
+ def has_previous(self):
+ return self.number > 1
+
+ def has_other_pages(self):
+ return self.has_previous() or self.has_next()
+
+ def next_page_number(self):
+ return self.number + 1
+
+ def previous_page_number(self):
+ return self.number - 1
+
+ def start_index(self):
+ """
+ Returns the 1-based index of the first object on this page,
+ relative to total objects in the paginator.
+ """
+ # Special case, return zero if no items.
+ if self.paginator.count == 0:
+ return 0
+ return (self.paginator.per_page * (self.number - 1)) + 1
+
+ def end_index(self):
+ """
+ Returns the 1-based index of the last object on this page,
+ relative to total objects found (hits).
+ """
+ # Special case for the last page because there can be orphans.
+ if self.number == self.paginator.num_pages:
+ return self.paginator.count
+ return self.number * self.paginator.per_page
diff --git a/pelican/readers.py b/pelican/readers.py
new file mode 100644
index 00000000..f6140525
--- /dev/null
+++ b/pelican/readers.py
@@ -0,0 +1,470 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+
+import datetime
+import logging
+import os
+import re
+try:
+ 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:
+ core = False
+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:
+ from cgi import escape # NOQA
+try:
+ from html.parser import HTMLParser
+except ImportError:
+ from HTMLParser import HTMLParser
+
+from pelican.contents import Page, Category, Tag, Author
+from pelican.utils import get_date, pelican_open
+
+
+logger = logging.getLogger(__name__)
+
+METADATA_PROCESSORS = {
+ 'tags': lambda x, y: [Tag(tag, y) for tag in x.split(',')],
+ 'date': lambda x, y: get_date(x),
+ 'status': lambda x, y: x.strip(),
+ 'category': Category,
+ 'author': Author,
+ 'authors': lambda x, y: [Author(name, y) for name in x.split(',')],
+}
+
+
+class Reader(object):
+ enabled = True
+ file_extensions = ['static']
+ extensions = None
+
+ def __init__(self, settings):
+ self.settings = settings
+
+ def process_metadata(self, name, value):
+ if name in METADATA_PROCESSORS:
+ return METADATA_PROCESSORS[name](value, self.settings)
+ return value
+
+ def read(self, source_path):
+ "No-op parser"
+ content = None
+ metadata = {}
+ return content, metadata
+
+
+class _FieldBodyTranslator(HTMLTranslator):
+
+ def __init__(self, document):
+ HTMLTranslator.__init__(self, document)
+ self.compact_p = None
+
+ def astext(self):
+ return ''.join(self.body)
+
+ def visit_field_body(self, node):
+ pass
+
+ def depart_field_body(self, node):
+ pass
+
+
+def render_node_to_html(document, node):
+ visitor = _FieldBodyTranslator(document)
+ node.walkabout(visitor)
+ return visitor.astext()
+
+
+class PelicanHTMLTranslator(HTMLTranslator):
+
+ def visit_abbreviation(self, node):
+ attrs = {}
+ if node.hasattr('explanation'):
+ attrs['title'] = node['explanation']
+ self.body.append(self.starttag(node, 'abbr', '', **attrs))
+
+ def depart_abbreviation(self, node):
+ self.body.append('')
+
+
+class RstReader(Reader):
+ enabled = bool(docutils)
+ file_extensions = ['rst']
+
+ def __init__(self, *args, **kwargs):
+ super(RstReader, self).__init__(*args, **kwargs)
+
+ def _parse_metadata(self, document):
+ """Return the dict containing document metadata"""
+ output = {}
+ for docinfo in document.traverse(docutils.nodes.docinfo):
+ for element in docinfo.children:
+ if element.tagname == 'field': # custom fields (e.g. summary)
+ name_elem, body_elem = element.children
+ name = name_elem.astext()
+ if name == 'summary':
+ value = render_node_to_html(document, body_elem)
+ else:
+ value = body_elem.astext()
+ else: # standard fields (e.g. address)
+ name = element.tagname
+ value = element.astext()
+ name = name.lower()
+
+ output[name] = self.process_metadata(name, value)
+ return output
+
+ def _get_publisher(self, source_path):
+ extra_params = {'initial_header_level': '2',
+ 'syntax_highlight': 'short',
+ 'input_encoding': 'utf-8'}
+ user_params = self.settings.get('DOCUTILS_SETTINGS')
+ if user_params:
+ extra_params.update(user_params)
+
+ pub = docutils.core.Publisher(
+ destination_class=docutils.io.StringOutput)
+ pub.set_components('standalone', 'restructuredtext', 'html')
+ pub.writer.translator_class = PelicanHTMLTranslator
+ pub.process_programmatic_settings(None, extra_params, None)
+ pub.set_source(source_path=source_path)
+ pub.publish()
+ return pub
+
+ def read(self, source_path):
+ """Parses restructured text"""
+ pub = self._get_publisher(source_path)
+ parts = pub.writer.parts
+ content = parts.get('body')
+
+ metadata = self._parse_metadata(pub.document)
+ metadata.setdefault('title', parts.get('title'))
+
+ return content, metadata
+
+
+class MarkdownReader(Reader):
+ enabled = bool(Markdown)
+ file_extensions = ['md', 'markdown', 'mkd', 'mdown']
+
+ def __init__(self, *args, **kwargs):
+ super(MarkdownReader, self).__init__(*args, **kwargs)
+ self.extensions = self.settings['MD_EXTENSIONS']
+ self.extensions.append('meta')
+ self._md = Markdown(extensions=self.extensions)
+
+ def _parse_metadata(self, meta):
+ """Return the dict containing document metadata"""
+ output = {}
+ for name, value in meta.items():
+ name = name.lower()
+ if name == "summary":
+ 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)
+ else:
+ output[name] = self.process_metadata(name, value[0])
+ return output
+
+ def read(self, source_path):
+ """Parse content and metadata of markdown files"""
+
+ with pelican_open(source_path) as text:
+ content = self._md.convert(text)
+
+ metadata = self._parse_metadata(self._md.Meta)
+ return content, metadata
+
+
+class HTMLReader(Reader):
+ """Parses HTML files as input, looking for meta, title, and body tags"""
+ file_extensions = ['htm', 'html']
+ enabled = True
+
+ class _HTMLParser(HTMLParser):
+ def __init__(self, settings):
+ HTMLParser.__init__(self)
+ self.body = ''
+ self.metadata = {}
+ self.settings = settings
+
+ self._data_buffer = ''
+
+ self._in_top_level = True
+ self._in_head = False
+ self._in_title = False
+ self._in_body = False
+ self._in_tags = False
+
+ def handle_starttag(self, tag, attrs):
+ if tag == 'head' and self._in_top_level:
+ self._in_top_level = False
+ self._in_head = True
+ elif tag == 'title' and self._in_head:
+ self._in_title = True
+ self._data_buffer = ''
+ elif tag == 'body' and self._in_top_level:
+ self._in_top_level = False
+ self._in_body = True
+ self._data_buffer = ''
+ elif tag == 'meta' and self._in_head:
+ self._handle_meta_tag(attrs)
+
+ elif self._in_body:
+ self._data_buffer += self.build_tag(tag, attrs, False)
+
+ def handle_endtag(self, tag):
+ if tag == 'head':
+ if self._in_head:
+ self._in_head = False
+ self._in_top_level = True
+ elif tag == 'title':
+ self._in_title = False
+ self.metadata['title'] = self._data_buffer
+ elif tag == 'body':
+ self.body = self._data_buffer
+ self._in_body = False
+ self._in_top_level = True
+ elif self._in_body:
+ self._data_buffer += '{}>'.format(escape(tag))
+
+ def handle_startendtag(self, tag, attrs):
+ if tag == 'meta' and self._in_head:
+ self._handle_meta_tag(attrs)
+ if self._in_body:
+ self._data_buffer += self.build_tag(tag, attrs, True)
+
+ def handle_comment(self, data):
+ self._data_buffer += ''.format(data)
+
+ def handle_data(self, data):
+ self._data_buffer += data
+
+ def handle_entityref(self, data):
+ self._data_buffer += '&{};'.format(data)
+
+ def handle_charref(self, data):
+ self._data_buffer += '{};'.format(data)
+
+ def build_tag(self, tag, attrs, close_tag):
+ result = '<{}'.format(escape(tag))
+ for k, v in attrs:
+ result += ' ' + escape(k)
+ if v is not None:
+ result += '="{}"'.format(escape(v))
+ if close_tag:
+ return result + ' />'
+ return result + '>'
+
+ def _handle_meta_tag(self, attrs):
+ name = self._attr_value(attrs, 'name').lower()
+ contents = self._attr_value(attrs, 'contents', '')
+
+ if name == 'keywords':
+ name = 'tags'
+ self.metadata[name] = contents
+
+ @classmethod
+ def _attr_value(cls, attrs, name, default=None):
+ return next((x[1] for x in attrs if x[0] == name), default)
+
+ def read(self, filename):
+ """Parse content and metadata of HTML files"""
+ with pelican_open(filename) as content:
+ parser = self._HTMLParser(self.settings)
+ parser.feed(content)
+ parser.close()
+
+ metadata = {}
+ for k in parser.metadata:
+ metadata[k] = self.process_metadata(k, parser.metadata[k])
+ return parser.body, metadata
+
+
+class AsciiDocReader(Reader):
+ enabled = bool(asciidoc)
+ file_extensions = ['asc']
+ 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
+
+
+EXTENSIONS = {}
+
+for cls in [Reader] + Reader.__subclasses__():
+ for ext in cls.file_extensions:
+ EXTENSIONS[ext] = cls
+
+
+def read_file(base_path, path, content_class=Page, fmt=None,
+ settings=None, context=None,
+ preread_signal=None, preread_sender=None,
+ context_signal=None, context_sender=None):
+ """Return a content object parsed with the given format."""
+ path = os.path.abspath(os.path.join(base_path, path))
+ source_path = os.path.relpath(path, base_path)
+ base, ext = os.path.splitext(os.path.basename(path))
+ logger.debug('read file {} -> {}'.format(
+ source_path, content_class.__name__))
+ if not fmt:
+ fmt = ext[1:]
+
+ if fmt not in EXTENSIONS:
+ raise TypeError('Pelican does not know how to parse {}'.format(path))
+
+ if preread_signal:
+ logger.debug('signal {}.send({})'.format(
+ preread_signal, preread_sender))
+ preread_signal.send(preread_sender)
+
+ if settings is None:
+ settings = {}
+
+ reader_class = EXTENSIONS[fmt]
+ if not reader_class.enabled:
+ raise ValueError('Missing dependencies for {}'.format(fmt))
+
+ reader = reader_class(settings)
+
+ settings_key = '%s_EXTENSIONS' % fmt.upper()
+
+ if settings and settings_key in settings:
+ reader.extensions = settings[settings_key]
+
+ metadata = default_metadata(
+ settings=settings, process=reader.process_metadata)
+ metadata.update(path_metadata(
+ full_path=path, source_path=source_path, settings=settings))
+ metadata.update(parse_path_metadata(
+ source_path=source_path, settings=settings,
+ process=reader.process_metadata))
+ content, reader_metadata = reader.read(path)
+ metadata.update(reader_metadata)
+
+ # eventually filter the content with typogrify if asked so
+ if content and settings and settings['TYPOGRIFY']:
+ from typogrify.filters import typogrify
+ content = typogrify(content)
+ metadata['title'] = typogrify(metadata['title'])
+
+ if context_signal:
+ logger.debug('signal {}.send({}, )'.format(
+ context_signal, context_sender))
+ context_signal.send(context_sender, metadata=metadata)
+ return content_class(
+ content=content,
+ metadata=metadata,
+ settings=settings,
+ source_path=path,
+ context=context)
+
+
+def default_metadata(settings=None, process=None):
+ metadata = {}
+ if settings:
+ if 'DEFAULT_CATEGORY' in settings:
+ value = settings['DEFAULT_CATEGORY']
+ if process:
+ value = process('category', value)
+ metadata['category'] = value
+ if 'DEFAULT_DATE' in settings and settings['DEFAULT_DATE'] != 'fs':
+ metadata['date'] = datetime.datetime(*settings['DEFAULT_DATE'])
+ return metadata
+
+
+def path_metadata(full_path, source_path, settings=None):
+ metadata = {}
+ if settings:
+ if settings.get('DEFAULT_DATE', None) == 'fs':
+ metadata['date'] = datetime.datetime.fromtimestamp(
+ os.stat(full_path).st_ctime)
+ metadata.update(settings.get('EXTRA_PATH_METADATA', {}).get(
+ source_path, {}))
+ return metadata
+
+
+def parse_path_metadata(source_path, settings=None, process=None):
+ """Extract a metadata dictionary from a file's path
+
+ >>> import pprint
+ >>> settings = {
+ ... 'FILENAME_METADATA': '(?P[^.]*).*',
+ ... 'PATH_METADATA':
+ ... '(?P[^/]*)/(?P\d{4}-\d{2}-\d{2})/.*',
+ ... }
+ >>> reader = Reader(settings=settings)
+ >>> metadata = parse_path_metadata(
+ ... source_path='my-cat/2013-01-01/my-slug.html',
+ ... settings=settings,
+ ... process=reader.process_metadata)
+ >>> pprint.pprint(metadata) # doctest: +ELLIPSIS
+ {'category': ,
+ 'date': datetime.datetime(2013, 1, 1, 0, 0),
+ 'slug': 'my-slug'}
+ """
+ metadata = {}
+ dirname, basename = os.path.split(source_path)
+ base, ext = os.path.splitext(basename)
+ subdir = os.path.basename(dirname)
+ if settings:
+ checks = []
+ for key, data in [('FILENAME_METADATA', base),
+ ('PATH_METADATA', source_path),
+ ]:
+ checks.append((settings.get(key, None), data))
+ if settings.get('USE_FOLDER_AS_CATEGORY', None):
+ checks.insert(0, ('(?P.*)', subdir))
+ for regexp, data in checks:
+ if regexp and data:
+ match = re.match(regexp, data)
+ if match:
+ # .items() for py3k compat.
+ for k, v in match.groupdict().items():
+ if k not in metadata:
+ k = k.lower() # metadata must be lowercase
+ if process:
+ v = process(k, v)
+ metadata[k] = v
+ return metadata
diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py
new file mode 100644
index 00000000..fb4a6c93
--- /dev/null
+++ b/pelican/rstdirectives.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+
+from docutils import nodes, utils
+from docutils.parsers.rst import directives, roles, Directive
+from pygments.formatters import HtmlFormatter
+from pygments import highlight
+from pygments.lexers import get_lexer_by_name, TextLexer
+import re
+
+INLINESTYLES = False
+DEFAULT = HtmlFormatter(noclasses=INLINESTYLES)
+VARIANTS = {
+ 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
+}
+
+
+class Pygments(Directive):
+ """ Source code syntax hightlighting.
+ """
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = dict([(key, directives.flag) for key in VARIANTS])
+ has_content = True
+
+ def run(self):
+ self.assert_has_content()
+ try:
+ lexer = get_lexer_by_name(self.arguments[0])
+ except ValueError:
+ # no lexer found - use the text one instead of an exception
+ lexer = TextLexer()
+ # take an arbitrary option if more than one is given
+ formatter = self.options and VARIANTS[self.options.keys()[0]] \
+ or DEFAULT
+ parsed = highlight('\n'.join(self.content), lexer, formatter)
+ return [nodes.raw('', parsed, format='html')]
+
+directives.register_directive('code-block', Pygments)
+directives.register_directive('sourcecode', Pygments)
+
+
+class YouTube(Directive):
+ """ Embed YouTube video in posts.
+
+ Courtesy of Brian Hsu: https://gist.github.com/1422773
+
+ VIDEO_ID is required, with / height are optional integer,
+ and align could be left / center / right.
+
+ Usage:
+ .. youtube:: VIDEO_ID
+ :width: 640
+ :height: 480
+ :align: center
+ """
+
+ def align(argument):
+ """Conversion function for the "align" option."""
+ return directives.choice(argument, ('left', 'center', 'right'))
+
+ required_arguments = 1
+ optional_arguments = 2
+ option_spec = {
+ 'width': directives.positive_int,
+ 'height': directives.positive_int,
+ 'align': align
+ }
+
+ final_argument_whitespace = False
+ has_content = False
+
+ def run(self):
+ videoID = self.arguments[0].strip()
+ width = 420
+ height = 315
+ align = 'left'
+
+ if 'width' in self.options:
+ width = self.options['width']
+
+ if 'height' in self.options:
+ height = self.options['height']
+
+ if 'align' in self.options:
+ align = self.options['align']
+
+ url = 'http://www.youtube.com/embed/%s' % videoID
+ div_block = '' % align
+ embed_block = '' % (width, height, url)
+
+ return [
+ nodes.raw('', div_block, format='html'),
+ nodes.raw('', embed_block, format='html'),
+ nodes.raw('', '
', format='html')]
+
+directives.register_directive('youtube', YouTube)
+
+_abbr_re = re.compile('\((.*)\)$')
+
+
+class abbreviation(nodes.Inline, nodes.TextElement):
+ pass
+
+
+def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
+ text = utils.unescape(text)
+ m = _abbr_re.search(text)
+ if m is None:
+ return [abbreviation(text, text)], []
+ abbr = text[:m.start()].strip()
+ expl = m.group(1)
+ return [abbreviation(abbr, abbr, explanation=expl)], []
+
+roles.register_local_role('abbr', abbr_role)
diff --git a/pelican/server.py b/pelican/server.py
new file mode 100644
index 00000000..fd99b209
--- /dev/null
+++ b/pelican/server.py
@@ -0,0 +1,29 @@
+from __future__ import print_function
+import sys
+try:
+ import SimpleHTTPServer as srvmod
+except ImportError:
+ import http.server as srvmod # NOQA
+
+try:
+ import SocketServer as socketserver
+except ImportError:
+ import socketserver # NOQA
+
+PORT = 8000
+
+Handler = srvmod.SimpleHTTPRequestHandler
+
+try:
+ httpd = socketserver.TCPServer(("", PORT), Handler)
+except OSError as e:
+ print("Could not listen on port", PORT)
+ sys.exit(getattr(e, 'exitcode', 1))
+
+
+print("serving at port", PORT)
+try:
+ httpd.serve_forever()
+except KeyboardInterrupt as e:
+ print("shutting down server")
+ httpd.socket.close()
\ No newline at end of file
diff --git a/pelican/settings.py b/pelican/settings.py
new file mode 100644
index 00000000..1c9b48c3
--- /dev/null
+++ b/pelican/settings.py
@@ -0,0 +1,271 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+import six
+
+import copy
+import inspect
+import os
+import locale
+import logging
+
+try:
+ # SourceFileLoader is the recommended way in 3.3+
+ from importlib.machinery import SourceFileLoader
+ load_source = lambda name, path: SourceFileLoader(name, path).load_module()
+except ImportError:
+ # but it does not exist in 3.2-, so fall back to imp
+ import imp
+ load_source = imp.load_source
+
+from os.path import isabs
+
+
+logger = logging.getLogger(__name__)
+
+
+DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ 'themes', 'notmyidea')
+DEFAULT_CONFIG = {
+ 'PATH': os.curdir,
+ 'ARTICLE_DIR': '',
+ 'ARTICLE_EXCLUDES': ('pages',),
+ 'PAGE_DIR': 'pages',
+ 'PAGE_EXCLUDES': (),
+ 'THEME': DEFAULT_THEME,
+ 'OUTPUT_PATH': 'output',
+ 'MARKUP': ('rst', 'md'),
+ 'STATIC_PATHS': ['images', ],
+ 'THEME_STATIC_PATHS': ['static', ],
+ 'FEED_ALL_ATOM': os.path.join('feeds', 'all.atom.xml'),
+ 'CATEGORY_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'),
+ 'TRANSLATION_FEED_ATOM': os.path.join('feeds', 'all-%s.atom.xml'),
+ 'FEED_MAX_ITEMS': '',
+ 'SITEURL': '',
+ 'SITENAME': 'A Pelican Blog',
+ 'DISPLAY_PAGES_ON_MENU': True,
+ 'DISPLAY_CATEGORIES_ON_MENU': True,
+ 'PDF_GENERATOR': False,
+ 'OUTPUT_SOURCES': False,
+ 'OUTPUT_SOURCES_EXTENSION': '.text',
+ 'USE_FOLDER_AS_CATEGORY': True,
+ 'DEFAULT_CATEGORY': 'misc',
+ 'WITH_FUTURE_DATES': True,
+ 'CSS_FILE': 'main.css',
+ 'NEWEST_FIRST_ARCHIVES': True,
+ 'REVERSE_CATEGORY_ORDER': False,
+ 'DELETE_OUTPUT_DIRECTORY': False,
+ 'OUTPUT_RETENTION': (),
+ 'ARTICLE_URL': '{slug}.html',
+ 'ARTICLE_SAVE_AS': '{slug}.html',
+ 'ARTICLE_LANG_URL': '{slug}-{lang}.html',
+ 'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html',
+ 'PAGE_URL': 'pages/{slug}.html',
+ 'PAGE_SAVE_AS': os.path.join('pages', '{slug}.html'),
+ 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html',
+ 'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'),
+ 'STATIC_URL': '{path}',
+ 'STATIC_SAVE_AS': '{path}',
+ 'PDF_STYLE_PATH': '',
+ 'PDF_STYLE': 'twelvepoint',
+ 'CATEGORY_URL': 'category/{slug}.html',
+ 'CATEGORY_SAVE_AS': os.path.join('category', '{slug}.html'),
+ 'TAG_URL': 'tag/{slug}.html',
+ 'TAG_SAVE_AS': os.path.join('tag', '{slug}.html'),
+ 'AUTHOR_URL': 'author/{slug}.html',
+ 'AUTHOR_SAVE_AS': os.path.join('author', '{slug}.html'),
+ 'YEAR_ARCHIVE_SAVE_AS': False,
+ 'MONTH_ARCHIVE_SAVE_AS': False,
+ 'DAY_ARCHIVE_SAVE_AS': False,
+ 'RELATIVE_URLS': False,
+ 'DEFAULT_LANG': 'en',
+ 'TAG_CLOUD_STEPS': 4,
+ 'TAG_CLOUD_MAX_ITEMS': 100,
+ 'DIRECT_TEMPLATES': ('index', 'tags', 'categories', 'archives'),
+ 'EXTRA_TEMPLATES_PATHS': [],
+ 'PAGINATED_DIRECT_TEMPLATES': ('index', ),
+ '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': {},
+ 'LOCALE': [], # defaults to user locale
+ 'DEFAULT_PAGINATION': False,
+ 'DEFAULT_ORPHANS': 0,
+ 'DEFAULT_METADATA': (),
+ 'FILENAME_METADATA': '(?P\d{4}-\d{2}-\d{2}).*',
+ 'PATH_METADATA': '',
+ 'EXTRA_PATH_METADATA': {},
+ 'DEFAULT_STATUS': 'published',
+ 'ARTICLE_PERMALINK_STRUCTURE': '',
+ 'TYPOGRIFY': False,
+ 'SUMMARY_MAX_LENGTH': 50,
+ 'PLUGIN_PATH': '',
+ 'PLUGINS': [],
+ 'TEMPLATE_PAGES': {},
+ 'IGNORE_FILES': ['.#*'],
+ }
+
+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']:
+ 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):
+ local_settings[p] = absp
+ else:
+ local_settings = copy.deepcopy(DEFAULT_CONFIG)
+
+ if override:
+ local_settings.update(override)
+
+ return configure_settings(local_settings)
+
+
+def get_settings_from_module(module=None, default_settings=DEFAULT_CONFIG):
+ """Loads settings from a module, returns a dictionary."""
+
+ context = copy.deepcopy(default_settings)
+ if module is not None:
+ context.update(
+ (k, v) for k, v in inspect.getmembers(module) if k.isupper())
+ return context
+
+
+def get_settings_from_file(path, default_settings=DEFAULT_CONFIG):
+ """Loads settings from a file path, returning a dict."""
+
+ name, ext = os.path.splitext(os.path.basename(path))
+ module = load_source(name, path)
+ return get_settings_from_module(module, default_settings=default_settings)
+
+
+def configure_settings(settings):
+ """Provide optimizations, error checking and warnings for the given
+ settings.
+
+ """
+ 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)')
+
+ # 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(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'themes',
+ settings['THEME'])
+ if os.path.exists(theme_path):
+ settings['THEME'] = theme_path
+ else:
+ raise Exception("Could not find the theme %s"
+ % settings['THEME'])
+
+ # standardize strings to lowercase strings
+ for key in [
+ 'DEFAULT_LANG',
+ ]:
+ if key in settings:
+ settings[key] = settings[key].lower()
+
+ # standardize strings to lists
+ for key in [
+ 'LOCALE',
+ ]:
+ if key in settings and isinstance(settings[key], six.string_types):
+ settings[key] = [settings[key]]
+
+ # check settings that must be a particular type
+ for key, types in [
+ ('OUTPUT_SOURCES_EXTENSION', six.string_types),
+ ('FILENAME_METADATA', six.string_types),
+ ]:
+ if key in settings and not isinstance(settings[key], types):
+ value = settings.pop(key)
+ logger.warn(
+ 'Detected misconfigured {} ({}), '
+ 'falling back to the default ({})'.format(
+ key, value, DEFAULT_CONFIG[key]))
+
+ # try to set the different locales, fallback on the default.
+ locales = settings.get('LOCALE', DEFAULT_CONFIG['LOCALE'])
+
+ for locale_ in locales:
+ try:
+ locale.setlocale(locale.LC_ALL, str(locale_))
+ break # break if it is successful
+ except locale.Error:
+ pass
+ else:
+ logger.warning("LOCALE option doesn't contain a correct value")
+
+ if ('SITEURL' in settings):
+ # If SITEURL has a trailing slash, remove it and provide a warning
+ siteurl = settings['SITEURL']
+ if (siteurl.endswith('/')):
+ settings['SITEURL'] = siteurl[:-1]
+ logger.warning("Removed extraneous trailing slash from SITEURL.")
+ # If SITEURL is defined but FEED_DOMAIN isn't,
+ # set FEED_DOMAIN to SITEURL
+ if not 'FEED_DOMAIN' in settings:
+ settings['FEED_DOMAIN'] = settings['SITEURL']
+
+ # Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined
+ feed_keys = ['FEED_ATOM', 'FEED_RSS',
+ 'FEED_ALL_ATOM', 'FEED_ALL_RSS',
+ 'CATEGORY_FEED_ATOM', 'CATEGORY_FEED_RSS',
+ 'TAG_FEED_ATOM', 'TAG_FEED_RSS',
+ 'TRANSLATION_FEED_ATOM', 'TRANSLATION_FEED_RSS',
+ ]
+
+ if any(settings.get(k) for k in feed_keys):
+ if not settings.get('SITEURL'):
+ logger.warning('Feeds generated without SITEURL set properly may not'
+ ' be valid')
+
+ if not 'TIMEZONE' in settings:
+ logger.warning(
+ 'No timezone information specified in the settings. Assuming'
+ ' your timezone is UTC for feed generation. Check '
+ 'http://docs.getpelican.com/en/latest/settings.html#timezone '
+ 'for more information')
+
+ # Save people from accidentally setting a string rather than a list
+ path_keys = (
+ 'ARTICLE_EXCLUDES',
+ 'DEFAULT_METADATA',
+ 'DIRECT_TEMPLATES',
+ 'EXTRA_TEMPLATES_PATHS',
+ 'FILES_TO_COPY',
+ 'IGNORE_FILES',
+ 'JINJA_EXTENSIONS',
+ 'MARKUP',
+ 'PAGINATED_DIRECT_TEMPLATES',
+ 'PLUGINS',
+ 'STATIC_PATHS',
+ 'THEME_STATIC_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]
+
+ for old,new,doc in [
+ ('LESS_GENERATOR', 'the Webassets plugin', None),
+ ('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA',
+ 'https://github.com/getpelican/pelican/blob/master/docs/settings.rst#path-metadata'),
+ ]:
+ if old in settings:
+ message = 'The {} setting has been removed in favor of {}'.format(
+ old, new)
+ if doc:
+ message += ', see {} for details'.format(doc)
+ logger.warning(message)
+
+ return settings
diff --git a/pelican/signals.py b/pelican/signals.py
new file mode 100644
index 00000000..cb010d37
--- /dev/null
+++ b/pelican/signals.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+from blinker import signal
+
+# Run-level signals:
+
+initialized = signal('pelican_initialized')
+get_generators = signal('get_generators')
+finalized = signal('pelican_finalized')
+
+# Generator-level signals
+
+generator_init = signal('generator_init')
+
+article_generator_init = signal('article_generator_init')
+article_generator_finalized = signal('article_generator_finalized')
+
+page_generator_init = signal('page_generator_init')
+page_generator_finalized = signal('page_generator_finalized')
+
+static_generator_init = signal('static_generator_init')
+static_generator_finalized = signal('static_generator_finalized')
+
+# Page-level signals
+
+article_generator_preread = signal('article_generator_preread')
+article_generator_context = signal('article_generator_context')
+
+page_generator_preread = signal('page_generator_preread')
+page_generator_context = signal('page_generator_context')
+
+static_generator_preread = signal('static_generator_preread')
+static_generator_context = signal('static_generator_context')
+
+content_object_init = signal('content_object_init')
diff --git a/pelican/tests/TestPages/bad_page.rst b/pelican/tests/TestPages/bad_page.rst
new file mode 100644
index 00000000..bc62948b
--- /dev/null
+++ b/pelican/tests/TestPages/bad_page.rst
@@ -0,0 +1,8 @@
+This is a test bad page
+#######################
+
+:status: invalid
+
+The quick brown fox jumped over the lazy dog's back.
+
+The status here is invalid, the page should not render.
diff --git a/pelican/tests/TestPages/hidden_page.rst b/pelican/tests/TestPages/hidden_page.rst
new file mode 100644
index 00000000..57ca329c
--- /dev/null
+++ b/pelican/tests/TestPages/hidden_page.rst
@@ -0,0 +1,8 @@
+This is a test hidden page
+##########################
+
+:status: hidden
+
+The quick brown fox jumped over the lazy dog's back.
+
+This page is hidden
diff --git a/pelican/tests/TestPages/hidden_page_markdown.md b/pelican/tests/TestPages/hidden_page_markdown.md
new file mode 100644
index 00000000..1e532fe7
--- /dev/null
+++ b/pelican/tests/TestPages/hidden_page_markdown.md
@@ -0,0 +1,12 @@
+title: This is a markdown test hidden page
+status: hidden
+
+Test Markdown File Header
+=========================
+
+Used for pelican test
+---------------------
+
+The quick brown fox jumped over the lazy dog's back.
+
+This page is hidden
diff --git a/pelican/tests/TestPages/hidden_page_with_template.rst b/pelican/tests/TestPages/hidden_page_with_template.rst
new file mode 100644
index 00000000..36104a09
--- /dev/null
+++ b/pelican/tests/TestPages/hidden_page_with_template.rst
@@ -0,0 +1,11 @@
+This is a test hidden page with a custom template
+#################################################
+
+:status: hidden
+:template: custom
+
+The quick brown fox jumped over the lazy dog's back.
+
+This page is hidden
+
+This page has a custom template to be called when rendered
diff --git a/pelican/tests/TestPages/page.rst b/pelican/tests/TestPages/page.rst
new file mode 100644
index 00000000..2d13976d
--- /dev/null
+++ b/pelican/tests/TestPages/page.rst
@@ -0,0 +1,4 @@
+This is a test page
+###################
+
+The quick brown fox jumped over the lazy dog's back.
diff --git a/pelican/tests/TestPages/page_markdown.md b/pelican/tests/TestPages/page_markdown.md
new file mode 100644
index 00000000..d5416a6f
--- /dev/null
+++ b/pelican/tests/TestPages/page_markdown.md
@@ -0,0 +1,9 @@
+title: This is a markdown test page
+
+Test Markdown File Header
+=========================
+
+Used for pelican test
+---------------------
+
+The quick brown fox jumped over the lazy dog's back.
diff --git a/pelican/tests/TestPages/page_with_template.rst b/pelican/tests/TestPages/page_with_template.rst
new file mode 100644
index 00000000..9388dc2f
--- /dev/null
+++ b/pelican/tests/TestPages/page_with_template.rst
@@ -0,0 +1,8 @@
+This is a test page with a preset template
+##########################################
+
+:template: custom
+
+The quick brown fox jumped over the lazy dog's back.
+
+This article has a custom template to be called when rendered
diff --git a/pelican/tests/__init__.py b/pelican/tests/__init__.py
new file mode 100644
index 00000000..32353ea2
--- /dev/null
+++ b/pelican/tests/__init__.py
@@ -0,0 +1,2 @@
+import logging
+logging.getLogger().addHandler(logging.NullHandler())
diff --git a/pelican/tests/content/2012-11-29_rst_w_filename_meta#foo-bar.rst b/pelican/tests/content/2012-11-29_rst_w_filename_meta#foo-bar.rst
new file mode 100644
index 00000000..43f05a15
--- /dev/null
+++ b/pelican/tests/content/2012-11-29_rst_w_filename_meta#foo-bar.rst
@@ -0,0 +1,6 @@
+
+Rst with filename metadata
+##########################
+
+:category: yeah
+:author: Alexis Métaireau
diff --git a/pelican/tests/content/2012-11-30_md_w_filename_meta#foo-bar.md b/pelican/tests/content/2012-11-30_md_w_filename_meta#foo-bar.md
new file mode 100644
index 00000000..cdccfc8a
--- /dev/null
+++ b/pelican/tests/content/2012-11-30_md_w_filename_meta#foo-bar.md
@@ -0,0 +1,6 @@
+category: yeah
+author: Alexis Métaireau
+
+Markdown with filename metadata
+===============================
+
diff --git a/pelican/tests/content/TestCategory/article_with_category.rst b/pelican/tests/content/TestCategory/article_with_category.rst
new file mode 100644
index 00000000..69ffa98b
--- /dev/null
+++ b/pelican/tests/content/TestCategory/article_with_category.rst
@@ -0,0 +1,7 @@
+This is an article with category !
+##################################
+
+:category: yeah
+:date: 1970-01-01
+
+This article should be in 'yeah' category.
diff --git a/pelican/tests/content/TestCategory/article_without_category.rst b/pelican/tests/content/TestCategory/article_without_category.rst
new file mode 100644
index 00000000..4bc5d78d
--- /dev/null
+++ b/pelican/tests/content/TestCategory/article_without_category.rst
@@ -0,0 +1,4 @@
+This is an article without category !
+#####################################
+
+This article should be in 'TestCategory' category.
diff --git a/pelican/tests/content/article.rst b/pelican/tests/content/article.rst
new file mode 100644
index 00000000..7109c29b
--- /dev/null
+++ b/pelican/tests/content/article.rst
@@ -0,0 +1,6 @@
+Article title
+#############
+
+This is some content. With some stuff to "typogrify".
+
+Now with added support for :abbr:`TLA (three letter acronym)`.
diff --git a/pelican/tests/content/article_with_asc_extension.asc b/pelican/tests/content/article_with_asc_extension.asc
new file mode 100644
index 00000000..9ce2166c
--- /dev/null
+++ b/pelican/tests/content/article_with_asc_extension.asc
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 00000000..bafb3a4a
--- /dev/null
+++ b/pelican/tests/content/article_with_asc_options.asc
@@ -0,0 +1,9 @@
+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/content/article_with_comments.html b/pelican/tests/content/article_with_comments.html
new file mode 100644
index 00000000..289e4a66
--- /dev/null
+++ b/pelican/tests/content/article_with_comments.html
@@ -0,0 +1,8 @@
+
+
+
+
+ Body content
+
+
+
diff --git a/pelican/tests/content/article_with_keywords.html b/pelican/tests/content/article_with_keywords.html
new file mode 100644
index 00000000..c869f514
--- /dev/null
+++ b/pelican/tests/content/article_with_keywords.html
@@ -0,0 +1,6 @@
+
+
+ This is a super article !
+
+
+
diff --git a/pelican/tests/content/article_with_markdown_and_footnote.md b/pelican/tests/content/article_with_markdown_and_footnote.md
new file mode 100644
index 00000000..dc257d62
--- /dev/null
+++ b/pelican/tests/content/article_with_markdown_and_footnote.md
@@ -0,0 +1,8 @@
+Title: Article with markdown containing footnotes
+Date: 2012-10-31
+Summary: Summary with **inline** markup *should* be supported.
+
+This is some content[^1] with some footnotes[^footnote]
+
+[^1]: Numbered footnote
+[^footnote]: Named footnote
\ No newline at end of file
diff --git a/pelican/tests/content/article_with_markdown_and_nonascii_summary.md b/pelican/tests/content/article_with_markdown_and_nonascii_summary.md
new file mode 100644
index 00000000..e26cc009
--- /dev/null
+++ b/pelican/tests/content/article_with_markdown_and_nonascii_summary.md
@@ -0,0 +1,18 @@
+Title: マックOS X 10.8でパイソンとVirtualenvをインストールと設定
+Slug: python-virtualenv-on-mac-osx-mountain-lion-10.8
+Date: 2012-12-20
+Tags: パイソン, マック
+Category: 指導書
+Summary: パイソンとVirtualenvをまっくでインストールする方法について明確に説明します。
+
+Writing unicode is certainly fun.
+
+パイソンとVirtualenvをまっくでインストールする方法について明確に説明します。
+
+And let's mix languages.
+
+первый пост
+
+Now another.
+
+İlk yazı çok özel değil.
diff --git a/pelican/tests/content/article_with_markdown_and_summary_metadata_multi.md b/pelican/tests/content/article_with_markdown_and_summary_metadata_multi.md
new file mode 100644
index 00000000..b6ef666c
--- /dev/null
+++ b/pelican/tests/content/article_with_markdown_and_summary_metadata_multi.md
@@ -0,0 +1,7 @@
+Title: Article with markdown and summary metadata multi
+Date: 2012-10-31
+Summary:
+ A multi-line summary should be supported
+ as well as **inline markup**.
+
+This is some content.
diff --git a/pelican/tests/content/article_with_markdown_and_summary_metadata_single.md b/pelican/tests/content/article_with_markdown_and_summary_metadata_single.md
new file mode 100644
index 00000000..a7d6f09b
--- /dev/null
+++ b/pelican/tests/content/article_with_markdown_and_summary_metadata_single.md
@@ -0,0 +1,5 @@
+Title: Article with markdown and summary metadata single
+Date: 2012-10-30
+Summary: A single-line summary should be supported as well as **inline markup**.
+
+This is some content.
diff --git a/pelican/tests/content/article_with_markdown_extension.markdown b/pelican/tests/content/article_with_markdown_extension.markdown
new file mode 100644
index 00000000..94e92871
--- /dev/null
+++ b/pelican/tests/content/article_with_markdown_extension.markdown
@@ -0,0 +1,10 @@
+title: Test markdown File
+category: test
+
+Test Markdown File Header
+=========================
+
+Used for pelican test
+---------------------
+
+This is another markdown test file. Uses the markdown extension.
diff --git a/pelican/tests/content/article_with_markdown_markup_extensions.md b/pelican/tests/content/article_with_markdown_markup_extensions.md
new file mode 100644
index 00000000..6cf56403
--- /dev/null
+++ b/pelican/tests/content/article_with_markdown_markup_extensions.md
@@ -0,0 +1,8 @@
+Title: Test Markdown extensions
+
+[TOC]
+
+## Level1
+
+### Level2
+
diff --git a/pelican/tests/content/article_with_md_extension.md b/pelican/tests/content/article_with_md_extension.md
new file mode 100644
index 00000000..1f111796
--- /dev/null
+++ b/pelican/tests/content/article_with_md_extension.md
@@ -0,0 +1,13 @@
+Title: Test md File
+Category: test
+Tags: foo, bar, foobar
+Date: 2010-12-02 10:14
+Summary: I have a lot to test
+
+Test Markdown File Header
+=========================
+
+Used for pelican test
+---------------------
+
+The quick brown fox jumped over the lazy dog's back.
diff --git a/pelican/tests/content/article_with_mdown_extension.mdown b/pelican/tests/content/article_with_mdown_extension.mdown
new file mode 100644
index 00000000..bdaf74cd
--- /dev/null
+++ b/pelican/tests/content/article_with_mdown_extension.mdown
@@ -0,0 +1,10 @@
+title: Test mdown File
+category: test
+
+Test Markdown File Header
+=========================
+
+Used for pelican test
+---------------------
+
+This is another markdown test file. Uses the mdown extension.
diff --git a/pelican/tests/content/article_with_metadata.html b/pelican/tests/content/article_with_metadata.html
new file mode 100644
index 00000000..b108ac8a
--- /dev/null
+++ b/pelican/tests/content/article_with_metadata.html
@@ -0,0 +1,15 @@
+
+
+ This is a super article !
+
+
+
+
+
+
+
+
+ Multi-line metadata should be supported
+ as well as inline markup .
+
+
diff --git a/pelican/tests/content/article_with_metadata.rst b/pelican/tests/content/article_with_metadata.rst
new file mode 100644
index 00000000..d4bac1c0
--- /dev/null
+++ b/pelican/tests/content/article_with_metadata.rst
@@ -0,0 +1,12 @@
+
+This is a super article !
+#########################
+
+:tags: foo, bar, foobar
+:date: 2010-12-02 10:14
+:category: yeah
+:author: Alexis Métaireau
+:summary:
+ Multi-line metadata should be supported
+ as well as **inline markup**.
+:custom_field: http://notmyidea.org
diff --git a/pelican/tests/content/article_with_mkd_extension.mkd b/pelican/tests/content/article_with_mkd_extension.mkd
new file mode 100644
index 00000000..c946cb87
--- /dev/null
+++ b/pelican/tests/content/article_with_mkd_extension.mkd
@@ -0,0 +1,10 @@
+title: Test mkd File
+category: test
+
+Test Markdown File Header
+=========================
+
+Used for pelican test
+---------------------
+
+This is another markdown test file. Uses the mkd extension.
diff --git a/pelican/tests/content/article_with_null_attributes.html b/pelican/tests/content/article_with_null_attributes.html
new file mode 100644
index 00000000..68da704c
--- /dev/null
+++ b/pelican/tests/content/article_with_null_attributes.html
@@ -0,0 +1,8 @@
+
+
+
+
+ Ensure that empty attributes are copied properly.
+
+
+
diff --git a/pelican/tests/content/article_with_template.rst b/pelican/tests/content/article_with_template.rst
new file mode 100644
index 00000000..eb55738c
--- /dev/null
+++ b/pelican/tests/content/article_with_template.rst
@@ -0,0 +1,8 @@
+Article with template
+#####################
+
+:template: custom
+
+This article has a custom template to be called when rendered
+
+This is some content. With some stuff to "typogrify".
diff --git a/pelican/tests/content/article_with_uppercase_metadata.html b/pelican/tests/content/article_with_uppercase_metadata.html
new file mode 100644
index 00000000..4fe5a9ee
--- /dev/null
+++ b/pelican/tests/content/article_with_uppercase_metadata.html
@@ -0,0 +1,6 @@
+
+
+ This is a super article !
+
+
+
diff --git a/pelican/tests/content/article_with_uppercase_metadata.rst b/pelican/tests/content/article_with_uppercase_metadata.rst
new file mode 100644
index 00000000..e26cdd13
--- /dev/null
+++ b/pelican/tests/content/article_with_uppercase_metadata.rst
@@ -0,0 +1,6 @@
+
+This is a super article !
+#########################
+
+:Category: Yeah
+
diff --git a/pelican/tests/content/article_without_category.rst b/pelican/tests/content/article_without_category.rst
new file mode 100644
index 00000000..ff47f6ef
--- /dev/null
+++ b/pelican/tests/content/article_without_category.rst
@@ -0,0 +1,6 @@
+
+This is an article without category !
+#####################################
+
+This article should be in the DEFAULT_CATEGORY.
+
diff --git a/pelican/tests/content/wordpress_content_decoded b/pelican/tests/content/wordpress_content_decoded
new file mode 100644
index 00000000..6e91338c
--- /dev/null
+++ b/pelican/tests/content/wordpress_content_decoded
@@ -0,0 +1,48 @@
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+ a = [1, 2, 3]
+ b = [4, 5, 6]
+ for i in zip(a, b):
+ print i
+
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
diff --git a/pelican/tests/content/wordpress_content_encoded b/pelican/tests/content/wordpress_content_encoded
new file mode 100644
index 00000000..da35de3b
--- /dev/null
+++ b/pelican/tests/content/wordpress_content_encoded
@@ -0,0 +1,55 @@
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+ a = [1, 2, 3]
+ b = [4, 5, 6]
+ for i in zip(a, b):
+ print i
+
+
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
diff --git a/pelican/tests/content/wordpressexport.xml b/pelican/tests/content/wordpressexport.xml
new file mode 100644
index 00000000..56d9a458
--- /dev/null
+++ b/pelican/tests/content/wordpressexport.xml
@@ -0,0 +1,686 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pelican test channel
+ http://thisisa.test
+ Not a real feed, just for test
+ Sun, 13 May 2012 01:13:52 +0000
+ en
+ 1.1
+ http://thisisa.test
+ http://thisisa.test
+
+ 2 Bob bob@thisisa.test
+ 3 Jonh jonh@thisisa.test
+
+ 7 categ-1
+ 11 categ-2
+ 1 uncategorized
+ 15 categ-3
+ 25 tag-1
+ 122 tag2
+ 68 tag-3
+
+ http://wordpress.org/?v=3.3.1
+
+ -
+
Empty post
+ http://thisisa.test/?attachment_id=24
+ Sat, 04 Feb 2012 03:17:33 +0000
+ bob
+ https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Pelican_lakes_entrance02.jpg/240px-Pelican_lakes_entrance02.jpg
+
+
+
+ 24
+ 2012-02-04 03:17:33
+ 2012-02-04 03:17:33
+ open
+ open
+ empty-post
+ inherit
+ 0
+ 0
+ attachment
+
+ 0
+ https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Pelican_lakes_entrance02.jpg/240px-Pelican_lakes_entrance02.jpg
+
+ _wp_attachment_metadata
+
+
+
+ _wp_attached_file
+
+
+
+ _wp_attachment_image_alt
+
+
+
+ -
+
+ http://thisisa.test/?p=168
+ Thu, 01 Jan 1970 00:00:00 +0000
+ bob
+ http://thisisa.test/?p=168
+
+
+
+ 168
+ 2012-02-15 21:23:57
+ 0000-00-00 00:00:00
+ open
+ open
+
+ draft
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+ -
+
A normal post
+ http://thisisa.test/?p=174
+ Thu, 01 Jan 1970 00:00:00 +0000
+ bob
+ http://thisisa.test/?p=174
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]>
+
+ 174
+ 2012-02-16 15:52:55
+ 0000-00-00 00:00:00
+ open
+ open
+
+ draft
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+ -
+
Complete draft
+ http://thisisa.test/?p=176
+ Thu, 01 Jan 1970 00:00:00 +0000
+ bob
+ http://thisisa.test/?p=176
+
+
+
+ 176
+ 2012-02-17 15:11:55
+ 0000-00-00 00:00:00
+ open
+ open
+
+ draft
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+ -
+
Page
+ http://thisisa.test/contact/
+ Wed, 11 Apr 2012 11:38:08 +0000
+ bob
+ http://thisisa.test/?page_id=334
+
+
+
+ 334
+ 2012-04-11 06:38:08
+ 2012-04-11 11:38:08
+ open
+ open
+ contact
+ publish
+ 0
+ 0
+ page
+
+ 0
+
+ sharing_disabled
+
+
+
+ _wp_page_template
+
+
+
+ _edit_last
+
+
+
+ -
+
Empty Page
+ http://thisisa.test/empty/
+ Wed, 11 Apr 2012 11:38:08 +0000
+ bob
+ http://thisisa.test/?page_id=334
+
+
+
+ 334
+ 2012-04-11 06:38:08
+ 2012-04-11 11:38:08
+ open
+ open
+ empty
+ publish
+ 0
+ 0
+ page
+
+ 0
+
+ sharing_disabled
+
+
+
+ _wp_page_template
+
+
+
+ _edit_last
+
+
+
+ -
+
Special chars: l'é
+ http://thisisa.test/?p=471
+ Thu, 01 Jan 1970 00:00:00 +0000
+ bob
+ http://thisisa.test/?p=471
+
+
+
+ 471
+ 2012-04-29 09:44:27
+ 0000-00-00 00:00:00
+ open
+ open
+
+ draft
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+
+ -
+
With excerpt
+ http://thisisa.test/with-excerpt/
+ Sat, 04 Feb 2012 02:03:06 +0000
+ bob
+ http://thisisa.test/?p=8
+
+
+
+ 8
+ 2012-02-04 02:03:06
+ 2012-02-04 02:03:06
+ open
+ open
+ with-excerpt
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+
+
+ _edit_last
+
+
+
+ et_bigpost
+
+
+
+ _thumbnail_id
+
+
+
+ -
+
With tags
+ http://thisisa.test/tags/
+ Sat, 04 Feb 2012 21:05:25 +0000
+ bob
+ http://thisisa.test/?p=25
+
+
+
+ 25
+ 2012-02-04 21:05:25
+ 2012-02-04 21:05:25
+ open
+ open
+ with-tags
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+
+
+
+ _edit_last
+
+
+
+ et_bigpost
+
+
+
+ _thumbnail_id
+
+
+
+ -
+
With comments
+ http://thisisa.test/with-comments/
+ Wed, 18 Apr 2012 08:36:26 +0000
+ john
+ http://thisisa.test/?p=422
+
+
+
+ 422
+ 2012-04-18 03:36:26
+ 2012-04-18 08:36:26
+ open
+ open
+ with-comments
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+ _thumbnail_id
+
+
+
+ 116
+
+ User2@mail.test
+
+ 127.0.0.1
+ 2012-05-06 15:46:06
+ 2012-05-06 20:46:06
+
+ 1
+
+ 0
+ 0
+
+ akismet_result
+
+
+
+ akismet_history
+
+
+
+ akismet_as_submitted
+
+
+
+
+ 117
+
+ bob@thisisa.test
+
+ 127.0.0.1
+ 2012-05-06 17:44:06
+ 2012-05-06 22:44:06
+
+ 1
+
+ 116
+ 3
+
+ akismet_result
+
+
+
+ akismet_history
+
+
+
+ akismet_as_submitted
+
+
+
+
+ 156
+
+
+ http://thisisa.test/to-article-you-ping-back/
+ 127.0.0.1
+ 2012-05-09 19:30:19
+ 2012-05-10 00:30:19
+
+ trash
+ pingback
+ 0
+ 0
+
+ akismet_history
+
+
+
+ _wp_trash_meta_status
+
+
+
+ _wp_trash_meta_time
+
+
+
+
+ 122
+
+ bob@thisisa.test
+
+ 127.0.0.1
+ 2012-05-07 14:11:34
+ 2012-05-07 19:11:34
+
+ 1
+
+ 121
+ 3
+
+ akismet_result
+
+
+
+ akismet_history
+
+
+
+ akismet_as_submitted
+
+
+
+
+ -
+
Post with raw data
+ http://thisisa.test/?p=173
+ Thu, 01 Jan 1970 00:00:00 +0000
+ bob
+ http://thisisa.test/?p=173
+
+ Pelicans are scary
+
+Pelicans are supposed to eat fish, damn it!
+
+VIDEO
+
+Bottom line: don't mess up with birds]]>
+
+ 173
+ 2012-02-16 15:52:55
+ 0000-00-00 00:00:00
+ open
+ open
+ post-with-raw-data
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+ -
+
A normal post with some <html> entities in the title. You can't miss them.
+ http://thisisa.test/?p=175
+ Thu, 01 Jan 1970 00:00:00 +0000
+ bob
+ http://thisisa.test/?p=175
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]>
+
+ 175
+ 2012-02-16 15:52:55
+ 0000-00-00 00:00:00
+ open
+ open
+ html-entity-test
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+ -
+
Code in List
+ http://thisisa.test/?p=175
+ Thu, 01 Jan 1970 00:00:00 +0000
+ bob
+ http://thisisa.test/?p=175
+
+
+ List Item One!
+ List Item Two!
+This is a code sample
+
+
+ a = [1, 2, 3]
+ b = [4, 5, 6]
+ for i in zip(a, b):
+ print i
+
+
+ List Item Four!
+
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]>
+
+ 175
+ 2012-02-16 15:52:55
+ 0000-00-00 00:00:00
+ open
+ open
+ code-in-list-test
+ publish
+ 0
+ 0
+ post
+
+ 0
+
+
+ _edit_last
+
+
+
+
+
diff --git a/pelican/tests/default_conf.py b/pelican/tests/default_conf.py
new file mode 100644
index 00000000..80a990b5
--- /dev/null
+++ b/pelican/tests/default_conf.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+AUTHOR = 'Alexis Métaireau'
+SITENAME = "Alexis' log"
+SITEURL = 'http://blog.notmyidea.org'
+TIMEZONE = 'UTC'
+
+GITHUB_URL = 'http://github.com/ametaireau/'
+DISQUS_SITENAME = "blog-notmyidea"
+PDF_GENERATOR = False
+REVERSE_CATEGORY_ORDER = True
+LOCALE = ""
+DEFAULT_PAGINATION = 2
+
+FEED_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',
+ ]
+
+# foobar will not be used, because it's not in caps. All configuration keys
+# have to be in caps
+foobar = "barbaz"
diff --git a/pelican/tests/output/basic/a-markdown-powered-article.html b/pelican/tests/output/basic/a-markdown-powered-article.html
new file mode 100644
index 00000000..94b6e4ca
--- /dev/null
+++ b/pelican/tests/output/basic/a-markdown-powered-article.html
@@ -0,0 +1,68 @@
+
+
+
+
+ A markdown powered article
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/archives.html b/pelican/tests/output/basic/archives.html
new file mode 100644
index 00000000..57f4a57a
--- /dev/null
+++ b/pelican/tests/output/basic/archives.html
@@ -0,0 +1,69 @@
+
+
+
+
+ A Pelican Blog
+
+
+
+
+
+
+
+
+
+Archives for A Pelican Blog
+
+
+ Fri 30 November 2012
+ FILENAME_METADATA example
+ Wed 29 February 2012
+ Second article
+ Wed 20 April 2011
+ A markdown powered article
+ Thu 17 February 2011
+ Article 1
+ Thu 17 February 2011
+ Article 2
+ Thu 17 February 2011
+ Article 3
+ Thu 02 December 2010
+ This is a super article !
+ Wed 20 October 2010
+ Oh yeah !
+ Fri 15 October 2010
+ Unbelievable !
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/article-1.html b/pelican/tests/output/basic/article-1.html
new file mode 100644
index 00000000..cfe91009
--- /dev/null
+++ b/pelican/tests/output/basic/article-1.html
@@ -0,0 +1,67 @@
+
+
+
+
+ Article 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 1
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/article-2.html b/pelican/tests/output/basic/article-2.html
new file mode 100644
index 00000000..70f38852
--- /dev/null
+++ b/pelican/tests/output/basic/article-2.html
@@ -0,0 +1,67 @@
+
+
+
+
+ Article 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 2
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/article-3.html b/pelican/tests/output/basic/article-3.html
new file mode 100644
index 00000000..20e58fcc
--- /dev/null
+++ b/pelican/tests/output/basic/article-3.html
@@ -0,0 +1,67 @@
+
+
+
+
+ Article 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 3
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/author/alexis-metaireau.html b/pelican/tests/output/basic/author/alexis-metaireau.html
new file mode 100644
index 00000000..4c0321ad
--- /dev/null
+++ b/pelican/tests/output/basic/author/alexis-metaireau.html
@@ -0,0 +1,99 @@
+
+
+
+
+ A Pelican Blog - Alexis Métaireau
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
+
+
Why not ?
+
After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
+YEAH !
+
+
+
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Multi-line metadata should be supported
+as well as inline markup .
+
+
read more
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html
new file mode 100644
index 00000000..a5eb2621
--- /dev/null
+++ b/pelican/tests/output/basic/categories.html
@@ -0,0 +1,51 @@
+
+
+
+
+ A Pelican Blog
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/category/bar.html b/pelican/tests/output/basic/category/bar.html
new file mode 100644
index 00000000..6414bf7b
--- /dev/null
+++ b/pelican/tests/output/basic/category/bar.html
@@ -0,0 +1,70 @@
+
+
+
+
+ A Pelican Blog - bar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
+
+
Why not ?
+
After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
+YEAH !
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/category/cat1.html b/pelican/tests/output/basic/category/cat1.html
new file mode 100644
index 00000000..c8912fc2
--- /dev/null
+++ b/pelican/tests/output/basic/category/cat1.html
@@ -0,0 +1,134 @@
+
+
+
+
+ A Pelican Blog - cat1
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 1
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 2
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 3
+
+
read more
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/category/misc.html b/pelican/tests/output/basic/category/misc.html
new file mode 100644
index 00000000..c0722292
--- /dev/null
+++ b/pelican/tests/output/basic/category/misc.html
@@ -0,0 +1,119 @@
+
+
+
+
+ A Pelican Blog - misc
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 30 November 2012
+
+
+ In misc .
+
+ Some cool stuff!
+
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By babar
+
+celestine
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Fri 15 October 2010
+
+
+ In misc .
+
+ Or completely awesome. Depends the needs.
+
a root-relative link to markdown-article
+a file-relative link to markdown-article
+
+
read more
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/category/yeah.html b/pelican/tests/output/basic/category/yeah.html
new file mode 100644
index 00000000..3e61d850
--- /dev/null
+++ b/pelican/tests/output/basic/category/yeah.html
@@ -0,0 +1,76 @@
+
+
+
+
+ A Pelican Blog - yeah
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Some content here !
+
+
This is a simple title
+
And here comes the cool stuff .
+
+
+
+>>> from ipdb import set_trace
+>>> set_trace()
+
+
→ And now try with some utf8 hell: ééé
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/feeds/all-en.atom.xml b/pelican/tests/output/basic/feeds/all-en.atom.xml
new file mode 100644
index 00000000..d4be1694
--- /dev/null
+++ b/pelican/tests/output/basic/feeds/all-en.atom.xml
@@ -0,0 +1,32 @@
+
+A Pelican Blog / 2012-11-30T00:00:00Z FILENAME_METADATA example 2012-11-30T00:00:00Z tag:,2012-11-30:filename_metadata-example.html <p>Some cool stuff!</p>
+ Second article 2012-02-29T00:00:00Z babar
+
+celestine tag:,2012-02-29:second-article.html <p>This is some article, in english</p>
+ A markdown powered article 2011-04-20T00:00:00Z tag:,2011-04-20:a-markdown-powered-article.html <p>You're mutually oblivious.</p>
+<p><a href="/unbelievable.html">a root-relative link to unbelievable</a>
+<a href="/unbelievable.html">a file-relative link to unbelievable</a></p> Article 1 2011-02-17T00:00:00Z tag:,2011-02-17:article-1.html <p>Article 1</p>
+ Article 2 2011-02-17T00:00:00Z tag:,2011-02-17:article-2.html <p>Article 2</p>
+ Article 3 2011-02-17T00:00:00Z tag:,2011-02-17:article-3.html <p>Article 3</p>
+ This is a super article ! 2010-12-02T10:14:00Z Alexis Métaireau tag:,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">
+>>> from ipdb import set_trace
+>>> set_trace()
+</pre>
+<p>→ And now try with some utf8 hell: ééé</p>
+</div>
+ Oh yeah ! 2010-10-20T10:14:00Z Alexis Métaireau tag:,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>
+ Unbelievable ! 2010-10-15T20:30:00Z tag:,2010-10-15:unbelievable.html <p>Or completely awesome. Depends the needs.</p>
+<p><a class="reference external" href="/a-markdown-powered-article.html">a root-relative link to markdown-article</a>
+<a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p>
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/feeds/all-fr.atom.xml b/pelican/tests/output/basic/feeds/all-fr.atom.xml
new file mode 100644
index 00000000..98b9a681
--- /dev/null
+++ b/pelican/tests/output/basic/feeds/all-fr.atom.xml
@@ -0,0 +1,3 @@
+
+A Pelican Blog / 2012-02-29T00:00:00Z Deuxième article 2012-02-29T00:00:00Z tag:,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/basic/feeds/all.atom.xml b/pelican/tests/output/basic/feeds/all.atom.xml
new file mode 100644
index 00000000..b3bbd8d8
--- /dev/null
+++ b/pelican/tests/output/basic/feeds/all.atom.xml
@@ -0,0 +1,33 @@
+
+A Pelican Blog / 2012-11-30T00:00:00Z FILENAME_METADATA example 2012-11-30T00:00:00Z tag:,2012-11-30:filename_metadata-example.html <p>Some cool stuff!</p>
+ Second article 2012-02-29T00:00:00Z babar
+
+celestine tag:,2012-02-29:second-article.html <p>This is some article, in english</p>
+ Deuxième article 2012-02-29T00:00:00Z tag:,2012-02-29:second-article-fr.html <p>Ceci est un article, en français.</p>
+ A markdown powered article 2011-04-20T00:00:00Z tag:,2011-04-20:a-markdown-powered-article.html <p>You're mutually oblivious.</p>
+<p><a href="/unbelievable.html">a root-relative link to unbelievable</a>
+<a href="/unbelievable.html">a file-relative link to unbelievable</a></p> Article 1 2011-02-17T00:00:00Z tag:,2011-02-17:article-1.html <p>Article 1</p>
+ Article 2 2011-02-17T00:00:00Z tag:,2011-02-17:article-2.html <p>Article 2</p>
+ Article 3 2011-02-17T00:00:00Z tag:,2011-02-17:article-3.html <p>Article 3</p>
+ This is a super article ! 2010-12-02T10:14:00Z Alexis Métaireau tag:,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">
+>>> from ipdb import set_trace
+>>> set_trace()
+</pre>
+<p>→ And now try with some utf8 hell: ééé</p>
+</div>
+ Oh yeah ! 2010-10-20T10:14:00Z Alexis Métaireau tag:,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>
+ Unbelievable ! 2010-10-15T20:30:00Z tag:,2010-10-15:unbelievable.html <p>Or completely awesome. Depends the needs.</p>
+<p><a class="reference external" href="/a-markdown-powered-article.html">a root-relative link to markdown-article</a>
+<a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p>
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/feeds/bar.atom.xml b/pelican/tests/output/basic/feeds/bar.atom.xml
new file mode 100644
index 00000000..2c988122
--- /dev/null
+++ b/pelican/tests/output/basic/feeds/bar.atom.xml
@@ -0,0 +1,8 @@
+
+A Pelican Blog / 2010-10-20T10:14:00Z Oh yeah ! 2010-10-20T10:14:00Z Alexis Métaireau tag:,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/cat1.atom.xml b/pelican/tests/output/basic/feeds/cat1.atom.xml
new file mode 100644
index 00000000..2fa534aa
--- /dev/null
+++ b/pelican/tests/output/basic/feeds/cat1.atom.xml
@@ -0,0 +1,7 @@
+
+A Pelican Blog / 2011-04-20T00:00:00Z A markdown powered article 2011-04-20T00:00:00Z tag:,2011-04-20:a-markdown-powered-article.html <p>You're mutually oblivious.</p>
+<p><a href="/unbelievable.html">a root-relative link to unbelievable</a>
+<a href="/unbelievable.html">a file-relative link to unbelievable</a></p> Article 1 2011-02-17T00:00:00Z tag:,2011-02-17:article-1.html <p>Article 1</p>
+ Article 2 2011-02-17T00:00:00Z tag:,2011-02-17:article-2.html <p>Article 2</p>
+ Article 3 2011-02-17T00:00:00Z tag:,2011-02-17:article-3.html <p>Article 3</p>
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/feeds/misc.atom.xml b/pelican/tests/output/basic/feeds/misc.atom.xml
new file mode 100644
index 00000000..0c0044f6
--- /dev/null
+++ b/pelican/tests/output/basic/feeds/misc.atom.xml
@@ -0,0 +1,9 @@
+
+A Pelican Blog / 2012-11-30T00:00:00Z FILENAME_METADATA example 2012-11-30T00:00:00Z tag:,2012-11-30:filename_metadata-example.html <p>Some cool stuff!</p>
+ Second article 2012-02-29T00:00:00Z babar
+
+celestine tag:,2012-02-29:second-article.html <p>This is some article, in english</p>
+ Unbelievable ! 2010-10-15T20:30:00Z tag:,2010-10-15:unbelievable.html <p>Or completely awesome. Depends the needs.</p>
+<p><a class="reference external" href="/a-markdown-powered-article.html">a root-relative link to markdown-article</a>
+<a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p>
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/feeds/yeah.atom.xml b/pelican/tests/output/basic/feeds/yeah.atom.xml
new file mode 100644
index 00000000..78d1a2d0
--- /dev/null
+++ b/pelican/tests/output/basic/feeds/yeah.atom.xml
@@ -0,0 +1,14 @@
+
+A Pelican Blog / 2010-12-02T10:14:00Z This is a super article ! 2010-12-02T10:14:00Z Alexis Métaireau tag:,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">
+>>> from ipdb import set_trace
+>>> 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/basic/filename_metadata-example.html b/pelican/tests/output/basic/filename_metadata-example.html
new file mode 100644
index 00000000..3092742b
--- /dev/null
+++ b/pelican/tests/output/basic/filename_metadata-example.html
@@ -0,0 +1,67 @@
+
+
+
+
+ FILENAME_METADATA example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 30 November 2012
+
+
+ In misc .
+
+ Some cool stuff!
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/index.html b/pelican/tests/output/basic/index.html
new file mode 100644
index 00000000..cbc3597f
--- /dev/null
+++ b/pelican/tests/output/basic/index.html
@@ -0,0 +1,262 @@
+
+
+
+
+ A Pelican Blog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 30 November 2012
+
+
+ In misc .
+
+ Some cool stuff!
+
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By babar
+
+celestine
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 1
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 2
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+ In cat1 .
+
+ Article 3
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Multi-line metadata should be supported
+as well as inline markup .
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
+
+
Why not ?
+
After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
+YEAH !
+
+
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Fri 15 October 2010
+
+
+ In misc .
+
+ Or completely awesome. Depends the needs.
+
a root-relative link to markdown-article
+a file-relative link to markdown-article
+
+
read more
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/oh-yeah.html b/pelican/tests/output/basic/oh-yeah.html
new file mode 100644
index 00000000..9c2ca876
--- /dev/null
+++ b/pelican/tests/output/basic/oh-yeah.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Oh yeah !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
+
+
Why not ?
+
After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
+YEAH !
+
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/override/index.html b/pelican/tests/output/basic/override/index.html
new file mode 100644
index 00000000..e75c75f7
--- /dev/null
+++ b/pelican/tests/output/basic/override/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+ Override url/save_as
+
+
+
+
+
+
+
+
+
+
+ Override url/save_as
+
+ Test page which overrides save_as and url so that this page will be generated
+at a custom location.
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html
new file mode 100644
index 00000000..6b5ae4c8
--- /dev/null
+++ b/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html
@@ -0,0 +1,53 @@
+
+
+
+
+ This is a test hidden page
+
+
+
+
+
+
+
+
+
+
+ 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!
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/pages/this-is-a-test-page.html b/pelican/tests/output/basic/pages/this-is-a-test-page.html
new file mode 100644
index 00000000..9f1572af
--- /dev/null
+++ b/pelican/tests/output/basic/pages/this-is-a-test-page.html
@@ -0,0 +1,53 @@
+
+
+
+
+ This is a test page
+
+
+
+
+
+
+
+
+
+
+ This is a test page
+
+ Just an image.
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/second-article-fr.html b/pelican/tests/output/basic/second-article-fr.html
new file mode 100644
index 00000000..f0c166de
--- /dev/null
+++ b/pelican/tests/output/basic/second-article-fr.html
@@ -0,0 +1,69 @@
+
+
+
+
+ Deuxième article
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+ In misc .
+tags: foo bar baz
Translations:
+ en
+
+ Ceci est un article, en français.
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/second-article.html b/pelican/tests/output/basic/second-article.html
new file mode 100644
index 00000000..8f04a4e5
--- /dev/null
+++ b/pelican/tests/output/basic/second-article.html
@@ -0,0 +1,73 @@
+
+
+
+
+ Second article
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By babar
+
+celestine
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/tag/bar.html b/pelican/tests/output/basic/tag/bar.html
new file mode 100644
index 00000000..f0179152
--- /dev/null
+++ b/pelican/tests/output/basic/tag/bar.html
@@ -0,0 +1,127 @@
+
+
+
+
+ A Pelican Blog - bar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By babar
+
+celestine
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Multi-line metadata should be supported
+as well as inline markup .
+
+
read more
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
+
+
Why not ?
+
After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
+YEAH !
+
+
+
+
read more
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/tag/baz.html b/pelican/tests/output/basic/tag/baz.html
new file mode 100644
index 00000000..912f0d97
--- /dev/null
+++ b/pelican/tests/output/basic/tag/baz.html
@@ -0,0 +1,69 @@
+
+
+
+
+ A Pelican Blog - baz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By babar
+
+celestine
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/tag/foo.html b/pelican/tests/output/basic/tag/foo.html
new file mode 100644
index 00000000..f1aa7fb1
--- /dev/null
+++ b/pelican/tests/output/basic/tag/foo.html
@@ -0,0 +1,98 @@
+
+
+
+
+ A Pelican Blog - foo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By babar
+
+celestine
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Multi-line metadata should be supported
+as well as inline markup .
+
+
read more
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/tag/foobar.html b/pelican/tests/output/basic/tag/foobar.html
new file mode 100644
index 00000000..24d5e21d
--- /dev/null
+++ b/pelican/tests/output/basic/tag/foobar.html
@@ -0,0 +1,76 @@
+
+
+
+
+ A Pelican Blog - foobar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Some content here !
+
+
This is a simple title
+
And here comes the cool stuff .
+
+
+
+>>> from ipdb import set_trace
+>>> set_trace()
+
+
→ And now try with some utf8 hell: ééé
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/tag/oh.html b/pelican/tests/output/basic/tag/oh.html
new file mode 100644
index 00000000..e4da0f93
--- /dev/null
+++ b/pelican/tests/output/basic/tag/oh.html
@@ -0,0 +1,70 @@
+
+
+
+
+ A Pelican Blog - oh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
+
+
Why not ?
+
After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
+YEAH !
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/tag/yeah.html b/pelican/tests/output/basic/tag/yeah.html
new file mode 100644
index 00000000..e3c3a1c6
--- /dev/null
+++ b/pelican/tests/output/basic/tag/yeah.html
@@ -0,0 +1,70 @@
+
+
+
+
+ A Pelican Blog - yeah
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
+
+
Why not ?
+
After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
+YEAH !
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/tags.html b/pelican/tests/output/basic/tags.html
new file mode 100644
index 00000000..e69de29b
diff --git a/pelican/tests/output/basic/theme/css/main.css b/pelican/tests/output/basic/theme/css/main.css
new file mode 100644
index 00000000..7f4ca363
--- /dev/null
+++ b/pelican/tests/output/basic/theme/css/main.css
@@ -0,0 +1,447 @@
+/*
+ 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');}
+ .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*='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/basic/theme/css/pygment.css b/pelican/tests/output/basic/theme/css/pygment.css
new file mode 100644
index 00000000..fdd056f6
--- /dev/null
+++ b/pelican/tests/output/basic/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/basic/theme/css/reset.css b/pelican/tests/output/basic/theme/css/reset.css
new file mode 100644
index 00000000..1e217566
--- /dev/null
+++ b/pelican/tests/output/basic/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/basic/theme/css/typogrify.css b/pelican/tests/output/basic/theme/css/typogrify.css
new file mode 100644
index 00000000..c9b34dc8
--- /dev/null
+++ b/pelican/tests/output/basic/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/basic/theme/css/wide.css b/pelican/tests/output/basic/theme/css/wide.css
new file mode 100644
index 00000000..88fd59ce
--- /dev/null
+++ b/pelican/tests/output/basic/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/basic/theme/images/icons/aboutme.png b/pelican/tests/output/basic/theme/images/icons/aboutme.png
new file mode 100644
index 00000000..9609df3b
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/aboutme.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/bitbucket.png b/pelican/tests/output/basic/theme/images/icons/bitbucket.png
new file mode 100644
index 00000000..d05ba161
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/bitbucket.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/delicious.png b/pelican/tests/output/basic/theme/images/icons/delicious.png
new file mode 100644
index 00000000..3dccdd84
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/delicious.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/facebook.png b/pelican/tests/output/basic/theme/images/icons/facebook.png
new file mode 100644
index 00000000..74e7ad52
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/facebook.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/github.png b/pelican/tests/output/basic/theme/images/icons/github.png
new file mode 100644
index 00000000..6c52b486
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/github.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/gitorious.png b/pelican/tests/output/basic/theme/images/icons/gitorious.png
new file mode 100644
index 00000000..3eeb3ece
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/gitorious.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/gittip.png b/pelican/tests/output/basic/theme/images/icons/gittip.png
new file mode 100644
index 00000000..af949625
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/gittip.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/google-groups.png b/pelican/tests/output/basic/theme/images/icons/google-groups.png
new file mode 100644
index 00000000..5de15e68
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/google-groups.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/google-plus.png b/pelican/tests/output/basic/theme/images/icons/google-plus.png
new file mode 100644
index 00000000..3c6b7432
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/google-plus.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/hackernews.png b/pelican/tests/output/basic/theme/images/icons/hackernews.png
new file mode 100644
index 00000000..fc7a82d4
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/hackernews.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/lastfm.png b/pelican/tests/output/basic/theme/images/icons/lastfm.png
new file mode 100644
index 00000000..3a6c6262
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/lastfm.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/linkedin.png b/pelican/tests/output/basic/theme/images/icons/linkedin.png
new file mode 100644
index 00000000..d29c1201
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/linkedin.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/reddit.png b/pelican/tests/output/basic/theme/images/icons/reddit.png
new file mode 100644
index 00000000..71ae1215
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/reddit.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/rss.png b/pelican/tests/output/basic/theme/images/icons/rss.png
new file mode 100644
index 00000000..7862c65a
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/rss.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/slideshare.png b/pelican/tests/output/basic/theme/images/icons/slideshare.png
new file mode 100644
index 00000000..ecc97410
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/slideshare.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/speakerdeck.png b/pelican/tests/output/basic/theme/images/icons/speakerdeck.png
new file mode 100644
index 00000000..087d0931
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/speakerdeck.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/twitter.png b/pelican/tests/output/basic/theme/images/icons/twitter.png
new file mode 100644
index 00000000..d0ef3cc1
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/twitter.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/vimeo.png b/pelican/tests/output/basic/theme/images/icons/vimeo.png
new file mode 100644
index 00000000..dba47202
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/vimeo.png differ
diff --git a/pelican/tests/output/basic/theme/images/icons/youtube.png b/pelican/tests/output/basic/theme/images/icons/youtube.png
new file mode 100644
index 00000000..ce6cbe4f
Binary files /dev/null and b/pelican/tests/output/basic/theme/images/icons/youtube.png differ
diff --git a/pelican/tests/output/basic/this-is-a-super-article.html b/pelican/tests/output/basic/this-is-a-super-article.html
new file mode 100644
index 00000000..e65f37e3
--- /dev/null
+++ b/pelican/tests/output/basic/this-is-a-super-article.html
@@ -0,0 +1,80 @@
+
+
+
+
+ This is a super article !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Some content here !
+
+
This is a simple title
+
And here comes the cool stuff .
+
+
+
+>>> from ipdb import set_trace
+>>> set_trace()
+
+
→ And now try with some utf8 hell: ééé
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/basic/unbelievable.html b/pelican/tests/output/basic/unbelievable.html
new file mode 100644
index 00000000..73c95423
--- /dev/null
+++ b/pelican/tests/output/basic/unbelievable.html
@@ -0,0 +1,69 @@
+
+
+
+
+ Unbelievable !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 15 October 2010
+
+
+ In misc .
+
+ Or completely awesome. Depends the needs.
+
a root-relative link to markdown-article
+a file-relative link to markdown-article
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/a-markdown-powered-article.html b/pelican/tests/output/custom/a-markdown-powered-article.html
new file mode 100644
index 00000000..6fbb116f
--- /dev/null
+++ b/pelican/tests/output/custom/a-markdown-powered-article.html
@@ -0,0 +1,111 @@
+
+
+
+
+ A markdown powered article
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/archives.html b/pelican/tests/output/custom/archives.html
new file mode 100644
index 00000000..8437e350
--- /dev/null
+++ b/pelican/tests/output/custom/archives.html
@@ -0,0 +1,97 @@
+
+
+
+
+ Alexis' log
+
+
+
+
+
+
+
+
+
+
+
+
+
+Archives for Alexis' log
+
+
+ Fri 30 November 2012
+ FILENAME_METADATA example
+ Wed 29 February 2012
+ Second article
+ Wed 20 April 2011
+ A markdown powered article
+ Thu 17 February 2011
+ Article 1
+ Thu 17 February 2011
+ Article 2
+ Thu 17 February 2011
+ Article 3
+ Thu 02 December 2010
+ This is a super article !
+ Wed 20 October 2010
+ Oh yeah !
+ Fri 15 October 2010
+ Unbelievable !
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/article-1.html b/pelican/tests/output/custom/article-1.html
new file mode 100644
index 00000000..38d0efb5
--- /dev/null
+++ b/pelican/tests/output/custom/article-1.html
@@ -0,0 +1,110 @@
+
+
+
+
+ Article 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 1
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/article-2.html b/pelican/tests/output/custom/article-2.html
new file mode 100644
index 00000000..cf7d3f6a
--- /dev/null
+++ b/pelican/tests/output/custom/article-2.html
@@ -0,0 +1,110 @@
+
+
+
+
+ Article 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 2
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/article-3.html b/pelican/tests/output/custom/article-3.html
new file mode 100644
index 00000000..da73f06f
--- /dev/null
+++ b/pelican/tests/output/custom/article-3.html
@@ -0,0 +1,110 @@
+
+
+
+
+ Article 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 3
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/author/alexis-metaireau.html b/pelican/tests/output/custom/author/alexis-metaireau.html
new file mode 100644
index 00000000..7dcfc87e
--- /dev/null
+++ b/pelican/tests/output/custom/author/alexis-metaireau.html
@@ -0,0 +1,174 @@
+
+
+
+
+ Alexis' log - Alexis Métaireau
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 1
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 2
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 3
+
+
read more
+
There are comments .
+
+
+
+ Page 1 / 2
+ »
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/author/alexis-metaireau2.html b/pelican/tests/output/custom/author/alexis-metaireau2.html
new file mode 100644
index 00000000..40fc46f9
--- /dev/null
+++ b/pelican/tests/output/custom/author/alexis-metaireau2.html
@@ -0,0 +1,188 @@
+
+
+
+
+ Alexis' log - Alexis Métaireau
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 30 November 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+
+ Some cool stuff!
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
Translations:
+ fr
+
+
+
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 !
+
+
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Multi-line metadata should be supported
+as well as inline markup .
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+
+ «
+ Page 2 / 2
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/author/alexis-metaireau3.html b/pelican/tests/output/custom/author/alexis-metaireau3.html
new file mode 100644
index 00000000..1966b99a
--- /dev/null
+++ b/pelican/tests/output/custom/author/alexis-metaireau3.html
@@ -0,0 +1,108 @@
+
+
+
+
+ Alexis' log - Alexis Métaireau
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ «
+ Page 3 / 3
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/categories.html b/pelican/tests/output/custom/categories.html
new file mode 100644
index 00000000..146d7f3a
--- /dev/null
+++ b/pelican/tests/output/custom/categories.html
@@ -0,0 +1,79 @@
+
+
+
+
+ Alexis' log
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/category/bar.html b/pelican/tests/output/custom/category/bar.html
new file mode 100644
index 00000000..24bd7d5e
--- /dev/null
+++ b/pelican/tests/output/custom/category/bar.html
@@ -0,0 +1,103 @@
+
+
+
+
+ Alexis' log - bar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
Translations:
+ fr
+
+
+
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 !
+
+
+There are comments .
+
+ Page 1 / 1
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/category/cat1.html b/pelican/tests/output/custom/category/cat1.html
new file mode 100644
index 00000000..2a9af66d
--- /dev/null
+++ b/pelican/tests/output/custom/category/cat1.html
@@ -0,0 +1,173 @@
+
+
+
+
+ Alexis' log - cat1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 1
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 2
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 3
+
+
read more
+
There are comments .
+
+
+
+ Page 1 / 1
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/category/misc.html b/pelican/tests/output/custom/category/misc.html
new file mode 100644
index 00000000..d815aa44
--- /dev/null
+++ b/pelican/tests/output/custom/category/misc.html
@@ -0,0 +1,152 @@
+
+
+
+
+ Alexis' log - misc
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 30 November 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+
+ Some cool stuff!
+There are comments .
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+
+ Page 1 / 1
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/category/yeah.html b/pelican/tests/output/custom/category/yeah.html
new file mode 100644
index 00000000..452a5c44
--- /dev/null
+++ b/pelican/tests/output/custom/category/yeah.html
@@ -0,0 +1,107 @@
+
+
+
+
+ Alexis' log - yeah
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Some content here !
+
+
This is a simple title
+
And here comes the cool stuff .
+
+
+
+>>> from ipdb import set_trace
+>>> set_trace()
+
+
→ And now try with some utf8 hell: ééé
+
+There are comments .
+
+ Page 1 / 1
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/drafts/a-draft-article.html b/pelican/tests/output/custom/drafts/a-draft-article.html
new file mode 100644
index 00000000..af9b0620
--- /dev/null
+++ b/pelican/tests/output/custom/drafts/a-draft-article.html
@@ -0,0 +1,98 @@
+
+
+
+
+ A draft article
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 02 March 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+
+ This is a draft article, it should live under the /drafts/ folder and not be
+listed anywhere else.
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/all-en.atom.xml b/pelican/tests/output/custom/feeds/all-en.atom.xml
new file mode 100644
index 00000000..49d45cde
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/all-en.atom.xml
@@ -0,0 +1,30 @@
+
+Alexis' log http://blog.notmyidea.org/ 2012-11-30T00:00:00+01:00 FILENAME_METADATA example 2012-11-30T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-11-30:filename_metadata-example.html <p>Some cool stuff!</p>
+ Second article 2012-02-29T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-02-29:second-article.html <p>This is some article, in english</p>
+ A markdown powered article 2011-04-20T00:00:00+02:00 Alexis Métaireau tag: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 1 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-1.html <p>Article 1</p>
+ Article 2 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-2.html <p>Article 2</p>
+ Article 3 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-3.html <p>Article 3</p>
+ This is a super article ! 2010-12-02T10:14:00+01:00 Alexis Métaireau tag: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">
+>>> from ipdb import set_trace
+>>> set_trace()
+</pre>
+<p>→ And now try with some utf8 hell: ééé</p>
+</div>
+ Oh yeah ! 2010-10-20T10:14:00+02:00 Alexis Métaireau tag: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:00 Alexis Métaireau tag: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>
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/all-fr.atom.xml b/pelican/tests/output/custom/feeds/all-fr.atom.xml
new file mode 100644
index 00000000..5d58742c
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/all-fr.atom.xml
@@ -0,0 +1,4 @@
+
+Alexis' log http://blog.notmyidea.org/ 2012-03-02T14:01:01+01:00 Trop bien ! 2012-03-02T14:01:01+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-03-02:oh-yeah-fr.html <p>Et voila du contenu en français</p>
+ Deuxième article 2012-02-29T00:00:00+01:00 Alexis Métaireau tag: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/feeds/all.atom.xml b/pelican/tests/output/custom/feeds/all.atom.xml
new file mode 100644
index 00000000..3187c2aa
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/all.atom.xml
@@ -0,0 +1,32 @@
+
+Alexis' log http://blog.notmyidea.org/ 2012-11-30T00:00:00+01:00 FILENAME_METADATA example 2012-11-30T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-11-30:filename_metadata-example.html <p>Some cool stuff!</p>
+ Trop bien ! 2012-03-02T14:01:01+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-03-02:oh-yeah-fr.html <p>Et voila du contenu en français</p>
+ Second article 2012-02-29T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-02-29:second-article.html <p>This is some article, in english</p>
+ Deuxième article 2012-02-29T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-02-29:second-article-fr.html <p>Ceci est un article, en français.</p>
+ A markdown powered article 2011-04-20T00:00:00+02:00 Alexis Métaireau tag: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 1 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-1.html <p>Article 1</p>
+ Article 2 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-2.html <p>Article 2</p>
+ Article 3 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-3.html <p>Article 3</p>
+ This is a super article ! 2010-12-02T10:14:00+01:00 Alexis Métaireau tag: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">
+>>> from ipdb import set_trace
+>>> set_trace()
+</pre>
+<p>→ And now try with some utf8 hell: ééé</p>
+</div>
+ Oh yeah ! 2010-10-20T10:14:00+02:00 Alexis Métaireau tag: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:00 Alexis Métaireau tag: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>
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/all.rss.xml b/pelican/tests/output/custom/feeds/all.rss.xml
new file mode 100644
index 00000000..8d07bec7
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/all.rss.xml
@@ -0,0 +1,32 @@
+
+Alexis' log http://blog.notmyidea.org/Fri, 30 Nov 2012 00:00:00 +0100 FILENAME_METADATA example http://blog.notmyidea.org/filename_metadata-example.html<p>Some cool stuff!</p>
+ Alexis Métaireau Fri, 30 Nov 2012 00:00:00 +0100 tag:blog.notmyidea.org,2012-11-30:filename_metadata-example.html Trop bien ! http://blog.notmyidea.org/oh-yeah-fr.html<p>Et voila du contenu en français</p>
+ Alexis Métaireau Fri, 02 Mar 2012 14:01:01 +0100 tag:blog.notmyidea.org,2012-03-02:oh-yeah-fr.html Second article http://blog.notmyidea.org/second-article.html<p>This is some article, in english</p>
+ Alexis Métaireau Wed, 29 Feb 2012 00:00:00 +0100 tag:blog.notmyidea.org,2012-02-29:second-article.html foo bar baz Deuxième article http://blog.notmyidea.org/second-article-fr.html<p>Ceci est un article, en français.</p>
+ Alexis Métaireau Wed, 29 Feb 2012 00:00:00 +0100 tag:blog.notmyidea.org,2012-02-29:second-article-fr.html foo bar baz A markdown powered article http://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étaireau Wed, 20 Apr 2011 00:00:00 +0200 tag:blog.notmyidea.org,2011-04-20:a-markdown-powered-article.html Article 1 http://blog.notmyidea.org/article-1.html<p>Article 1</p>
+ Alexis Métaireau Thu, 17 Feb 2011 00:00:00 +0100 tag:blog.notmyidea.org,2011-02-17:article-1.html Article 2 http://blog.notmyidea.org/article-2.html<p>Article 2</p>
+ Alexis Métaireau Thu, 17 Feb 2011 00:00:00 +0100 tag:blog.notmyidea.org,2011-02-17:article-2.html Article 3 http://blog.notmyidea.org/article-3.html<p>Article 3</p>
+ Alexis Métaireau Thu, 17 Feb 2011 00:00:00 +0100 tag:blog.notmyidea.org,2011-02-17:article-3.html This 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">
+>>> from ipdb import set_trace
+>>> set_trace()
+</pre>
+<p>→ And now try with some utf8 hell: ééé</p>
+</div>
+ Alexis Métaireau Thu, 02 Dec 2010 10:14:00 +0100 tag:blog.notmyidea.org,2010-12-02:this-is-a-super-article.html foo bar foobar Oh 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étaireau Wed, 20 Oct 2010 10:14:00 +0200 tag:blog.notmyidea.org,2010-10-20:oh-yeah.html oh bar yeah Unbelievable ! 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>
+ Alexis Métaireau Fri, 15 Oct 2010 20:30:00 +0200 tag:blog.notmyidea.org,2010-10-15:unbelievable.html
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/bar.atom.xml b/pelican/tests/output/custom/feeds/bar.atom.xml
new file mode 100644
index 00000000..99b7cc45
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/bar.atom.xml
@@ -0,0 +1,8 @@
+
+Alexis' log http://blog.notmyidea.org/ 2010-10-20T10:14:00+02:00 Oh yeah ! 2010-10-20T10:14:00+02:00 Alexis Métaireau tag: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>
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/bar.rss.xml b/pelican/tests/output/custom/feeds/bar.rss.xml
new file mode 100644
index 00000000..94bd93f2
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/bar.rss.xml
@@ -0,0 +1,8 @@
+
+Alexis' log http://blog.notmyidea.org/Wed, 20 Oct 2010 10:14:00 +0200 Oh 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étaireau Wed, 20 Oct 2010 10:14:00 +0200 tag:blog.notmyidea.org,2010-10-20:oh-yeah.html oh bar yeah
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/cat1.atom.xml b/pelican/tests/output/custom/feeds/cat1.atom.xml
new file mode 100644
index 00000000..5454ce4d
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/cat1.atom.xml
@@ -0,0 +1,7 @@
+
+Alexis' log http://blog.notmyidea.org/ 2011-04-20T00:00:00+02:00 A markdown powered article 2011-04-20T00:00:00+02:00 Alexis Métaireau tag: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 1 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-1.html <p>Article 1</p>
+ Article 2 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-2.html <p>Article 2</p>
+ Article 3 2011-02-17T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2011-02-17:article-3.html <p>Article 3</p>
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/cat1.rss.xml b/pelican/tests/output/custom/feeds/cat1.rss.xml
new file mode 100644
index 00000000..62b93fd1
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/cat1.rss.xml
@@ -0,0 +1,7 @@
+
+Alexis' log http://blog.notmyidea.org/Wed, 20 Apr 2011 00:00:00 +0200 A markdown powered article http://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étaireau Wed, 20 Apr 2011 00:00:00 +0200 tag:blog.notmyidea.org,2011-04-20:a-markdown-powered-article.html Article 1 http://blog.notmyidea.org/article-1.html<p>Article 1</p>
+ Alexis Métaireau Thu, 17 Feb 2011 00:00:00 +0100 tag:blog.notmyidea.org,2011-02-17:article-1.html Article 2 http://blog.notmyidea.org/article-2.html<p>Article 2</p>
+ Alexis Métaireau Thu, 17 Feb 2011 00:00:00 +0100 tag:blog.notmyidea.org,2011-02-17:article-2.html Article 3 http://blog.notmyidea.org/article-3.html<p>Article 3</p>
+ Alexis Métaireau Thu, 17 Feb 2011 00:00:00 +0100 tag:blog.notmyidea.org,2011-02-17:article-3.html
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/misc.atom.xml b/pelican/tests/output/custom/feeds/misc.atom.xml
new file mode 100644
index 00000000..45c996f3
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/misc.atom.xml
@@ -0,0 +1,7 @@
+
+Alexis' log http://blog.notmyidea.org/ 2012-11-30T00:00:00+01:00 FILENAME_METADATA example 2012-11-30T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-11-30:filename_metadata-example.html <p>Some cool stuff!</p>
+ Second article 2012-02-29T00:00:00+01:00 Alexis Métaireau tag:blog.notmyidea.org,2012-02-29:second-article.html <p>This is some article, in english</p>
+ Unbelievable ! 2010-10-15T20:30:00+02:00 Alexis Métaireau tag: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>
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/misc.rss.xml b/pelican/tests/output/custom/feeds/misc.rss.xml
new file mode 100644
index 00000000..1d295abc
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/misc.rss.xml
@@ -0,0 +1,7 @@
+
+Alexis' log http://blog.notmyidea.org/Fri, 30 Nov 2012 00:00:00 +0100 FILENAME_METADATA example http://blog.notmyidea.org/filename_metadata-example.html<p>Some cool stuff!</p>
+ Alexis Métaireau Fri, 30 Nov 2012 00:00:00 +0100 tag:blog.notmyidea.org,2012-11-30:filename_metadata-example.html Second article http://blog.notmyidea.org/second-article.html<p>This is some article, in english</p>
+ Alexis Métaireau Wed, 29 Feb 2012 00:00:00 +0100 tag:blog.notmyidea.org,2012-02-29:second-article.html foo bar baz Unbelievable ! 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>
+ Alexis Métaireau Fri, 15 Oct 2010 20:30:00 +0200 tag:blog.notmyidea.org,2010-10-15:unbelievable.html
\ No newline at end of file
diff --git a/pelican/tests/output/custom/feeds/yeah.atom.xml b/pelican/tests/output/custom/feeds/yeah.atom.xml
new file mode 100644
index 00000000..1a152174
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/yeah.atom.xml
@@ -0,0 +1,14 @@
+
+Alexis' log http://blog.notmyidea.org/ 2010-12-02T10:14:00+01:00 This is a super article ! 2010-12-02T10:14:00+01:00 Alexis Métaireau tag: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">
+>>> from ipdb import set_trace
+>>> 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/feeds/yeah.rss.xml b/pelican/tests/output/custom/feeds/yeah.rss.xml
new file mode 100644
index 00000000..85f93686
--- /dev/null
+++ b/pelican/tests/output/custom/feeds/yeah.rss.xml
@@ -0,0 +1,14 @@
+
+Alexis' log http://blog.notmyidea.org/Thu, 02 Dec 2010 10:14:00 +0100 This 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">
+>>> from ipdb import set_trace
+>>> set_trace()
+</pre>
+<p>→ And now try with some utf8 hell: ééé</p>
+</div>
+ Alexis Métaireau Thu, 02 Dec 2010 10:14:00 +0100 tag:blog.notmyidea.org,2010-12-02:this-is-a-super-article.html foo bar foobar
\ No newline at end of file
diff --git a/pelican/tests/output/custom/filename_metadata-example.html b/pelican/tests/output/custom/filename_metadata-example.html
new file mode 100644
index 00000000..65b42d7f
--- /dev/null
+++ b/pelican/tests/output/custom/filename_metadata-example.html
@@ -0,0 +1,110 @@
+
+
+
+
+ FILENAME_METADATA example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 30 November 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+
+ Some cool stuff!
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/index.html b/pelican/tests/output/custom/index.html
new file mode 100644
index 00000000..1b084d49
--- /dev/null
+++ b/pelican/tests/output/custom/index.html
@@ -0,0 +1,176 @@
+
+
+
+
+ Alexis' log
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 30 November 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+
+ Some cool stuff!
+There are comments .
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 1
+
+
read more
+
There are comments .
+
+
+
+ Page 1 / 3
+ »
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/index2.html b/pelican/tests/output/custom/index2.html
new file mode 100644
index 00000000..52f0ecc6
--- /dev/null
+++ b/pelican/tests/output/custom/index2.html
@@ -0,0 +1,187 @@
+
+
+
+
+ Alexis' log
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 2
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Thu 17 February 2011
+
+
+
+ By Alexis Métaireau
+ In cat1 .
+
+ Article 3
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Multi-line metadata should be supported
+as well as inline markup .
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
Translations:
+ fr
+
+
+
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 !
+
+
+
+
read more
+
There are comments .
+
+
+
+ «
+ Page 2 / 3
+ »
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/index3.html b/pelican/tests/output/custom/index3.html
new file mode 100644
index 00000000..d0e537ba
--- /dev/null
+++ b/pelican/tests/output/custom/index3.html
@@ -0,0 +1,108 @@
+
+
+
+
+ Alexis' log
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ «
+ Page 3 / 3
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/jinja2_template.html b/pelican/tests/output/custom/jinja2_template.html
new file mode 100644
index 00000000..95077c2f
--- /dev/null
+++ b/pelican/tests/output/custom/jinja2_template.html
@@ -0,0 +1,76 @@
+
+
+
+
+ Alexis' log
+
+
+
+
+
+
+
+
+
+
+
+
+
+Some text
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/oh-yeah-fr.html b/pelican/tests/output/custom/oh-yeah-fr.html
new file mode 100644
index 00000000..9d2a764a
--- /dev/null
+++ b/pelican/tests/output/custom/oh-yeah-fr.html
@@ -0,0 +1,112 @@
+
+
+
+
+ Trop bien !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fri 02 March 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+Translations:
+ en
+
+ Et voila du contenu en français
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/oh-yeah.html b/pelican/tests/output/custom/oh-yeah.html
new file mode 100644
index 00000000..04db2171
--- /dev/null
+++ b/pelican/tests/output/custom/oh-yeah.html
@@ -0,0 +1,117 @@
+
+
+
+
+ Oh yeah !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
Translations:
+ fr
+
+
+
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 !
+
+
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/override/index.html b/pelican/tests/output/custom/override/index.html
new file mode 100644
index 00000000..ad9fb8b1
--- /dev/null
+++ b/pelican/tests/output/custom/override/index.html
@@ -0,0 +1,81 @@
+
+
+
+
+ Override url/save_as
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Override url/save_as
+
+ Test page which overrides save_as and url so that this page will be generated
+at a custom location.
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html
new file mode 100644
index 00000000..b4ac59b0
--- /dev/null
+++ b/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html
@@ -0,0 +1,81 @@
+
+
+
+
+ This is a test hidden page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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!
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/pages/this-is-a-test-page.html b/pelican/tests/output/custom/pages/this-is-a-test-page.html
new file mode 100644
index 00000000..b4cb678c
--- /dev/null
+++ b/pelican/tests/output/custom/pages/this-is-a-test-page.html
@@ -0,0 +1,81 @@
+
+
+
+
+ This is a test page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is a test page
+
+ Just an image.
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/pictures/Fat_Cat.jpg b/pelican/tests/output/custom/pictures/Fat_Cat.jpg
new file mode 100644
index 00000000..d8a96d35
Binary files /dev/null and b/pelican/tests/output/custom/pictures/Fat_Cat.jpg differ
diff --git a/pelican/tests/output/custom/pictures/Sushi.jpg b/pelican/tests/output/custom/pictures/Sushi.jpg
new file mode 100644
index 00000000..e49e5f0a
Binary files /dev/null and b/pelican/tests/output/custom/pictures/Sushi.jpg differ
diff --git a/pelican/tests/output/custom/pictures/Sushi_Macro.jpg b/pelican/tests/output/custom/pictures/Sushi_Macro.jpg
new file mode 100644
index 00000000..21f935a1
Binary files /dev/null and b/pelican/tests/output/custom/pictures/Sushi_Macro.jpg differ
diff --git a/pelican/tests/output/custom/robots.txt b/pelican/tests/output/custom/robots.txt
new file mode 100644
index 00000000..19a6e299
--- /dev/null
+++ b/pelican/tests/output/custom/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /pictures
diff --git a/pelican/tests/output/custom/second-article-fr.html b/pelican/tests/output/custom/second-article-fr.html
new file mode 100644
index 00000000..9021cdc1
--- /dev/null
+++ b/pelican/tests/output/custom/second-article-fr.html
@@ -0,0 +1,112 @@
+
+
+
+
+ Deuxième article
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+tags: foo bar baz
Translations:
+ en
+
+ Ceci est un article, en français.
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/second-article.html b/pelican/tests/output/custom/second-article.html
new file mode 100644
index 00000000..d0430d53
--- /dev/null
+++ b/pelican/tests/output/custom/second-article.html
@@ -0,0 +1,112 @@
+
+
+
+
+ Second article
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/tag/bar.html b/pelican/tests/output/custom/tag/bar.html
new file mode 100644
index 00000000..f9896c5e
--- /dev/null
+++ b/pelican/tests/output/custom/tag/bar.html
@@ -0,0 +1,158 @@
+
+
+
+
+ Alexis' log - bar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+There are comments .
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Multi-line metadata should be supported
+as well as inline markup .
+
+
read more
+
There are comments .
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
Translations:
+ fr
+
+
+
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 !
+
+
+
+
read more
+
There are comments .
+
+
+
+ Page 1 / 1
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/tag/baz.html b/pelican/tests/output/custom/tag/baz.html
new file mode 100644
index 00000000..df7dc92c
--- /dev/null
+++ b/pelican/tests/output/custom/tag/baz.html
@@ -0,0 +1,98 @@
+
+
+
+
+ Alexis' log - baz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+There are comments .
+
+ Page 1 / 1
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/tag/foo.html b/pelican/tests/output/custom/tag/foo.html
new file mode 100644
index 00000000..3daf7c68
--- /dev/null
+++ b/pelican/tests/output/custom/tag/foo.html
@@ -0,0 +1,127 @@
+
+
+
+
+ Alexis' log - foo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 29 February 2012
+
+
+
+ By Alexis Métaireau
+ In misc .
+tags: foo bar baz
Translations:
+ fr
+
+ This is some article, in english
+There are comments .
+
+
+ Other articles
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Multi-line metadata should be supported
+as well as inline markup .
+
+
read more
+
There are comments .
+
+
+
+ Page 1 / 1
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/tag/foobar.html b/pelican/tests/output/custom/tag/foobar.html
new file mode 100644
index 00000000..9184e3dd
--- /dev/null
+++ b/pelican/tests/output/custom/tag/foobar.html
@@ -0,0 +1,107 @@
+
+
+
+
+ Alexis' log - foobar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Some content here !
+
+
This is a simple title
+
And here comes the cool stuff .
+
+
+
+>>> from ipdb import set_trace
+>>> set_trace()
+
+
→ And now try with some utf8 hell: ééé
+
+There are comments .
+
+ Page 1 / 1
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/tag/oh.html b/pelican/tests/output/custom/tag/oh.html
new file mode 100644
index 00000000..901fe9ff
--- /dev/null
+++ b/pelican/tests/output/custom/tag/oh.html
@@ -0,0 +1,103 @@
+
+
+
+
+ Alexis' log - oh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
Translations:
+ fr
+
+
+
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 !
+
+
+There are comments .
+
+ Page 1 / 1
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/tag/yeah.html b/pelican/tests/output/custom/tag/yeah.html
new file mode 100644
index 00000000..d12788e0
--- /dev/null
+++ b/pelican/tests/output/custom/tag/yeah.html
@@ -0,0 +1,103 @@
+
+
+
+
+ Alexis' log - yeah
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wed 20 October 2010
+
+
+
+ By Alexis Métaireau
+ In bar .
+tags: oh bar yeah
Translations:
+ fr
+
+
+
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 !
+
+
+There are comments .
+
+ Page 1 / 1
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/tags.html b/pelican/tests/output/custom/tags.html
new file mode 100644
index 00000000..e69de29b
diff --git a/pelican/tests/output/custom/theme/css/main.css b/pelican/tests/output/custom/theme/css/main.css
new file mode 100644
index 00000000..7f4ca363
--- /dev/null
+++ b/pelican/tests/output/custom/theme/css/main.css
@@ -0,0 +1,447 @@
+/*
+ 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');}
+ .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*='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/theme/css/pygment.css b/pelican/tests/output/custom/theme/css/pygment.css
new file mode 100644
index 00000000..fdd056f6
--- /dev/null
+++ b/pelican/tests/output/custom/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/theme/css/reset.css b/pelican/tests/output/custom/theme/css/reset.css
new file mode 100644
index 00000000..1e217566
--- /dev/null
+++ b/pelican/tests/output/custom/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/theme/css/typogrify.css b/pelican/tests/output/custom/theme/css/typogrify.css
new file mode 100644
index 00000000..c9b34dc8
--- /dev/null
+++ b/pelican/tests/output/custom/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/theme/css/wide.css b/pelican/tests/output/custom/theme/css/wide.css
new file mode 100644
index 00000000..88fd59ce
--- /dev/null
+++ b/pelican/tests/output/custom/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/theme/images/icons/aboutme.png b/pelican/tests/output/custom/theme/images/icons/aboutme.png
new file mode 100644
index 00000000..9609df3b
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/aboutme.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/bitbucket.png b/pelican/tests/output/custom/theme/images/icons/bitbucket.png
new file mode 100644
index 00000000..d05ba161
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/bitbucket.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/delicious.png b/pelican/tests/output/custom/theme/images/icons/delicious.png
new file mode 100644
index 00000000..3dccdd84
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/delicious.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/facebook.png b/pelican/tests/output/custom/theme/images/icons/facebook.png
new file mode 100644
index 00000000..74e7ad52
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/facebook.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/github.png b/pelican/tests/output/custom/theme/images/icons/github.png
new file mode 100644
index 00000000..6c52b486
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/github.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/gitorious.png b/pelican/tests/output/custom/theme/images/icons/gitorious.png
new file mode 100644
index 00000000..3eeb3ece
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/gitorious.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/gittip.png b/pelican/tests/output/custom/theme/images/icons/gittip.png
new file mode 100644
index 00000000..af949625
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/gittip.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/google-groups.png b/pelican/tests/output/custom/theme/images/icons/google-groups.png
new file mode 100644
index 00000000..5de15e68
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/google-groups.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/google-plus.png b/pelican/tests/output/custom/theme/images/icons/google-plus.png
new file mode 100644
index 00000000..3c6b7432
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/google-plus.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/hackernews.png b/pelican/tests/output/custom/theme/images/icons/hackernews.png
new file mode 100644
index 00000000..fc7a82d4
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/hackernews.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/lastfm.png b/pelican/tests/output/custom/theme/images/icons/lastfm.png
new file mode 100644
index 00000000..3a6c6262
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/lastfm.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/linkedin.png b/pelican/tests/output/custom/theme/images/icons/linkedin.png
new file mode 100644
index 00000000..d29c1201
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/linkedin.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/reddit.png b/pelican/tests/output/custom/theme/images/icons/reddit.png
new file mode 100644
index 00000000..71ae1215
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/reddit.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/rss.png b/pelican/tests/output/custom/theme/images/icons/rss.png
new file mode 100644
index 00000000..7862c65a
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/rss.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/slideshare.png b/pelican/tests/output/custom/theme/images/icons/slideshare.png
new file mode 100644
index 00000000..ecc97410
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/slideshare.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/speakerdeck.png b/pelican/tests/output/custom/theme/images/icons/speakerdeck.png
new file mode 100644
index 00000000..087d0931
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/speakerdeck.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/twitter.png b/pelican/tests/output/custom/theme/images/icons/twitter.png
new file mode 100644
index 00000000..d0ef3cc1
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/twitter.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/vimeo.png b/pelican/tests/output/custom/theme/images/icons/vimeo.png
new file mode 100644
index 00000000..dba47202
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/vimeo.png differ
diff --git a/pelican/tests/output/custom/theme/images/icons/youtube.png b/pelican/tests/output/custom/theme/images/icons/youtube.png
new file mode 100644
index 00000000..ce6cbe4f
Binary files /dev/null and b/pelican/tests/output/custom/theme/images/icons/youtube.png differ
diff --git a/pelican/tests/output/custom/this-is-a-super-article.html b/pelican/tests/output/custom/this-is-a-super-article.html
new file mode 100644
index 00000000..5b1b1485
--- /dev/null
+++ b/pelican/tests/output/custom/this-is-a-super-article.html
@@ -0,0 +1,121 @@
+
+
+
+
+ This is a super article !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thu 02 December 2010
+
+
+
+ By Alexis Métaireau
+ In yeah .
+tags: foo bar foobar
+ Some content here !
+
+
This is a simple title
+
And here comes the cool stuff .
+
+
+
+>>> from ipdb import set_trace
+>>> set_trace()
+
+
→ And now try with some utf8 hell: ééé
+
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/output/custom/unbelievable.html b/pelican/tests/output/custom/unbelievable.html
new file mode 100644
index 00000000..d06dcb9e
--- /dev/null
+++ b/pelican/tests/output/custom/unbelievable.html
@@ -0,0 +1,112 @@
+
+
+
+
+ Unbelievable !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Proudly powered by Pelican , which takes great advantage of Python .
+
+
+ The theme is by Smashing Magazine , thanks!
+
+
+
+
+
\ No newline at end of file
diff --git a/pelican/tests/support.py b/pelican/tests/support.py
new file mode 100644
index 00000000..a4fd2c35
--- /dev/null
+++ b/pelican/tests/support.py
@@ -0,0 +1,205 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+__all__ = ['get_article', 'unittest', ]
+
+import os
+import re
+import subprocess
+import sys
+from six import StringIO
+import logging
+from logging.handlers import BufferingHandler
+import unittest
+import locale
+
+from functools import wraps
+from contextlib import contextmanager
+from tempfile import mkdtemp
+from shutil import rmtree
+
+from pelican.contents import Article
+from pelican.settings import DEFAULT_CONFIG
+
+
+@contextmanager
+def temporary_folder():
+ """creates a temporary folder, return it and delete it afterwards.
+
+ This allows to do something like this in tests:
+
+ >>> with temporary_folder() as d:
+ # do whatever you want
+ """
+ tempdir = mkdtemp()
+ try:
+ yield tempdir
+ finally:
+ rmtree(tempdir)
+
+
+def isplit(s, sep=None):
+ """Behaves like str.split but returns a generator instead of a list.
+
+ >>> list(isplit('\tUse the force\n')) == '\tUse the force\n'.split()
+ True
+ >>> list(isplit('\tUse the force\n')) == ['Use', 'the', 'force']
+ True
+ >>> (list(isplit('\tUse the force\n', "e"))
+ == '\tUse the force\n'.split("e"))
+ True
+ >>> list(isplit('Use the force', "e")) == 'Use the force'.split("e")
+ True
+ >>> list(isplit('Use the force', "e")) == ['Us', ' th', ' forc', '']
+ True
+
+ """
+ sep, hardsep = r'\s+' if sep is None else re.escape(sep), sep is not None
+ exp, pos, l = re.compile(sep), 0, len(s)
+ while True:
+ m = exp.search(s, pos)
+ if not m:
+ if pos < l or hardsep:
+ # ^ mimic "split()": ''.split() returns []
+ yield s[pos:]
+ break
+ start = m.start()
+ if pos < start or hardsep:
+ # ^ mimic "split()": includes trailing empty string
+ yield s[pos:start]
+ pos = m.end()
+
+
+def mute(returns_output=False):
+ """Decorate a function that prints to stdout, intercepting the output.
+ If "returns_output" is True, the function will return a generator
+ yielding the printed lines instead of the return values.
+
+ The decorator litterally hijack sys.stdout during each function
+ execution, so be careful with what you apply it to.
+
+ >>> def numbers():
+ print "42"
+ print "1984"
+ ...
+ >>> numbers()
+ 42
+ 1984
+ >>> mute()(numbers)()
+ >>> list(mute(True)(numbers)())
+ ['42', '1984']
+
+ """
+
+ def decorator(func):
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+
+ saved_stdout = sys.stdout
+ sys.stdout = StringIO()
+
+ try:
+ out = func(*args, **kwargs)
+ if returns_output:
+ out = isplit(sys.stdout.getvalue().strip())
+ finally:
+ sys.stdout = saved_stdout
+
+ return out
+
+ return wrapper
+
+ return decorator
+
+
+def get_article(title, slug, content, lang, extra_metadata=None):
+ metadata = {'slug': slug, 'title': title, 'lang': lang}
+ if extra_metadata is not None:
+ metadata.update(extra_metadata)
+ return Article(content, metadata=metadata)
+
+
+def skipIfNoExecutable(executable):
+ """Skip test if `executable` is not found
+
+ Tries to run `executable` with subprocess to make sure it's in the path,
+ and skips the tests if not found (if subprocess raises a `OSError`).
+ """
+
+ with open(os.devnull, 'w') as fnull:
+ try:
+ res = subprocess.call(executable, stdout=fnull, stderr=fnull)
+ except OSError:
+ res = None
+
+ if res is None:
+ return unittest.skip('{0} executable not found'.format(executable))
+
+ return lambda func: func
+
+
+def module_exists(module_name):
+ """Test if a module is importable."""
+
+ try:
+ __import__(module_name)
+ except ImportError:
+ return False
+ else:
+ return True
+
+
+def locale_available(locale_):
+ old_locale = locale.setlocale(locale.LC_TIME)
+
+ try:
+ locale.setlocale(locale.LC_TIME, str(locale_))
+ except locale.Error:
+ return False
+ else:
+ locale.setlocale(locale.LC_TIME, old_locale)
+ return True
+
+
+def get_settings(**kwargs):
+ """Provide tweaked setting dictionaries for testing
+
+ Set keyword arguments to override specific settings.
+ """
+ settings = DEFAULT_CONFIG.copy()
+ for key,value in kwargs.items():
+ settings[key] = value
+ return settings
+
+
+class LogCountHandler(BufferingHandler):
+ """Capturing and counting logged messages."""
+
+ def __init__(self, capacity=1000):
+ logging.handlers.BufferingHandler.__init__(self, capacity)
+
+ def count_logs(self, msg=None, level=None):
+ return len([l for l in self.buffer
+ if (msg is None or re.match(msg, l.getMessage()))
+ and (level is None or l.levelno == level)
+ ])
+
+
+class LoggedTestCase(unittest.TestCase):
+ """A test case that captures log messages."""
+
+ def setUp(self):
+ super(LoggedTestCase, self).setUp()
+ self._logcount_handler = LogCountHandler()
+ logging.getLogger().addHandler(self._logcount_handler)
+
+ def tearDown(self):
+ logging.getLogger().removeHandler(self._logcount_handler)
+ super(LoggedTestCase, self).tearDown()
+
+ def assertLogCountEqual(self, count=None, msg=None, **kwargs):
+ actual = self._logcount_handler.count_logs(msg=msg, **kwargs)
+ self.assertEqual(
+ actual, count,
+ msg='expected {} occurrences of {!r}, but found {}'.format(
+ count, msg, actual))
diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py
new file mode 100644
index 00000000..c081639d
--- /dev/null
+++ b/pelican/tests/test_contents.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from datetime import datetime
+from sys import platform
+
+from .support import unittest, get_settings
+
+from pelican.contents import Page, Article, URLWrapper
+from pelican.settings import DEFAULT_CONFIG
+from pelican.utils import truncate_html_words
+from pelican.signals import content_object_init
+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',
+ },
+ }
+
+ def test_use_args(self):
+ # Creating a page with arguments passed to the constructor should use
+ # them to initialise object's attributes.
+ metadata = {'foo': 'bar', 'foobar': 'baz', 'title': 'foobar', }
+ page = Page(TEST_CONTENT, metadata=metadata,
+ context={'localsiteurl': ''})
+ for key, value in metadata.items():
+ self.assertTrue(hasattr(page, key))
+ self.assertEqual(value, getattr(page, key))
+ self.assertEqual(page.content, TEST_CONTENT)
+
+ def test_mandatory_properties(self):
+ # If the title is not set, must throw an exception.
+ page = Page('content')
+ with self.assertRaises(NameError):
+ page.check_properties()
+
+ page = Page('content', metadata={'title': 'foobar'})
+ page.check_properties()
+
+ def test_summary_from_metadata(self):
+ # If a :summary: metadata is given, it should be used
+ page = Page(**self.page_kwargs)
+ self.assertEqual(page.summary, TEST_SUMMARY)
+
+ def test_summary_max_length(self):
+ # If a :SUMMARY_MAX_LENGTH: is set, and there is no other summary,
+ # generated summary should not exceed the given length.
+ page_kwargs = self._copy_page_kwargs()
+ settings = get_settings()
+ page_kwargs['settings'] = settings
+ del page_kwargs['metadata']['summary']
+ settings['SUMMARY_MAX_LENGTH'] = None
+ page = Page(**page_kwargs)
+ self.assertEqual(page.summary, TEST_CONTENT)
+ settings['SUMMARY_MAX_LENGTH'] = 10
+ page = Page(**page_kwargs)
+ self.assertEqual(page.summary, truncate_html_words(TEST_CONTENT, 10))
+ settings['SUMMARY_MAX_LENGTH'] = 0
+ page = Page(**page_kwargs)
+ 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)
+ self.assertEqual(page.slug, 'foo-bar')
+
+ def test_defaultlang(self):
+ # If no lang is given, default to the default one.
+ page = Page(**self.page_kwargs)
+ self.assertEqual(page.lang, DEFAULT_CONFIG['DEFAULT_LANG'])
+
+ # it is possible to specify the lang in the metadata infos
+ self.page_kwargs['metadata'].update({'lang': 'fr', })
+ page = Page(**self.page_kwargs)
+ self.assertEqual(page.lang, 'fr')
+
+ def test_save_as(self):
+ # If a lang is not the default lang, save_as should be set
+ # accordingly.
+
+ # if a title is defined, save_as should be set
+ page = Page(**self.page_kwargs)
+ self.assertEqual(page.save_as, "pages/foo-bar.html")
+
+ # if a language is defined, save_as should include it accordingly
+ self.page_kwargs['metadata'].update({'lang': 'fr', })
+ page = Page(**self.page_kwargs)
+ self.assertEqual(page.save_as, "pages/foo-bar-fr.html")
+
+ def test_metadata_url_format(self):
+ # Arbitrary metadata should be passed through url_format()
+ page = Page(**self.page_kwargs)
+ self.assertIn('summary', page.url_format.keys())
+ page.metadata['directory'] = 'test-dir'
+ page.settings = get_settings(PAGE_SAVE_AS='{directory}/{slug}')
+ self.assertEqual(page.save_as, 'test-dir/foo-bar')
+
+ def test_datetime(self):
+ # If DATETIME is set to a tuple, it should be used to override LOCALE
+ dt = datetime(2015, 9, 13)
+
+ page_kwargs = self._copy_page_kwargs()
+
+ # set its date to dt
+ page_kwargs['metadata']['date'] = dt
+ page = Page(**page_kwargs)
+
+ self.assertEqual(page.locale_date,
+ dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT']))
+
+ page_kwargs['settings'] = get_settings()
+
+ # I doubt this can work on all platforms ...
+ if platform == "win32":
+ locale = 'jpn'
+ else:
+ locale = 'ja_JP.utf8'
+ page_kwargs['settings']['DATE_FORMATS'] = {'jp': (locale,
+ '%Y-%m-%d(%a)')}
+ page_kwargs['metadata']['lang'] = 'jp'
+
+ import locale as locale_module
+ try:
+ page = Page(**page_kwargs)
+ self.assertEqual(page.locale_date, '2015-09-13(\u65e5)')
+ except locale_module.Error:
+ # The constructor of ``Page`` will try to set the locale to
+ # ``ja_JP.utf8``. But this attempt will failed when there is no
+ # such locale in the system. You can see which locales there are
+ # in your system with ``locale -a`` command.
+ #
+ # Until we find some other method to test this functionality, we
+ # will simply skip this test.
+ unittest.skip("There is no locale %s in this system." % locale)
+
+ def test_template(self):
+ # Pages default to page, metadata overwrites
+ default_page = Page(**self.page_kwargs)
+ self.assertEqual('page', default_page.template)
+ page_kwargs = self._copy_page_kwargs()
+ page_kwargs['metadata']['template'] = 'custom'
+ custom_page = Page(**page_kwargs)
+ self.assertEqual('custom', custom_page.template)
+
+ def _copy_page_kwargs(self):
+ # make a deep copy of page_kwargs
+ page_kwargs = dict([(key, self.page_kwargs[key]) for key in
+ self.page_kwargs])
+ for key in page_kwargs:
+ if not isinstance(page_kwargs[key], dict):
+ break
+ page_kwargs[key] = dict([(subkey, page_kwargs[key][subkey])
+ for subkey in page_kwargs[key]])
+
+ return page_kwargs
+
+ def test_signal(self):
+ # If a title is given, it should be used to generate the slug.
+
+ def receiver_test_function(sender, instance):
+ pass
+
+ content_object_init.connect(receiver_test_function, sender=Page)
+ Page(**self.page_kwargs)
+ self.assertTrue(content_object_init.has_receivers_for(Page))
+
+
+class TestArticle(TestPage):
+ def test_template(self):
+ # Articles default to article, metadata overwrites
+ default_article = Article(**self.page_kwargs)
+ self.assertEqual('article', default_article.template)
+ article_kwargs = self._copy_page_kwargs()
+ article_kwargs['metadata']['template'] = 'custom'
+ custom_article = Article(**article_kwargs)
+ self.assertEqual('custom', custom_article.template)
+
+
+class TestURLWrapper(unittest.TestCase):
+ def test_comparisons(self):
+ # URLWrappers are sorted by name
+ wrapper_a = URLWrapper(name='first', settings={})
+ wrapper_b = URLWrapper(name='last', settings={})
+ self.assertFalse(wrapper_a > wrapper_b)
+ self.assertFalse(wrapper_a >= wrapper_b)
+ self.assertFalse(wrapper_a == wrapper_b)
+ self.assertTrue(wrapper_a != wrapper_b)
+ self.assertTrue(wrapper_a <= wrapper_b)
+ self.assertTrue(wrapper_a < wrapper_b)
+ wrapper_b.name = 'first'
+ self.assertFalse(wrapper_a > wrapper_b)
+ self.assertTrue(wrapper_a >= wrapper_b)
+ self.assertTrue(wrapper_a == wrapper_b)
+ self.assertFalse(wrapper_a != wrapper_b)
+ self.assertTrue(wrapper_a <= wrapper_b)
+ self.assertFalse(wrapper_a < wrapper_b)
+ wrapper_a.name = 'last'
+ self.assertTrue(wrapper_a > wrapper_b)
+ self.assertTrue(wrapper_a >= wrapper_b)
+ self.assertFalse(wrapper_a == wrapper_b)
+ self.assertTrue(wrapper_a != wrapper_b)
+ self.assertFalse(wrapper_a <= wrapper_b)
+ self.assertFalse(wrapper_a < wrapper_b)
diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py
new file mode 100644
index 00000000..2b9028c3
--- /dev/null
+++ b/pelican/tests/test_generators.py
@@ -0,0 +1,312 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+
+import os
+from codecs import open
+from mock import MagicMock
+from shutil import rmtree
+from tempfile import mkdtemp
+
+from pelican.generators import (Generator, ArticlesGenerator, PagesGenerator,
+ TemplatePagesGenerator)
+from pelican.writers import Writer
+from pelican.settings import DEFAULT_CONFIG
+from pelican.tests.support import unittest, get_settings
+
+CUR_DIR = os.path.dirname(__file__)
+CONTENT_DIR = os.path.join(CUR_DIR, 'content')
+
+
+class TestGenerator(unittest.TestCase):
+ def setUp(self):
+ self.settings = get_settings()
+ self.generator = Generator(self.settings.copy(), self.settings,
+ CUR_DIR, self.settings['THEME'], None,
+ self.settings['MARKUP'])
+
+ def test_include_path(self):
+ filename = os.path.join(CUR_DIR, 'content', 'article.rst')
+ include_path = self.generator._include_path
+ self.assertTrue(include_path(filename))
+ self.assertTrue(include_path(filename, extensions=('rst',)))
+ self.assertFalse(include_path(filename, extensions=('md',)))
+
+ # markup must be a tuple, test that this works also with a list
+ self.generator.markup = ['rst', 'md']
+ self.assertTrue(include_path(filename))
+
+
+class TestArticlesGenerator(unittest.TestCase):
+
+ def setUp(self):
+ super(TestArticlesGenerator, self).setUp()
+ self.generator = None
+
+ def get_populated_generator(self):
+ """
+ We only need to pull all the test articles once, but read from it
+ for each test.
+ """
+ if self.generator is None:
+ settings = get_settings(filenames={})
+ settings['DEFAULT_CATEGORY'] = 'Default'
+ settings['DEFAULT_DATE'] = (1970, 1, 1)
+ self.generator = ArticlesGenerator(
+ context=settings.copy(), settings=settings,
+ path=CONTENT_DIR, theme=settings['THEME'],
+ output_path=None, markup=settings['MARKUP'])
+ self.generator.generate_context()
+ return self.generator
+
+ def distill_articles(self, articles):
+ distilled = []
+ for page in articles:
+ distilled.append([
+ page.title,
+ page.status,
+ page.category.name,
+ page.template
+ ]
+ )
+ return distilled
+
+ def test_generate_feeds(self):
+ settings = get_settings()
+ generator = ArticlesGenerator(
+ context=settings, settings=settings,
+ path=None, theme=settings['THEME'],
+ output_path=None, markup=settings['MARKUP'])
+ writer = MagicMock()
+ generator.generate_feeds(writer)
+ writer.write_feed.assert_called_with([], settings,
+ 'feeds/all.atom.xml')
+
+ generator = ArticlesGenerator(
+ context=settings, settings=get_settings(FEED_ALL_ATOM=None),
+ path=None, theme=settings['THEME'],
+ output_path=None, markup=None)
+ writer = MagicMock()
+ generator.generate_feeds(writer)
+ self.assertFalse(writer.write_feed.called)
+
+ def test_generate_context(self):
+
+ generator = self.get_populated_generator()
+ articles = self.distill_articles(generator.articles)
+ articles_expected = [
+ ['Article title', 'published', 'Default', 'article'],
+ ['Article with markdown and summary metadata single', 'published',
+ 'Default', 'article'],
+ ['Article with markdown and summary metadata multi', 'published',
+ 'Default', 'article'],
+ ['Article with template', 'published', 'Default', 'custom'],
+ ['Test md File', 'published', 'test', 'article'],
+ ['Rst with filename metadata', 'published', 'yeah', 'article'],
+ ['Test Markdown extensions', 'published', 'Default', 'article'],
+ ['This is a super article !', 'published', 'Yeah', 'article'],
+ ['This is an article with category !', 'published', 'yeah',
+ 'article'],
+ ['This is an article without category !', 'published', 'Default',
+ 'article'],
+ ['This is an article without category !', 'published',
+ 'TestCategory', 'article'],
+ ['This is a super article !', 'published', 'yeah', 'article'],
+ ['マックOS X 10.8でパイソンとVirtualenvをインストールと設定',
+ 'published', '指導書', 'article'],
+ ['Article with markdown containing footnotes', 'published',
+ 'Default', 'article']
+ ]
+ self.assertEqual(sorted(articles_expected), sorted(articles))
+
+ def test_generate_categories(self):
+
+ generator = self.get_populated_generator()
+ # test for name
+ # categories are grouped by slug; if two categories have the same slug
+ # but different names they will be grouped together, the first one in
+ # terms of process order will define the name for that category
+ categories = [cat.name for cat, _ in generator.categories]
+ categories_alternatives = (
+ sorted(['Default', 'TestCategory', 'Yeah', 'test', '指導書']),
+ sorted(['Default', 'TestCategory', 'yeah', 'test', '指導書']),
+ )
+ self.assertIn(sorted(categories), categories_alternatives)
+ # test for slug
+ categories = [cat.slug for cat, _ in generator.categories]
+ categories_expected = ['default', 'testcategory', 'yeah', 'test',
+ 'zhi-dao-shu']
+ self.assertEqual(sorted(categories), sorted(categories_expected))
+
+ def test_do_not_use_folder_as_category(self):
+
+ settings = DEFAULT_CONFIG.copy()
+ settings['DEFAULT_CATEGORY'] = 'Default'
+ settings['DEFAULT_DATE'] = (1970, 1, 1)
+ settings['USE_FOLDER_AS_CATEGORY'] = False
+ settings['filenames'] = {}
+ generator = ArticlesGenerator(
+ context=settings.copy(), settings=settings,
+ path=CONTENT_DIR, theme=DEFAULT_CONFIG['THEME'],
+ output_path=None, markup=DEFAULT_CONFIG['MARKUP'])
+ generator.generate_context()
+ # test for name
+ # categories are grouped by slug; if two categories have the same slug
+ # but different names they will be grouped together, the first one in
+ # terms of process order will define the name for that category
+ categories = [cat.name for cat, _ in generator.categories]
+ categories_alternatives = (
+ sorted(['Default', 'Yeah', 'test', '指導書']),
+ sorted(['Default', 'yeah', 'test', '指導書']),
+ )
+ self.assertIn(sorted(categories), categories_alternatives)
+ # test for slug
+ categories = [cat.slug for cat, _ in generator.categories]
+ categories_expected = ['default', 'yeah', 'test', 'zhi-dao-shu']
+ self.assertEqual(sorted(categories), sorted(categories_expected))
+
+ def test_direct_templates_save_as_default(self):
+
+ settings = get_settings(filenames={})
+ generator = ArticlesGenerator(
+ context=settings, settings=settings,
+ path=None, theme=settings['THEME'],
+ output_path=None, markup=settings['MARKUP'])
+ write = MagicMock()
+ generator.generate_direct_templates(write)
+ write.assert_called_with("archives.html",
+ generator.get_template("archives"), settings,
+ blog=True, paginated={}, page_name='archives')
+
+ def test_direct_templates_save_as_modified(self):
+
+ settings = get_settings()
+ settings['DIRECT_TEMPLATES'] = ['archives']
+ settings['ARCHIVES_SAVE_AS'] = 'archives/index.html'
+ generator = ArticlesGenerator(
+ context=settings, settings=settings,
+ path=None, theme=settings['THEME'],
+ output_path=None, markup=settings['MARKUP'])
+ write = MagicMock()
+ generator.generate_direct_templates(write)
+ write.assert_called_with("archives/index.html",
+ generator.get_template("archives"), settings,
+ blog=True, paginated={}, page_name='archives/index')
+
+ def test_direct_templates_save_as_false(self):
+
+ settings = get_settings()
+ settings['DIRECT_TEMPLATES'] = ['archives']
+ settings['ARCHIVES_SAVE_AS'] = 'archives/index.html'
+ generator = ArticlesGenerator(
+ context=settings, settings=settings,
+ path=None, theme=settings['THEME'],
+ output_path=None, markup=settings['MARKUP'])
+ write = MagicMock()
+ generator.generate_direct_templates(write)
+ write.assert_called_count == 0
+
+ def test_per_article_template(self):
+ """
+ Custom template articles get the field but standard/unset are None
+ """
+ generator = self.get_populated_generator()
+ articles = self.distill_articles(generator.articles)
+ custom_template = ['Article with template', 'published', 'Default',
+ 'custom']
+ standard_template = ['This is a super article !', 'published', 'Yeah',
+ 'article']
+ self.assertIn(custom_template, articles)
+ self.assertIn(standard_template, articles)
+
+
+class TestPageGenerator(unittest.TestCase):
+ # Note: Every time you want to test for a new field; Make sure the test
+ # pages in "TestPages" have all the fields Add it to distilled in
+ # distill_pages Then update the assertEqual in test_generate_context
+ # to match expected
+
+ def distill_pages(self, pages):
+ distilled = []
+ for page in pages:
+ distilled.append([
+ page.title,
+ page.status,
+ page.template
+ ]
+ )
+ return distilled
+
+ def test_generate_context(self):
+ settings = get_settings(filenames={})
+ settings['PAGE_DIR'] = 'TestPages' # relative to CUR_DIR
+ settings['DEFAULT_DATE'] = (1970, 1, 1)
+
+ generator = PagesGenerator(
+ context=settings.copy(), settings=settings,
+ path=CUR_DIR, theme=settings['THEME'],
+ output_path=None, markup=settings['MARKUP'])
+ generator.generate_context()
+ pages = self.distill_pages(generator.pages)
+ hidden_pages = self.distill_pages(generator.hidden_pages)
+
+ pages_expected = [
+ ['This is a test page', 'published', 'page'],
+ ['This is a markdown test page', 'published', 'page'],
+ ['This is a test page with a preset template', 'published',
+ 'custom']
+ ]
+ hidden_pages_expected = [
+ ['This is a test hidden page', 'hidden', 'page'],
+ ['This is a markdown test hidden page', 'hidden', 'page'],
+ ['This is a test hidden page with a custom template', 'hidden',
+ 'custom']
+ ]
+
+ self.assertEqual(sorted(pages_expected), sorted(pages))
+ self.assertEqual(sorted(hidden_pages_expected), sorted(hidden_pages))
+
+
+class TestTemplatePagesGenerator(unittest.TestCase):
+
+ TEMPLATE_CONTENT = "foo: {{ foo }}"
+
+ def setUp(self):
+ self.temp_content = mkdtemp(prefix='pelicantests.')
+ self.temp_output = mkdtemp(prefix='pelicantests.')
+
+ def tearDown(self):
+ rmtree(self.temp_content)
+ rmtree(self.temp_output)
+
+ def test_generate_output(self):
+
+ settings = get_settings()
+ settings['STATIC_PATHS'] = ['static']
+ settings['TEMPLATE_PAGES'] = {
+ 'template/source.html': 'generated/file.html'
+ }
+
+ generator = TemplatePagesGenerator(
+ context={'foo': 'bar'}, settings=settings,
+ path=self.temp_content, theme='',
+ output_path=self.temp_output, markup=None)
+
+ # create a dummy template file
+ template_dir = os.path.join(self.temp_content, 'template')
+ template_path = os.path.join(template_dir, 'source.html')
+ os.makedirs(template_dir)
+ with open(template_path, 'w') as template_file:
+ template_file.write(self.TEMPLATE_CONTENT)
+
+ writer = Writer(self.temp_output, settings=settings)
+ generator.generate_output(writer)
+
+ output_path = os.path.join(
+ self.temp_output, 'generated', 'file.html')
+
+ # output file has been generated
+ self.assertTrue(os.path.exists(output_path))
+
+ # output content is correct
+ with open(output_path, 'r') as output_file:
+ self.assertEqual(output_file.read(), 'foo: bar')
diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py
new file mode 100644
index 00000000..53809e7e
--- /dev/null
+++ b/pelican/tests/test_importer.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+
+import os
+import re
+
+from pelican.tools.pelican_import import wp2fields, fields2pelican, decode_wp_content
+from pelican.tests.support import (unittest, temporary_folder, mute,
+ skipIfNoExecutable)
+
+CUR_DIR = 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',
+ 'wordpress_content_encoded')
+WORDPRESS_DECODED_CONTENT_SAMPLE = os.path.join(CUR_DIR,
+ 'content',
+ 'wordpress_content_decoded')
+
+try:
+ from bs4 import BeautifulSoup
+except ImportError:
+ BeautifulSoup = False # NOQA
+
+
+@skipIfNoExecutable(['pandoc', '--version'])
+@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
+class TestWordpressXmlImporter(unittest.TestCase):
+
+ def setUp(self):
+ self.posts = list(wp2fields(WORDPRESS_XML_SAMPLE))
+
+ def test_ignore_empty_posts(self):
+ self.assertTrue(self.posts)
+ for title, content, fname, date, author, categ, tags, kind, format in self.posts:
+ self.assertTrue(title.strip())
+
+ def test_recognise_page_kind(self):
+ """ Check that we recognise pages in wordpress, as opposed to posts """
+ self.assertTrue(self.posts)
+ # Collect (title, filename, kind) of non-empty posts recognised as page
+ pages_data = []
+ for title, content, fname, date, author, categ, tags, kind, format in self.posts:
+ if kind == 'page':
+ pages_data.append((title, fname))
+ self.assertEqual(2, len(pages_data))
+ self.assertEqual(('Page', 'contact'), pages_data[0])
+ self.assertEqual(('Empty Page', 'empty'), pages_data[1])
+
+ def test_dirpage_directive_for_page_kind(self):
+ silent_f2p = mute(True)(fields2pelican)
+ test_post = filter(lambda p: p[0].startswith("Empty Page"), self.posts)
+ with temporary_folder() as temp:
+ fname = list(silent_f2p(test_post, 'markdown', temp, dirpage=True))[0]
+ self.assertTrue(fname.endswith('pages%sempty.md' % os.path.sep))
+
+ def test_can_toggle_raw_html_code_parsing(self):
+ def r(f):
+ with open(f) as infile:
+ return infile.read()
+ silent_f2p = mute(True)(fields2pelican)
+
+ with temporary_folder() as temp:
+
+ rst_files = (r(f) for f in silent_f2p(self.posts, 'markdown', temp))
+ self.assertTrue(any('
', '
')
+ content = re.sub(r'\s*(?' + allblocks + '[^>]*>)', "\\1", content)
+ content = re.sub(r'(?' + allblocks + '[^>]*>)\s*
', "\\1", content)
+ if br:
+ def _preserve_newline(match):
+ return match.group(0).replace("\n", " ")
+ content = re.sub(r'/<(script|style).*?<\/\\1>/s', _preserve_newline, content)
+ # optionally make line breaks
+ content = re.sub(r'(?)\s*\n', " \n", content)
+ content = content.replace(" ", "\n")
+ content = re.sub(r'(?' + allblocks + r'[^>]*>)\s* ', "\\1", content)
+ content = re.sub(r' (\s*?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)', '\\1', content)
+ content = re.sub(r'\n', "", content)
+
+ if pre_tags:
+ def _multi_replace(dic, string):
+ pattern = r'|'.join(map(re.escape, dic.keys()))
+ return re.sub(pattern, lambda m: dic[m.group()], string)
+ content = _multi_replace(pre_tags, content)
+
+ return content
+
+
+def wp2fields(xml):
+ """Opens a wordpress XML file, and yield pelican fields"""
+ try:
+ from bs4 import BeautifulSoup
+ except ImportError:
+ error = ('Missing dependency '
+ '"BeautifulSoup4" and "lxml" required to import Wordpress XML files.')
+ sys.exit(error)
+
+
+ with open(xml, encoding='utf-8') as infile:
+ xmlfile = infile.read()
+ soup = BeautifulSoup(xmlfile, "xml")
+ items = soup.rss.channel.findAll('item')
+
+ for item in items:
+
+ if item.find('status').string == "publish":
+
+ try:
+ # Use HTMLParser due to issues with BeautifulSoup 3
+ 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)
+
+ content = item.find('encoded').string
+ filename = item.find('post_name').string
+
+ raw_date = item.find('post_date').string
+ date_object = time.strptime(raw_date, "%Y-%m-%d %H:%M:%S")
+ date = time.strftime("%Y-%m-%d %H:%M", date_object)
+ author = item.find('creator').string
+
+ categories = [cat.string for cat in item.findAll('category', {'domain' : 'category'})]
+ # caturl = [cat['nicename'] for cat in item.find(domain='category')]
+
+ tags = [tag.string for tag in item.findAll('category', {'domain' : 'post_tag'})]
+
+ kind = 'article'
+ if item.find('post_type').string == 'page':
+ kind = 'page'
+
+ yield (title, content, filename, date, author, categories, tags,
+ kind, "wp-html")
+
+def dc2fields(file):
+ """Opens a Dotclear export file, and yield pelican fields"""
+ try:
+ from bs4 import BeautifulSoup
+ except ImportError:
+ error = ('Missing dependency '
+ '"BeautifulSoup4" and "lxml" required to import Dotclear files.')
+ sys.exit(error)
+
+
+ in_cat = False
+ in_post = False
+ category_list = {}
+ posts = []
+
+ with open(file, 'r', encoding='utf-8') as f:
+
+ for line in f:
+ # remove final \n
+ line = line[:-1]
+
+ if line.startswith('[category'):
+ in_cat = True
+ elif line.startswith('[post'):
+ in_post = True
+ elif in_cat:
+ fields = line.split('","')
+ if not line:
+ in_cat = False
+ else:
+ # remove 1st and last ""
+ fields[0] = fields[0][1:]
+ # fields[-1] = fields[-1][:-1]
+ category_list[fields[0]]=fields[2]
+ elif in_post:
+ if not line:
+ in_post = False
+ break
+ else:
+ posts.append(line)
+
+ print("%i posts read." % len(posts))
+
+ for post in posts:
+ fields = post.split('","')
+
+ # post_id = fields[0][1:]
+ # blog_id = fields[1]
+ # user_id = fields[2]
+ cat_id = fields[3]
+ # post_dt = fields[4]
+ # post_tz = fields[5]
+ post_creadt = fields[6]
+ # post_upddt = fields[7]
+ # post_password = fields[8]
+ # post_type = fields[9]
+ post_format = fields[10]
+ # post_url = fields[11]
+ # post_lang = fields[12]
+ post_title = fields[13]
+ post_excerpt = fields[14]
+ post_excerpt_xhtml = fields[15]
+ post_content = fields[16]
+ post_content_xhtml = fields[17]
+ # post_notes = fields[18]
+ # post_words = fields[19]
+ # post_status = fields[20]
+ # post_selected = fields[21]
+ # post_position = fields[22]
+ # post_open_comment = fields[23]
+ # post_open_tb = fields[24]
+ # nb_comment = fields[25]
+ # nb_trackback = fields[26]
+ post_meta = fields[27]
+ # redirect_url = fields[28][:-1]
+
+ # remove seconds
+ post_creadt = ':'.join(post_creadt.split(':')[0:2])
+
+ author = ""
+ categories = []
+ tags = []
+
+ if cat_id:
+ categories = [category_list[id].strip() for id in cat_id.split(',')]
+
+ # Get tags related to a post
+ tag = post_meta.replace('{', '').replace('}', '').replace('a:1:s:3:\\"tag\\";a:', '').replace('a:0:', '')
+ if len(tag) > 1:
+ if int(tag[:1]) == 1:
+ newtag = tag.split('"')[1]
+ tags.append(
+ BeautifulSoup(
+ newtag
+ , "xml"
+ )
+ # bs4 always outputs UTF-8
+ .decode('utf-8')
+ )
+ else:
+ i=1
+ j=1
+ while(i <= int(tag[:1])):
+ newtag = tag.split('"')[j].replace('\\','')
+ tags.append(
+ BeautifulSoup(
+ newtag
+ , "xml"
+ )
+ # bs4 always outputs UTF-8
+ .decode('utf-8')
+ )
+ i=i+1
+ if j < int(tag[:1])*2:
+ j=j+2
+
+ """
+ dotclear2 does not use markdown by default unless you use the markdown plugin
+ Ref: http://plugins.dotaddict.org/dc2/details/formatting-markdown
+ """
+ if post_format == "markdown":
+ content = post_excerpt + post_content
+ else:
+ content = post_excerpt_xhtml + post_content_xhtml
+ content = content.replace('\\n', '')
+ post_format = "html"
+
+ kind = 'article' # TODO: Recognise pages
+
+ yield (post_title, content, slugify(post_title), post_creadt, author,
+ categories, tags, kind, post_format)
+
+
+def posterous2fields(api_token, email, password):
+ """Imports posterous posts"""
+ import base64
+ from datetime import datetime, timedelta
+ try:
+ # py3k import
+ import json
+ except ImportError:
+ # py2 import
+ import simplejson as json
+
+ try:
+ # py3k import
+ import urllib.request as urllib_request
+ except ImportError:
+ # py2 import
+ import urllib2 as urllib_request
+
+
+ def get_posterous_posts(api_token, email, password, page = 1):
+ base64string = base64.encodestring(("%s:%s" % (email, password)).encode('utf-8')).replace(b'\n', b'')
+ url = "http://posterous.com/api/v2/users/me/sites/primary/posts?api_token=%s&page=%d" % (api_token, page)
+ request = urllib_request.Request(url)
+ request.add_header("Authorization", "Basic %s" % base64string.decode())
+ handle = urllib_request.urlopen(request)
+ posts = json.loads(handle.read().decode('utf-8'))
+ return posts
+
+ page = 1
+ posts = get_posterous_posts(api_token, email, password, page)
+ while len(posts) > 0:
+ posts = get_posterous_posts(api_token, email, password, page)
+ page += 1
+
+ for post in posts:
+ slug = post.get('slug')
+ if not slug:
+ 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")
+ offset = int(raw_date[-5:])
+ delta = timedelta(hours = offset / 100)
+ date_object -= delta
+ date = date_object.strftime("%Y-%m-%d %H:%M")
+ kind = 'article' # TODO: Recognise pages
+
+ yield (post.get('title'), post.get('body_cleaned'), slug, date,
+ post.get('user').get('display_name'), [], tags, kind, "html")
+
+def feed2fields(file):
+ """Read a feed and yield pelican fields"""
+ import feedparser
+ d = feedparser.parse(file)
+ for entry in d.entries:
+ date = (time.strftime("%Y-%m-%d %H:%M", entry.updated_parsed)
+ if hasattr(entry, "updated_parsed") else None)
+ author = entry.author if hasattr(entry, "author") else None
+ tags = [e['term'] for e in entry.tags] if hasattr(entry, "tags") else None
+
+ slug = slugify(entry.title)
+ kind = 'article'
+ yield (entry.title, entry.description, slug, date, author, [], tags,
+ kind, "html")
+
+
+def build_header(title, date, author, categories, tags, slug):
+ """Build a header from a list of fields"""
+ header = '%s\n%s\n' % (title, '#' * len(title))
+ if date:
+ header += ':date: %s\n' % date
+ if author:
+ header += ':author: %s\n' % author
+ if categories:
+ header += ':category: %s\n' % ', '.join(categories)
+ if tags:
+ header += ':tags: %s\n' % ', '.join(tags)
+ if slug:
+ header += ':slug: %s\n' % slug
+ header += '\n'
+ return header
+
+def build_markdown_header(title, date, author, categories, tags, slug):
+ """Build a header from a list of fields"""
+ header = 'Title: %s\n' % title
+ if date:
+ header += 'Date: %s\n' % date
+ if author:
+ header += 'Author: %s\n' % author
+ if categories:
+ header += 'Category: %s\n' % ', '.join(categories)
+ if tags:
+ header += 'Tags: %s\n' % ', '.join(tags)
+ if slug:
+ header += 'Slug: %s\n' % slug
+ header += '\n'
+ return header
+
+def fields2pelican(fields, out_markup, output_path,
+ dircat=False, strip_raw=False, disable_slugs=False,
+ dirpage=False, filename_template=None):
+ for (title, content, filename, date, author, categories, tags,
+ kind, in_markup) in fields:
+ slug = not disable_slugs and filename or None
+ if (in_markup == "markdown") or (out_markup == "markdown") :
+ ext = '.md'
+ header = build_markdown_header(title, date, author, categories, tags, slug)
+ else:
+ out_markup = "rst"
+ ext = '.rst'
+ header = build_header(title, date, author, categories, tags, slug)
+
+ filename = os.path.basename(filename)
+
+ # Enforce filename restrictions for various filesystems at once; see
+ # http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
+ # we do not need to filter words because an extension will be appended
+ filename = re.sub(r'[<>:"/\\|?*^% ]', '-', filename) # invalid chars
+ filename = filename.lstrip('.') # should not start with a dot
+ if not filename:
+ filename = '_'
+ filename = filename[:249] # allow for 5 extra characters
+
+ # option to put page posts in pages/ subdirectory
+ if dirpage and kind == 'page':
+ pages_dir = os.path.join(output_path, 'pages')
+ if not os.path.isdir(pages_dir):
+ os.mkdir(pages_dir)
+ out_filename = os.path.join(pages_dir, filename+ext)
+ # option to put files in directories with categories names
+ elif dircat and (len(categories) > 0):
+ catname = slugify(categories[0])
+ out_filename = os.path.join(output_path, catname, filename+ext)
+ if not os.path.isdir(os.path.join(output_path, catname)):
+ os.mkdir(os.path.join(output_path, catname))
+ else:
+ out_filename = os.path.join(output_path, filename+ext)
+
+ print(out_filename)
+
+ if in_markup in ("html", "wp-html"):
+ html_filename = os.path.join(output_path, filename+'.html')
+
+ with open(html_filename, 'w', encoding='utf-8') as fp:
+ # Replace newlines with paragraphs wrapped with so
+ # HTML is valid before conversion
+ if in_markup == "wp-html":
+ new_content = decode_wp_content(content)
+ else:
+ paragraphs = content.splitlines()
+ paragraphs = ['
{0}
'.format(p) for p in paragraphs]
+ new_content = ''.join(paragraphs)
+
+ fp.write(new_content)
+
+
+ parse_raw = '--parse-raw' if not strip_raw else ''
+ cmd = ('pandoc --normalize --reference-links {0} --from=html'
+ ' --to={1} -o "{2}" "{3}"').format(
+ parse_raw, out_markup, out_filename, html_filename)
+
+ try:
+ rc = subprocess.call(cmd, shell=True)
+ if rc < 0:
+ error = "Child was terminated by signal %d" % -rc
+ exit(error)
+
+ elif rc > 0:
+ error = "Please, check your Pandoc installation."
+ exit(error)
+ except OSError as e:
+ error = "Pandoc execution failed: %s" % e
+ exit(error)
+
+ os.remove(html_filename)
+
+ with open(out_filename, 'r', encoding='utf-8') as fs:
+ content = fs.read()
+ if out_markup == "markdown":
+ # In markdown, to insert a , end a line with two or more spaces & then a end-of-line
+ content = content.replace("\\\n ", " \n")
+ content = content.replace("\\\n", " \n")
+
+ with open(out_filename, 'w', encoding='utf-8') as fs:
+ fs.write(header + content)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Transform feed, Wordpress or Dotclear files to reST (rst) "
+ "or Markdown (md) files. Be sure to have pandoc installed.",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+ parser.add_argument(dest='input', help='The input file to read')
+ parser.add_argument('--wpfile', action='store_true', dest='wpfile',
+ help='Wordpress XML export')
+ parser.add_argument('--dotclear', action='store_true', dest='dotclear',
+ help='Dotclear export')
+ parser.add_argument('--posterous', action='store_true', dest='posterous',
+ help='Posterous export')
+ parser.add_argument('--feed', action='store_true', dest='feed',
+ help='Feed to parse')
+ parser.add_argument('-o', '--output', dest='output', default='output',
+ help='Output path')
+ parser.add_argument('-m', '--markup', dest='markup', default='rst',
+ help='Output markup format (supports rst & markdown)')
+ parser.add_argument('--dir-cat', action='store_true', dest='dircat',
+ help='Put files in directories with categories name')
+ parser.add_argument('--dir-page', action='store_true', dest='dirpage',
+ help=('Put files recognised as pages in "pages/" sub-directory'
+ ' (wordpress import only)'))
+ parser.add_argument('--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('--disable-slugs', action='store_true',
+ dest='disable_slugs',
+ help='Disable storing slugs from imported posts within output. '
+ 'With this disabled, your Pelican URLs may not be consistent '
+ 'with your original posts.')
+ parser.add_argument('-e', '--email', dest='email',
+ help="Email address (posterous import only)")
+ parser.add_argument('-p', '--password', dest='password',
+ help="Password (posterous import only)")
+
+ args = parser.parse_args()
+
+ input_type = None
+ if args.wpfile:
+ input_type = 'wordpress'
+ elif args.dotclear:
+ input_type = 'dotclear'
+ elif args.posterous:
+ input_type = 'posterous'
+ elif args.feed:
+ input_type = 'feed'
+ else:
+ error = "You must provide either --wpfile, --dotclear, --posterous or --feed options"
+ exit(error)
+
+ if not os.path.exists(args.output):
+ try:
+ os.mkdir(args.output)
+ except OSError:
+ error = "Unable to create the output folder: " + args.output
+ exit(error)
+
+ if input_type == 'wordpress':
+ fields = wp2fields(args.input)
+ elif input_type == 'dotclear':
+ fields = dc2fields(args.input)
+ elif input_type == 'posterous':
+ fields = posterous2fields(args.input, args.email, args.password)
+ elif input_type == 'feed':
+ fields = feed2fields(args.input)
+
+ init() # init logging
+
+ fields2pelican(fields, args.markup, args.output,
+ dircat=args.dircat or False,
+ dirpage=args.dirpage or False,
+ strip_raw=args.strip_raw or False,
+ disable_slugs=args.disable_slugs or False)
diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py
new file mode 100755
index 00000000..e62bc5ca
--- /dev/null
+++ b/pelican/tools/pelican_quickstart.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python
+
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+import six
+
+import os
+import string
+import argparse
+import sys
+import codecs
+
+from pelican import __version__
+
+_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "templates")
+
+CONF = {
+ 'pelican': 'pelican',
+ 'pelicanopts': '',
+ 'basedir': os.curdir,
+ 'ftp_host': 'localhost',
+ 'ftp_user': 'anonymous',
+ 'ftp_target_dir': '/',
+ 'ssh_host': 'localhost',
+ 'ssh_port': 22,
+ 'ssh_user': 'root',
+ 'ssh_target_dir': '/var/www',
+ 's3_bucket': 'my_s3_bucket',
+ 'dropbox_dir': '~/Dropbox/Public/',
+ 'default_pagination': 10,
+ 'siteurl': '',
+ 'lang': 'en'
+}
+
+def _input_compat(prompt):
+ if six.PY3:
+ r = input(prompt)
+ else:
+ r = raw_input(prompt)
+ return r
+
+if six.PY3:
+ str_compat = str
+else:
+ str_compat = unicode
+
+def decoding_strings(f):
+ def wrapper(*args, **kwargs):
+ out = f(*args, **kwargs)
+ if isinstance(out, six.string_types) and not six.PY3:
+ # todo: make encoding configurable?
+ if six.PY3:
+ return out
+ else:
+ return out.decode(sys.stdin.encoding)
+ return out
+ return wrapper
+
+
+def get_template(name, as_encoding='utf-8'):
+ template = os.path.join(_TEMPLATES_DIR, "{0}.in".format(name))
+
+ if not os.path.isfile(template):
+ raise RuntimeError("Cannot open {0}".format(template))
+
+ with codecs.open(template, 'r', as_encoding) as fd:
+ line = fd.readline()
+ while line:
+ yield line
+ line = fd.readline()
+ fd.close()
+
+
+@decoding_strings
+def ask(question, answer=str_compat, default=None, l=None):
+ if answer == str_compat:
+ r = ''
+ while True:
+ if default:
+ r = _input_compat('> {0} [{1}] '.format(question, default))
+ else:
+ r = _input_compat('> {0} '.format(question, default))
+
+ r = r.strip()
+
+ if len(r) <= 0:
+ if default:
+ r = default
+ break
+ else:
+ print('You must enter something')
+ else:
+ if l and len(r) != l:
+ print('You must enter a {0} letters long string'.format(l))
+ else:
+ break
+
+ return r
+
+ elif answer == bool:
+ r = None
+ while True:
+ if default is True:
+ r = _input_compat('> {0} (Y/n) '.format(question))
+ elif default is False:
+ r = _input_compat('> {0} (y/N) '.format(question))
+ else:
+ r = _input_compat('> {0} (y/n) '.format(question))
+
+ r = r.strip().lower()
+
+ if r in ('y', 'yes'):
+ r = True
+ break
+ elif r in ('n', 'no'):
+ r = False
+ break
+ elif not r:
+ r = default
+ break
+ else:
+ print("You must answer 'yes' or 'no'")
+ return r
+ elif answer == int:
+ r = None
+ while True:
+ if default:
+ r = _input_compat('> {0} [{1}] '.format(question, default))
+ else:
+ r = _input_compat('> {0} '.format(question))
+
+ r = r.strip()
+
+ if not r:
+ r = default
+ break
+
+ try:
+ r = int(r)
+ break
+ except:
+ print('You must enter an integer')
+ return r
+ else:
+ raise NotImplemented('Argument `answer` must be str_compat, bool, or integer')
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="A kickstarter for Pelican",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument('-p', '--path', default=os.curdir,
+ help="The path to generate the blog into")
+ parser.add_argument('-t', '--title', metavar="title",
+ help='Set the title of the website')
+ parser.add_argument('-a', '--author', metavar="author",
+ help='Set the author name of the website')
+ parser.add_argument('-l', '--lang', metavar="lang",
+ help='Set the default web site language')
+
+ args = parser.parse_args()
+
+ print('''Welcome to pelican-quickstart v{v}.
+
+This script will help you create a new Pelican-based website.
+
+Please answer the following questions so this script can generate the files
+needed by Pelican.
+
+ '''.format(v=__version__))
+
+ project = os.path.join(
+ os.environ.get('VIRTUAL_ENV', os.curdir), '.project')
+ if os.path.isfile(project):
+ CONF['basedir'] = open(project, 'r').read().rstrip("\n")
+ print('Using project associated with current virtual environment.'
+ 'Will save to:\n%s\n' % CONF['basedir'])
+ else:
+ CONF['basedir'] = os.path.abspath(ask('Where do you want to create your new web site?', answer=str_compat, default=args.path))
+
+ CONF['sitename'] = ask('What will be the title of this web site?', answer=str_compat, default=args.title)
+ CONF['author'] = ask('Who will be the author of this web site?', answer=str_compat, default=args.author)
+ CONF['lang'] = ask('What will be the default language of this web site?', str_compat, args.lang or CONF['lang'], 2)
+
+ if ask('Do you want to specify a URL prefix? e.g., http://example.com ', answer=bool, default=True):
+ CONF['siteurl'] = ask('What is your URL prefix? (see above example; no trailing slash)', str_compat, CONF['siteurl'])
+
+ CONF['with_pagination'] = ask('Do you want to enable article pagination?', bool, bool(CONF['default_pagination']))
+
+ if CONF['with_pagination']:
+ CONF['default_pagination'] = ask('How many articles per page do you want?', int, CONF['default_pagination'])
+ else:
+ CONF['default_pagination'] = False
+
+ mkfile = ask('Do you want to generate a Makefile to easily manage your website?', bool, True)
+ develop = ask('Do you want an auto-reload & simpleHTTP script to assist with theme and site development?', bool, True)
+
+ if mkfile:
+ if ask('Do you want to upload your website using FTP?', answer=bool, default=False):
+ CONF['ftp_host'] = ask('What is the hostname of your FTP server?', str_compat, CONF['ftp_host'])
+ CONF['ftp_user'] = ask('What is your username on that server?', str_compat, CONF['ftp_user'])
+ CONF['ftp_target_dir'] = ask('Where do you want to put your web site on that server?', str_compat, CONF['ftp_target_dir'])
+ if ask('Do you want to upload your website using SSH?', answer=bool, default=False):
+ CONF['ssh_host'] = ask('What is the hostname of your SSH server?', str_compat, CONF['ssh_host'])
+ CONF['ssh_port'] = ask('What is the port of your SSH server?', int, CONF['ssh_port'])
+ CONF['ssh_user'] = ask('What is your username on that server?', str_compat, CONF['ssh_user'])
+ CONF['ssh_target_dir'] = ask('Where do you want to put your web site on that server?', str_compat, CONF['ssh_target_dir'])
+ if ask('Do you want to upload your website using Dropbox?', answer=bool, default=False):
+ CONF['dropbox_dir'] = ask('Where is your Dropbox directory?', str_compat, CONF['dropbox_dir'])
+ if ask('Do you want to upload your website using S3?', answer=bool, default=False):
+ CONF['s3_bucket'] = ask('What is the name of your S3 bucket?', str_compat, CONF['s3_bucket'])
+
+ try:
+ os.makedirs(os.path.join(CONF['basedir'], 'content'))
+ except OSError as e:
+ print('Error: {0}'.format(e))
+
+ try:
+ os.makedirs(os.path.join(CONF['basedir'], 'output'))
+ except OSError as e:
+ print('Error: {0}'.format(e))
+
+ try:
+ with codecs.open(os.path.join(CONF['basedir'], 'pelicanconf.py'), 'w', 'utf-8') as fd:
+ conf_python = dict()
+ for key, value in CONF.items():
+ conf_python[key] = repr(value)
+
+ for line in get_template('pelicanconf.py'):
+ template = string.Template(line)
+ fd.write(template.safe_substitute(conf_python))
+ fd.close()
+ except OSError as e:
+ print('Error: {0}'.format(e))
+
+ try:
+ with codecs.open(os.path.join(CONF['basedir'], 'publishconf.py'), 'w', 'utf-8') as fd:
+ for line in get_template('publishconf.py'):
+ template = string.Template(line)
+ fd.write(template.safe_substitute(CONF))
+ fd.close()
+ except OSError as e:
+ print('Error: {0}'.format(e))
+
+ if mkfile:
+ try:
+ with codecs.open(os.path.join(CONF['basedir'], 'Makefile'), 'w', 'utf-8') as fd:
+ mkfile_template_name = 'Makefile'
+ py_v = 'PY=python'
+ if six.PY3:
+ py_v = 'PY=python3'
+ template = string.Template(py_v)
+ fd.write(template.safe_substitute(CONF))
+ fd.write('\n')
+ for line in get_template(mkfile_template_name):
+ template = string.Template(line)
+ fd.write(template.safe_substitute(CONF))
+ fd.close()
+ except OSError as e:
+ print('Error: {0}'.format(e))
+
+ if develop:
+ conf_shell = dict()
+ for key, value in CONF.items():
+ if isinstance(value, six.string_types) and ' ' in value:
+ value = '"' + value.replace('"', '\\"') + '"'
+ conf_shell[key] = value
+ try:
+ with codecs.open(os.path.join(CONF['basedir'], 'develop_server.sh'), 'w', 'utf-8') as fd:
+ lines = list(get_template('develop_server.sh'))
+ py_v = 'PY=python\n'
+ if six.PY3:
+ py_v = 'PY=python3\n'
+ lines = lines[:4] + [py_v] + lines[4:]
+ for line in lines:
+ template = string.Template(line)
+ fd.write(template.safe_substitute(conf_shell))
+ fd.close()
+ os.chmod((os.path.join(CONF['basedir'], 'develop_server.sh')), 493) # mode 0o755
+ except OSError as e:
+ print('Error: {0}'.format(e))
+
+ print('Done. Your new project is available at %s' % CONF['basedir'])
diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py
new file mode 100755
index 00000000..8d71535d
--- /dev/null
+++ b/pelican/tools/pelican_themes.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+
+import six
+
+import argparse
+import os
+import shutil
+import sys
+
+try:
+ import pelican
+except:
+ err('Cannot import pelican.\nYou must install Pelican in order to run this script.', -1)
+
+
+global _THEMES_PATH
+_THEMES_PATH = os.path.join(
+ os.path.dirname(
+ os.path.abspath(
+ pelican.__file__
+ )
+ ),
+ 'themes'
+)
+
+__version__ = '0.2'
+_BUILTIN_THEMES = ['simple', 'notmyidea']
+
+
+def err(msg, die=None):
+ """Print an error message and exits if an exit code is given"""
+ sys.stderr.write(msg + '\n')
+ if die:
+ sys.exit((die if type(die) is int else 1))
+
+
+def main():
+ """Main function"""
+
+ parser = argparse.ArgumentParser(description="""Install themes for Pelican""")
+
+ excl= parser.add_mutually_exclusive_group()
+ excl.add_argument('-l', '--list', dest='action', action="store_const", const='list',
+ help="Show the themes already installed and exit")
+ excl.add_argument('-p', '--path', dest='action', action="store_const", const='path',
+ help="Show the themes path and exit")
+ excl.add_argument('-V', '--version', action='version', version='pelican-themes v{0}'.format(__version__),
+ help='Print the version of this script')
+
+
+ parser.add_argument('-i', '--install', dest='to_install', nargs='+', metavar="theme path",
+ help='The themes to install')
+ parser.add_argument('-r', '--remove', dest='to_remove', nargs='+', metavar="theme name",
+ help='The themes to remove')
+ parser.add_argument('-U', '--upgrade', dest='to_upgrade', nargs='+',
+ metavar="theme path", help='The themes to upgrade')
+ parser.add_argument('-s', '--symlink', dest='to_symlink', nargs='+', metavar="theme path",
+ help="Same as `--install', but create a symbolic link instead of copying the theme. Useful for theme development")
+ parser.add_argument('-c', '--clean', dest='clean', action="store_true",
+ help="Remove the broken symbolic links of the theme path")
+
+
+ parser.add_argument('-v', '--verbose', dest='verbose', action="store_true",
+ help="Verbose output")
+
+
+ args = parser.parse_args()
+
+ to_install = args.to_install or args.to_upgrade
+ to_sym = args.to_symlink or args.clean
+
+
+ if args.action:
+ if args.action is 'list':
+ list_themes(args.verbose)
+ elif args.action is 'path':
+ print(_THEMES_PATH)
+ elif to_install or args.to_remove or to_sym:
+ if args.to_remove:
+ if args.verbose:
+ print('Removing themes...')
+
+ for i in args.to_remove:
+ remove(i, v=args.verbose)
+
+ if args.to_install:
+ if args.verbose:
+ print('Installing themes...')
+
+ for i in args.to_install:
+ install(i, v=args.verbose)
+
+ if args.to_upgrade:
+ if args.verbose:
+ print('Upgrading themes...')
+
+ for i in args.to_upgrade:
+ install(i, v=args.verbose, u=True)
+
+ if args.to_symlink:
+ if args.verbose:
+ print('Linking themes...')
+
+ for i in args.to_symlink:
+ symlink(i, v=args.verbose)
+
+ if args.clean:
+ if args.verbose:
+ print('Cleaning the themes directory...')
+
+ clean(v=args.verbose)
+ else:
+ print('No argument given... exiting.')
+
+
+def themes():
+ """Returns the list of the themes"""
+ for i in os.listdir(_THEMES_PATH):
+ e = os.path.join(_THEMES_PATH, i)
+
+ if os.path.isdir(e):
+ if os.path.islink(e):
+ yield (e, os.readlink(e))
+ else:
+ yield (e, None)
+
+
+def list_themes(v=False):
+ """Display the list of the themes"""
+ for t, l in themes():
+ if not v:
+ t = os.path.basename(t)
+ if l:
+ if v:
+ print(t + (" (symbolic link to `" + l + "')"))
+ else:
+ print(t + '@')
+ else:
+ print(t)
+
+
+def remove(theme_name, v=False):
+ """Removes a theme"""
+
+ theme_name = theme_name.replace('/','')
+ target = os.path.join(_THEMES_PATH, theme_name)
+
+ if theme_name in _BUILTIN_THEMES:
+ err(theme_name + ' is a builtin theme.\nYou cannot remove a builtin theme with this script, remove it by hand if you want.')
+ elif os.path.islink(target):
+ if v:
+ print('Removing link `' + target + "'")
+ os.remove(target)
+ elif os.path.isdir(target):
+ if v:
+ print('Removing directory `' + target + "'")
+ shutil.rmtree(target)
+ elif os.path.exists(target):
+ err(target + ' : not a valid theme')
+ else:
+ err(target + ' : no such file or directory')
+
+
+def install(path, v=False, u=False):
+ """Installs a theme"""
+ if not os.path.exists(path):
+ err(path + ' : no such file or directory')
+ elif not os.path.isdir(path):
+ err(path + ' : not a directory')
+ else:
+ theme_name = os.path.basename(os.path.normpath(path))
+ theme_path = os.path.join(_THEMES_PATH, theme_name)
+ exists = os.path.exists(theme_path)
+ if exists and not u:
+ err(path + ' : already exists')
+ elif exists and u:
+ remove(theme_name, v)
+ install(path, v)
+ else:
+ if v:
+ print("Copying `{p}' to `{t}' ...".format(p=path, t=theme_path))
+ try:
+ shutil.copytree(path, theme_path)
+
+ try:
+ if os.name == 'posix':
+ for root, dirs, files in os.walk(theme_path):
+ for d in dirs:
+ dname = os.path.join(root, d)
+ os.chmod(dname, 493) # 0o755
+ for f in files:
+ fname = os.path.join(root, f)
+ os.chmod(fname, 420) # 0o644
+ except OSError as e:
+ err("Cannot change permissions of files or directory in `{r}':\n{e}".format(r=theme_path, e=str(e)), die=False)
+ except Exception as e:
+ err("Cannot copy `{p}' to `{t}':\n{e}".format(p=path, t=theme_path, e=str(e)))
+
+
+def symlink(path, v=False):
+ """Symbolically link a theme"""
+ if not os.path.exists(path):
+ err(path + ' : no such file or directory')
+ elif not os.path.isdir(path):
+ err(path + ' : not a directory')
+ else:
+ theme_name = os.path.basename(os.path.normpath(path))
+ theme_path = os.path.join(_THEMES_PATH, theme_name)
+ if os.path.exists(theme_path):
+ err(path + ' : already exists')
+ else:
+ if v:
+ print("Linking `{p}' to `{t}' ...".format(p=path, t=theme_path))
+ try:
+ os.symlink(path, theme_path)
+ except Exception as e:
+ err("Cannot link `{p}' to `{t}':\n{e}".format(p=path, t=theme_path, e=str(e)))
+
+
+def is_broken_link(path):
+ """Returns True if the path given as is a broken symlink"""
+ path = os.readlink(path)
+ return not os.path.exists(path)
+
+
+def clean(v=False):
+ """Removes the broken symbolic links"""
+ c=0
+ for path in os.listdir(_THEMES_PATH):
+ path = os.path.join(_THEMES_PATH, path)
+ if os.path.islink(path):
+ if is_broken_link(path):
+ if v:
+ print('Removing {0}'.format(path))
+ try:
+ os.remove(path)
+ except OSError as e:
+ print('Error: cannot remove {0}'.format(path))
+ else:
+ c+=1
+
+ print("\nRemoved {0} broken links".format(c))
diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in
new file mode 100644
index 00000000..4bc764ca
--- /dev/null
+++ b/pelican/tools/templates/Makefile.in
@@ -0,0 +1,85 @@
+PELICAN=$pelican
+PELICANOPTS=$pelicanopts
+
+BASEDIR=$$(CURDIR)
+INPUTDIR=$$(BASEDIR)/content
+OUTPUTDIR=$$(BASEDIR)/output
+CONFFILE=$$(BASEDIR)/pelicanconf.py
+PUBLISHCONF=$$(BASEDIR)/publishconf.py
+
+FTP_HOST=$ftp_host
+FTP_USER=$ftp_user
+FTP_TARGET_DIR=$ftp_target_dir
+
+SSH_HOST=$ssh_host
+SSH_PORT=$ssh_port
+SSH_USER=$ssh_user
+SSH_TARGET_DIR=$ssh_target_dir
+
+S3_BUCKET=$s3_bucket
+
+DROPBOX_DIR=$dropbox_dir
+
+help:
+ @echo 'Makefile for a pelican Web site '
+ @echo ' '
+ @echo 'Usage: '
+ @echo ' make html (re)generate the web site '
+ @echo ' make clean remove the generated files '
+ @echo ' make regenerate regenerate files upon modification '
+ @echo ' make publish generate using production settings '
+ @echo ' make serve serve site at http://localhost:8000'
+ @echo ' make devserver start/restart develop_server.sh '
+ @echo ' make stopserver stop local server '
+ @echo ' ssh_upload upload the web site via SSH '
+ @echo ' rsync_upload upload the web site via rsync+ssh '
+ @echo ' dropbox_upload upload the web site via Dropbox '
+ @echo ' ftp_upload upload the web site via FTP '
+ @echo ' s3_upload upload the web site via S3 '
+ @echo ' github upload the web site via gh-pages '
+ @echo ' '
+
+
+html:
+ $$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
+
+clean:
+ [ ! -d $$(OUTPUTDIR) ] || rm -rf $$(OUTPUTDIR)
+
+regenerate:
+ $$(PELICAN) -r $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS)
+
+serve:
+ cd $$(OUTPUTDIR) && $(PY) -m pelican.server
+
+devserver:
+ $$(BASEDIR)/develop_server.sh restart
+
+stopserver:
+ kill -9 `cat pelican.pid`
+ kill -9 `cat srv.pid`
+ @echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
+
+publish:
+ $$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(PUBLISHCONF) $$(PELICANOPTS)
+
+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
+
+dropbox_upload: publish
+ cp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR)
+
+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
+
+github: publish
+ ghp-import $$(OUTPUTDIR)
+ git push origin gh-pages
+
+.PHONY: html help clean regenerate serve devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload github
diff --git a/pelican/tools/templates/develop_server.sh.in b/pelican/tools/templates/develop_server.sh.in
new file mode 100755
index 00000000..6dd11e6d
--- /dev/null
+++ b/pelican/tools/templates/develop_server.sh.in
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+##
+# This section should match your Makefile
+##
+PELICAN=$pelican
+PELICANOPTS=$pelicanopts
+
+BASEDIR=$$(pwd)
+INPUTDIR=$$BASEDIR/content
+OUTPUTDIR=$$BASEDIR/output
+CONFFILE=$$BASEDIR/pelicanconf.py
+
+###
+# Don't change stuff below here unless you are sure
+###
+
+SRV_PID=$$BASEDIR/srv.pid
+PELICAN_PID=$$BASEDIR/pelican.pid
+
+function usage(){
+ echo "usage: $$0 (stop) (start) (restart)"
+ echo "This starts pelican in debug and reload mode and then launches"
+ echo "A pelican.server to help site development. It doesn't read"
+ echo "your pelican options so you edit any paths in your Makefile"
+ echo "you will need to edit it as well"
+ exit 3
+}
+
+function alive() {
+ kill -0 $$1 >/dev/null 2>&1
+}
+
+function shut_down(){
+ PID=$$(cat $$SRV_PID)
+ if [[ $$? -eq 0 ]]; then
+ if alive $PID; then
+ echo "Killing pelican.server"
+ kill $$PID
+ else
+ echo "Stale PID, deleting"
+ fi
+ rm $$SRV_PID
+ else
+ echo "pelican.server PIDFile not found"
+ fi
+
+ PID=$$(cat $$PELICAN_PID)
+ if [[ $$? -eq 0 ]]; then
+ if alive $$PID; then
+ echo "Killing Pelican"
+ kill $$PID
+ else
+ echo "Stale PID, deleting"
+ fi
+ rm $$PELICAN_PID
+ else
+ echo "Pelican PIDFile not found"
+ fi
+}
+
+function start_up(){
+ echo "Starting up Pelican and pelican.server"
+ shift
+ $$PELICAN --debug --autoreload -r $$INPUTDIR -o $$OUTPUTDIR -s $$CONFFILE $$PELICANOPTS &
+ pelican_pid=$$!
+ echo $$pelican_pid > $$PELICAN_PID
+ cd $$OUTPUTDIR
+ $PY -m pelican.server &
+ srv_pid=$$!
+ echo $$srv_pid > $$SRV_PID
+ cd $$BASEDIR
+ sleep 1
+ if ! alive $$pelican_pid ; then
+ echo "Pelican didn't start. Is the pelican package installed?"
+ return 1
+ elif ! alive $$srv_pid ; then
+ echo "pelican.server didn't start. Is there something else which uses port 8000?"
+ return 1
+ fi
+ echo 'Pelican and pelican.server processes now running in background.'
+}
+
+###
+# MAIN
+###
+[[ $$# -ne 1 ]] && usage
+if [[ $$1 == "stop" ]]; then
+ shut_down
+elif [[ $$1 == "restart" ]]; then
+ shut_down
+ start_up
+elif [[ $$1 == "start" ]]; then
+ if ! start_up; then
+ shut_down
+ fi
+else
+ usage
+fi
diff --git a/pelican/tools/templates/pelicanconf.py.in b/pelican/tools/templates/pelicanconf.py.in
new file mode 100644
index 00000000..5d96051d
--- /dev/null
+++ b/pelican/tools/templates/pelicanconf.py.in
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+from __future__ import unicode_literals
+
+AUTHOR = $author
+SITENAME = $sitename
+SITEURL = ''
+
+TIMEZONE = 'Europe/Paris'
+
+DEFAULT_LANG = $lang
+
+# Feed generation is usually not desired when developing
+FEED_ALL_ATOM = None
+CATEGORY_FEED_ATOM = None
+TRANSLATION_FEED_ATOM = None
+
+# Blogroll
+LINKS = (('Pelican', 'http://getpelican.com/'),
+ ('Python.org', 'http://python.org/'),
+ ('Jinja2', 'http://jinja.pocoo.org/'),
+ ('You can modify those links in your config file', '#'),)
+
+# Social widget
+SOCIAL = (('You can add links in your config file', '#'),
+ ('Another social link', '#'),)
+
+DEFAULT_PAGINATION = $default_pagination
+
+# Uncomment following line if you want document-relative URLs when developing
+#RELATIVE_URLS = True
diff --git a/pelican/tools/templates/publishconf.py.in b/pelican/tools/templates/publishconf.py.in
new file mode 100755
index 00000000..d1ed994d
--- /dev/null
+++ b/pelican/tools/templates/publishconf.py.in
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+from __future__ import unicode_literals
+
+# This file is only used if you use `make publish` or
+# explicitly specify it as your config file.
+
+import os
+import sys
+sys.path.append(os.curdir)
+from pelicanconf import *
+
+SITEURL = '$siteurl'
+RELATIVE_URLS = False
+
+FEED_ALL_ATOM = 'feeds/all.atom.xml'
+CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
+
+DELETE_OUTPUT_DIRECTORY = True
+
+# Following items are often useful when publishing
+
+#DISQUS_SITENAME = ""
+#GOOGLE_ANALYTICS = ""
diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py
new file mode 100644
index 00000000..b0df61ad
--- /dev/null
+++ b/pelican/urlwrappers.py
@@ -0,0 +1,96 @@
+import os
+import functools
+import logging
+
+import six
+
+from pelican.utils import (slugify, python_2_unicode_compatible)
+
+logger = logging.getLogger(__name__)
+
+
+@python_2_unicode_compatible
+@functools.total_ordering
+class URLWrapper(object):
+ def __init__(self, name, settings):
+ # next 2 lines are redundant with the setter of the name property
+ # but are here for clarity
+ self._name = name
+ self.slug = slugify(name)
+ self.name = name
+ self.settings = settings
+
+ @property
+ def name(self):
+ return self._name
+
+ @name.setter
+ def name(self, name):
+ self._name = name
+ self.slug = slugify(name)
+
+ def as_dict(self):
+ d = self.__dict__
+ d['name'] = self.name
+ return d
+
+ def __hash__(self):
+ return hash(self.slug)
+
+ def _key(self):
+ return self.slug
+
+ def _normalize_key(self, key):
+ return six.text_type(slugify(key))
+
+ def __eq__(self, other):
+ return self._key() == self._normalize_key(other)
+
+ def __ne__(self, other):
+ return self._key() != self._normalize_key(other)
+
+ def __lt__(self, other):
+ return self._key() < self._normalize_key(other)
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return '<{} {}>'.format(type(self).__name__, str(self))
+
+ def _from_settings(self, key, get_page_name=False):
+ """Returns URL information as defined in settings.
+
+ When get_page_name=True returns URL without anything after {slug} e.g.
+ if in settings: CATEGORY_URL="cat/{slug}.html" this returns
+ "cat/{slug}" Useful for pagination.
+
+ """
+ setting = "%s_%s" % (self.__class__.__name__.upper(), key)
+ value = self.settings[setting]
+ if not isinstance(value, six.string_types):
+ logger.warning('%s is set to %s' % (setting, value))
+ return value
+ else:
+ if get_page_name:
+ return os.path.splitext(value)[0].format(**self.as_dict())
+ else:
+ return value.format(**self.as_dict())
+
+ page_name = property(functools.partial(_from_settings, key='URL',
+ get_page_name=True))
+ url = property(functools.partial(_from_settings, key='URL'))
+ save_as = property(functools.partial(_from_settings, key='SAVE_AS'))
+
+
+class Category(URLWrapper):
+ pass
+
+
+class Tag(URLWrapper):
+ def __init__(self, name, *args, **kwargs):
+ super(Tag, self).__init__(name.strip(), *args, **kwargs)
+
+
+class Author(URLWrapper):
+ pass
diff --git a/pelican/utils.py b/pelican/utils.py
new file mode 100644
index 00000000..2c70ae8c
--- /dev/null
+++ b/pelican/utils.py
@@ -0,0 +1,572 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, print_function
+import six
+
+import os
+import re
+import pytz
+import shutil
+import traceback
+import logging
+import errno
+import locale
+import fnmatch
+from collections import Hashable
+from functools import partial
+
+from codecs import open, BOM_UTF8
+from datetime import datetime
+from itertools import groupby
+from jinja2 import Markup
+from operator import attrgetter
+
+logger = logging.getLogger(__name__)
+
+
+def strftime(date, date_format):
+ '''
+ Replacement for built-in strftime
+
+ This is necessary because of the way Py2 handles date format strings.
+ Specifically, Py2 strftime takes a bytestring. In the case of text output
+ (e.g. %b, %a, etc), the output is encoded with an encoding defined by
+ locale.LC_TIME. Things get messy if the formatting string has chars that
+ are not valid in LC_TIME defined encoding.
+
+ This works by 'grabbing' possible format strings (those starting with %),
+ formatting them with the date, (if necessary) decoding the output and
+ replacing formatted output back.
+ '''
+
+ # grab candidate format options
+ format_options = '%.'
+ candidates = re.findall(format_options, date_format)
+
+ # replace candidates with placeholders for later % formatting
+ template = re.sub(format_options, '%s', date_format)
+
+ # we need to convert formatted dates back to unicode in Py2
+ # LC_TIME determines the encoding for built-in strftime outputs
+ lang_code, enc = locale.getlocale(locale.LC_TIME)
+
+ formatted_candidates = []
+ for candidate in candidates:
+ # test for valid C89 directives only
+ if candidate[1] in 'aAbBcdfHIjmMpSUwWxXyYzZ%':
+ formatted = date.strftime(candidate)
+ # convert Py2 result to unicode
+ if not six.PY3 and enc is not None:
+ formatted = formatted.decode(enc)
+ else:
+ formatted = candidate
+ formatted_candidates.append(formatted)
+
+ # put formatted candidates back and return
+ return template % tuple(formatted_candidates)
+
+
+class DateFormatter(object):
+ '''A date formatter object used as a jinja filter
+
+ Uses the `strftime` implementation and makes sure jinja uses the locale
+ defined in LOCALE setting
+ '''
+
+ def __init__(self):
+ self.locale = locale.setlocale(locale.LC_TIME)
+
+ def __call__(self, date, date_format):
+ old_locale = locale.setlocale(locale.LC_TIME)
+ locale.setlocale(locale.LC_TIME, self.locale)
+
+ formatted = strftime(date, date_format)
+
+ locale.setlocale(locale.LC_TIME, old_locale)
+ return formatted
+
+
+def python_2_unicode_compatible(klass):
+ """
+ A decorator that defines __unicode__ and __str__ methods under Python 2.
+ Under Python 3 it does nothing.
+
+ To support Python 2 and 3 with a single code base, define a __str__ method
+ returning text and apply this decorator to the class.
+
+ From django.utils.encoding.
+ """
+ if not six.PY3:
+ klass.__unicode__ = klass.__str__
+ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+ return klass
+
+
+class memoized(object):
+ """Function decorator to cache return values.
+
+ If called later with the same arguments, the cached value is returned
+ (not reevaluated).
+
+ """
+ def __init__(self, func):
+ self.func = func
+ self.cache = {}
+
+ def __call__(self, *args):
+ if not isinstance(args, Hashable):
+ # uncacheable. a list, for instance.
+ # better to not cache than blow up.
+ return self.func(*args)
+ if args in self.cache:
+ return self.cache[args]
+ else:
+ value = self.func(*args)
+ self.cache[args] = value
+ return value
+
+ def __repr__(self):
+ return self.func.__doc__
+
+ def __get__(self, obj, objtype):
+ '''Support instance methods.'''
+ return partial(self.__call__, obj)
+
+
+def deprecated_attribute(old, new, since=None, remove=None, doc=None):
+ """Attribute deprecation decorator for gentle upgrades
+
+ For example:
+
+ class MyClass (object):
+ @deprecated_attribute(
+ old='abc', new='xyz', since=(3, 2, 0), remove=(4, 1, 3))
+ def abc(): return None
+
+ def __init__(self):
+ xyz = 5
+
+ Note that the decorator needs a dummy method to attach to, but the
+ content of the dummy method is ignored.
+ """
+ def _warn():
+ version = '.'.join(six.text_type(x) for x in since)
+ message = ['{} has been deprecated since {}'.format(old, version)]
+ if remove:
+ version = '.'.join(six.text_type(x) for x in remove)
+ message.append(
+ ' and will be removed by version {}'.format(version))
+ message.append('. Use {} instead.'.format(new))
+ logger.warning(''.join(message))
+ logger.debug(''.join(
+ six.text_type(x) for x in traceback.format_stack()))
+
+ def fget(self):
+ _warn()
+ return getattr(self, new)
+
+ def fset(self, value):
+ _warn()
+ setattr(self, new, value)
+
+ def decorator(dummy):
+ return property(fget=fget, fset=fset, doc=doc)
+
+ return decorator
+
+
+def get_date(string):
+ """Return a datetime object from a 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))
+
+
+class pelican_open(object):
+ """Open a file and return its content"""
+ def __init__(self, filename):
+ self.filename = filename
+
+ def __enter__(self):
+ with open(self.filename, encoding='utf-8') as infile:
+ content = infile.read()
+ if content[0] == BOM_UTF8.decode('utf8'):
+ content = content[1:]
+ return content
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ pass
+
+
+def slugify(value):
+ """
+ Normalizes string, converts to lowercase, removes non-alpha characters,
+ and converts spaces to hyphens.
+
+ Took from Django sources.
+ """
+ # TODO Maybe steal again from current Django 1.5dev
+ value = Markup(value).striptags()
+ # value must be unicode per se
+ import unicodedata
+ from unidecode import unidecode
+ # unidecode returns str in Py2 and 3, so in Py2 we have to make
+ # it unicode again
+ value = unidecode(value)
+ if isinstance(value, six.binary_type):
+ value = value.decode('ascii')
+ # still unicode
+ value = unicodedata.normalize('NFKD', value)
+ value = re.sub('[^\w\s-]', '', value).strip().lower()
+ value = re.sub('[-\s]+', '-', value)
+ # we want only ASCII chars
+ value = value.encode('ascii', 'ignore')
+ # but Pelican should generally use only unicode
+ return value.decode('ascii')
+
+
+def copy(path, source, destination, destination_path=None, overwrite=False):
+ """Copy path from origin to destination.
+
+ The function is able to copy either files or directories.
+
+ :param path: the path to be copied from the source to the destination
+ :param source: the source dir
+ :param destination: the destination dir
+ :param destination_path: the destination path (optional)
+ :param overwrite: whether to overwrite the destination if already exists
+ or not
+ """
+ if not destination_path:
+ destination_path = path
+
+ source_ = os.path.abspath(os.path.expanduser(os.path.join(source, path)))
+ destination_ = os.path.abspath(
+ os.path.expanduser(os.path.join(destination, destination_path)))
+
+ if os.path.isdir(source_):
+ try:
+ shutil.copytree(source_, destination_)
+ logger.info('copying %s to %s' % (source_, destination_))
+ except OSError:
+ if overwrite:
+ shutil.rmtree(destination_)
+ shutil.copytree(source_, destination_)
+ logger.info('replacement of %s with %s' % (source_,
+ destination_))
+
+ elif os.path.isfile(source_):
+ dest_dir = os.path.dirname(destination_)
+ if not os.path.exists(dest_dir):
+ os.makedirs(dest_dir)
+ shutil.copy(source_, destination_)
+ logger.info('copying %s to %s' % (source_, destination_))
+ else:
+ logger.warning('skipped copy %s to %s' % (source_, destination_))
+
+
+def clean_output_dir(path, retention):
+ """Remove all files from output directory except those in retention list"""
+
+ if not os.path.exists(path):
+ logger.debug("Directory already removed: %s" % path)
+ return
+
+ if not os.path.isdir(path):
+ try:
+ os.remove(path)
+ except Exception as e:
+ logger.error("Unable to delete file %s; %s" % (path, str(e)))
+ return
+
+ # remove existing content from output folder unless in retention list
+ for filename in os.listdir(path):
+ file = os.path.join(path, filename)
+ if any(filename == retain for retain in retention):
+ logger.debug("Skipping deletion; %s is on retention list: %s" \
+ % (filename, file))
+ elif os.path.isdir(file):
+ try:
+ shutil.rmtree(file)
+ logger.debug("Deleted directory %s" % file)
+ except Exception as e:
+ logger.error("Unable to delete directory %s; %s" % (
+ file, str(e)))
+ elif os.path.isfile(file) or os.path.islink(file):
+ try:
+ os.remove(file)
+ logger.debug("Deleted file/link %s" % file)
+ except Exception as e:
+ logger.error("Unable to delete file %s; %s" % (file, str(e)))
+ else:
+ logger.error("Unable to delete %s, file type unknown" % file)
+
+
+def get_relative_path(path):
+ """Return the relative path from the given path to the root path."""
+ components = split_all(path)
+ if len(components) <= 1:
+ return os.curdir
+ else:
+ parents = [os.pardir] * (len(components) - 1)
+ return os.path.join(*parents)
+
+
+def path_to_url(path):
+ """Return the URL corresponding to a given path."""
+ if os.sep == '/':
+ return path
+ else:
+ return '/'.join(split_all(path))
+
+
+def truncate_html_words(s, num, end_text='...'):
+ """Truncates HTML to a certain number of words.
+
+ (not counting tags and comments). Closes opened tags if they were correctly
+ closed in the given html. Takes an optional argument of what should be used
+ to notify that the string has been truncated, defaulting to ellipsis (...).
+
+ Newlines in the HTML are preserved. (From the django framework).
+ """
+ length = int(num)
+ if length <= 0:
+ return ''
+ html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area',
+ 'hr', 'input')
+
+ # Set up regular expressions
+ re_words = re.compile(r'&.*?;|<.*?>|(\w[\w-]*)', re.U)
+ re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>')
+ # Count non-HTML words and keep note of open tags
+ pos = 0
+ end_text_pos = 0
+ words = 0
+ open_tags = []
+ while words <= length:
+ m = re_words.search(s, pos)
+ if not m:
+ # Checked through whole string
+ break
+ pos = m.end(0)
+ if m.group(1):
+ # It's an actual non-HTML word
+ words += 1
+ if words == length:
+ end_text_pos = pos
+ continue
+ # Check for tag
+ tag = re_tag.match(m.group(0))
+ if not tag or end_text_pos:
+ # Don't worry about non tags or tags after our truncate point
+ continue
+ closing_tag, tagname, self_closing = tag.groups()
+ tagname = tagname.lower() # Element names are always case-insensitive
+ if self_closing or tagname in html4_singlets:
+ pass
+ elif closing_tag:
+ # Check for match in open tags list
+ try:
+ i = open_tags.index(tagname)
+ except ValueError:
+ pass
+ else:
+ # SGML: An end tag closes, back to the matching start tag,
+ # all unclosed intervening start tags with omitted end tags
+ open_tags = open_tags[i + 1:]
+ else:
+ # Add it to the start of the open tags list
+ open_tags.insert(0, tagname)
+ if words <= length:
+ # Don't try to close tags if we don't need to truncate
+ return s
+ out = s[:end_text_pos]
+ if end_text:
+ out += ' ' + end_text
+ # Close any tags still open
+ for tag in open_tags:
+ out += '%s>' % tag
+ # Return string
+ return out
+
+
+def process_translations(content_list):
+ """ Finds translation and returns them.
+
+ Returns a tuple with two lists (index, translations). Index list includes
+ items in default language or items which have no variant in default
+ language. Items with the `translation` metadata set to something else than
+ `False` or `false` will be used as translations, unless all the items with
+ the same slug have that metadata.
+
+ For each content_list item, sets the 'translations' attribute.
+ """
+ content_list.sort(key=attrgetter('slug'))
+ grouped_by_slugs = groupby(content_list, attrgetter('slug'))
+ index = []
+ translations = []
+
+ for slug, items in grouped_by_slugs:
+ items = list(items)
+ # items with `translation` metadata will be used as translations…
+ default_lang_items = list(filter(
+ lambda i: i.metadata.get('translation', 'false').lower()
+ == 'false',
+ items))
+ # …unless all items with that slug are translations
+ if not default_lang_items:
+ default_lang_items = items
+
+ # display warnings if several items have the same lang
+ for lang, lang_items in groupby(items, attrgetter('lang')):
+ lang_items = list(lang_items)
+ len_ = len(lang_items)
+ if len_ > 1:
+ logger.warning('There are %s variants of "%s" with lang %s' \
+ % (len_, slug, lang))
+ for x in lang_items:
+ logger.warning(' %s' % x.source_path)
+
+ # find items with default language
+ default_lang_items = list(filter(attrgetter('in_default_lang'),
+ default_lang_items))
+
+ # if there is no article with default language, take an other one
+ if not default_lang_items:
+ default_lang_items = items[:1]
+
+ if not slug:
+ logger.warning((
+ 'empty slug for {!r}. '
+ 'You can fix this by adding a title or a slug to your '
+ 'content'
+ ).format(default_lang_items[0].source_path))
+ index.extend(default_lang_items)
+ translations.extend([x for x in items if x not in default_lang_items])
+ for a in items:
+ a.translations = [x for x in items if x != a]
+ return index, translations
+
+
+def folder_watcher(path, extensions, ignores=[]):
+ '''Generator for monitoring a folder for modifications.
+
+ Returns a boolean indicating if files are changed since last check.
+ Returns None if there are no matching files in the folder'''
+
+ def file_times(path):
+ '''Return `mtime` for each file in path'''
+
+ for root, dirs, files in os.walk(path):
+ dirs[:] = [x for x in dirs if not x.startswith(os.curdir)]
+
+ for f in files:
+ if (f.endswith(tuple(extensions)) and
+ not any(fnmatch.fnmatch(f, ignore) for ignore in ignores)):
+ try:
+ yield os.stat(os.path.join(root, f)).st_mtime
+ except OSError as e:
+ logger.warning('Caught Exception: {}'.format(e))
+
+ LAST_MTIME = 0
+ while True:
+ try:
+ mtime = max(file_times(path))
+ if mtime > LAST_MTIME:
+ LAST_MTIME = mtime
+ yield True
+ except ValueError:
+ yield None
+ else:
+ yield False
+
+
+def file_watcher(path):
+ '''Generator for monitoring a file for modifications'''
+ LAST_MTIME = 0
+ while True:
+ if path:
+ try:
+ mtime = os.stat(path).st_mtime
+ except OSError as e:
+ logger.warning('Caught Exception: {}'.format(e))
+ continue
+
+ if mtime > LAST_MTIME:
+ LAST_MTIME = mtime
+ yield True
+ else:
+ yield False
+ else:
+ yield None
+
+
+def set_date_tzinfo(d, tz_name=None):
+ """Set the timezone for dates that don't have tzinfo"""
+ if tz_name and not d.tzinfo:
+ tz = pytz.timezone(tz_name)
+ return tz.localize(d)
+ return d
+
+
+def mkdir_p(path):
+ try:
+ os.makedirs(path)
+ except OSError as e:
+ if e.errno != errno.EEXIST or not os.path.isdir(path):
+ raise
+
+
+def split_all(path):
+ """Split a path into a list of components
+
+ While os.path.split() splits a single component off the back of
+ `path`, this function splits all components:
+
+ >>> split_all(os.path.join('a', 'b', 'c'))
+ ['a', 'b', 'c']
+ """
+ components = []
+ path = path.lstrip('/')
+ while path:
+ head, tail = os.path.split(path)
+ if tail:
+ components.insert(0, tail)
+ elif head == path:
+ components.insert(0, head)
+ break
+ path = head
+ return components
diff --git a/pelican/writers.py b/pelican/writers.py
new file mode 100644
index 00000000..fe37b25d
--- /dev/null
+++ b/pelican/writers.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+from __future__ import with_statement, unicode_literals, print_function
+import six
+
+import os
+import locale
+import logging
+
+if not six.PY3:
+ from codecs import open
+
+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
+
+logger = logging.getLogger(__name__)
+
+
+class Writer(object):
+
+ def __init__(self, output_path, settings=None):
+ self.output_path = output_path
+ self.reminder = dict()
+ self.settings = settings or {}
+ self._written_files = set()
+
+ def _create_new_feed(self, feed_type, context):
+ feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed
+ sitename = Markup(context['SITENAME']).striptags()
+ feed = feed_class(
+ title=sitename,
+ link=(self.site_url + '/'),
+ feed_url=self.feed_url,
+ description=context.get('SITESUBTITLE', ''))
+ return feed
+
+ def _add_item_to_the_feed(self, feed, item):
+
+ title = Markup(item.title).striptags()
+ feed.add_item(
+ title=title,
+ link='%s/%s' % (self.site_url, item.url),
+ unique_id='tag:%s,%s:%s' % (self.site_url.replace('http://', ''),
+ item.date.date(), item.url),
+ description=item.get_content(self.site_url),
+ categories=item.tags if hasattr(item, 'tags') else None,
+ author_name=getattr(item, 'author', ''),
+ pubdate=set_date_tzinfo(item.date,
+ self.settings.get('TIMEZONE', None)))
+
+ def _open_w(self, filename, encoding):
+ """Open a file to write some content to it.
+
+ Exit if we have already written to that file.
+ """
+ if filename in self._written_files:
+ raise IOError('File %s is to be overwritten' % filename)
+ self._written_files.add(filename)
+ return open(filename, 'w', encoding=encoding)
+
+ def write_feed(self, elements, context, path=None, feed_type='atom'):
+ """Generate a feed with the list of articles provided
+
+ Return the feed. If no path or output_path is specified, just
+ return the feed object.
+
+ :param elements: the articles to put on the feed.
+ :param context: the context to get the feed metadata.
+ :param path: the path to output.
+ :param feed_type: the feed type to use (atom or rss)
+ """
+ old_locale = locale.setlocale(locale.LC_ALL)
+ locale.setlocale(locale.LC_ALL, str('C'))
+ try:
+ self.site_url = context.get(
+ 'SITEURL', path_to_url(get_relative_path(path)))
+
+ self.feed_domain = context.get('FEED_DOMAIN')
+ self.feed_url = '{}/{}'.format(self.feed_domain, path)
+
+ feed = self._create_new_feed(feed_type, context)
+
+ max_items = len(elements)
+ if self.settings['FEED_MAX_ITEMS']:
+ max_items = min(self.settings['FEED_MAX_ITEMS'], max_items)
+ for i in range(max_items):
+ self._add_item_to_the_feed(feed, elements[i])
+
+ if path:
+ complete_path = os.path.join(self.output_path, path)
+ try:
+ os.makedirs(os.path.dirname(complete_path))
+ except Exception:
+ pass
+
+ encoding = 'utf-8' if six.PY3 else None
+ with self._open_w(complete_path, encoding) as fp:
+ feed.write(fp, 'utf-8')
+ logger.info('writing %s' % complete_path)
+ return feed
+ finally:
+ locale.setlocale(locale.LC_ALL, old_locale)
+
+ def write_file(self, name, template, context, relative_urls=False,
+ paginated=None, **kwargs):
+ """Render the template and write the file.
+
+ :param name: name of the file to output
+ :param template: template to use to generate the content
+ :param context: dict to pass to the templates.
+ :param relative_urls: use relative urls or absolutes ones
+ :param paginated: dict of article list to paginate - must have the
+ same length (same list in different orders)
+ :param **kwargs: additional variables to pass to the templates
+ """
+
+ if name is False:
+ return
+ elif not name:
+ # other stuff, just return for now
+ return
+
+ def _write_file(template, localcontext, output_path, name):
+ """Render the template write the file."""
+ old_locale = locale.setlocale(locale.LC_ALL)
+ locale.setlocale(locale.LC_ALL, str('C'))
+ try:
+ output = template.render(localcontext)
+ finally:
+ locale.setlocale(locale.LC_ALL, old_locale)
+ path = os.path.join(output_path, name)
+ try:
+ os.makedirs(os.path.dirname(path))
+ except Exception:
+ pass
+ with self._open_w(path, 'utf-8') as f:
+ f.write(output)
+ logger.info('writing {}'.format(path))
+
+ localcontext = context.copy()
+ if relative_urls:
+ relative_url = path_to_url(get_relative_path(name))
+ context['localsiteurl'] = relative_url
+ localcontext['SITEURL'] = relative_url
+
+ localcontext['output_file'] = name
+ localcontext.update(kwargs)
+
+ # check paginated
+ paginated = paginated or {}
+ if paginated:
+ # pagination needed, init paginators
+ paginators = {}
+ for key in paginated.keys():
+ object_list = paginated[key]
+
+ if self.settings['DEFAULT_PAGINATION']:
+ paginators[key] = Paginator(object_list,
+ self.settings['DEFAULT_PAGINATION'],
+ self.settings['DEFAULT_ORPHANS'])
+ else:
+ paginators[key] = Paginator(object_list, len(object_list))
+
+ # generated pages, and write
+ name_root, ext = os.path.splitext(name)
+ for page_num in range(list(paginators.values())[0].num_pages):
+ paginated_localcontext = localcontext.copy()
+ for key in paginators.keys():
+ paginator = paginators[key]
+ page = paginator.page(page_num + 1)
+ paginated_localcontext.update(
+ {'%s_paginator' % key: paginator,
+ '%s_page' % key: page})
+ if page_num > 0:
+ paginated_name = '%s%s%s' % (
+ name_root, page_num + 1, ext)
+ else:
+ paginated_name = name
+
+ _write_file(template, paginated_localcontext, self.output_path,
+ paginated_name)
+ else:
+ # no pagination
+ _write_file(template, localcontext, self.output_path, name)
diff --git a/samples/content/2012-11-30_filename-metadata.rst b/samples/content/2012-11-30_filename-metadata.rst
new file mode 100644
index 00000000..b048103d
--- /dev/null
+++ b/samples/content/2012-11-30_filename-metadata.rst
@@ -0,0 +1,4 @@
+FILENAME_METADATA example
+#########################
+
+Some cool stuff!
diff --git a/samples/content/another_super_article-fr.rst b/samples/content/another_super_article-fr.rst
new file mode 100644
index 00000000..71ac9635
--- /dev/null
+++ b/samples/content/another_super_article-fr.rst
@@ -0,0 +1,7 @@
+Trop bien !
+###########
+
+:lang: fr
+:slug: oh-yeah
+
+Et voila du contenu en français
diff --git a/samples/content/another_super_article.rst b/samples/content/another_super_article.rst
new file mode 100644
index 00000000..e6e0a92c
--- /dev/null
+++ b/samples/content/another_super_article.rst
@@ -0,0 +1,20 @@
+Oh yeah !
+#########
+
+:tags: oh, bar, yeah
+:date: 2010-10-20 10:14
+:category: bar
+:author: Alexis Métaireau
+:slug: oh-yeah
+:license: WTFPL
+
+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 !
+
+.. image:: |filename|/pictures/Sushi.jpg
+ :height: 450 px
+ :width: 600 px
+ :alt: alternate text
diff --git a/samples/content/article2-fr.rst b/samples/content/article2-fr.rst
new file mode 100644
index 00000000..31970f7e
--- /dev/null
+++ b/samples/content/article2-fr.rst
@@ -0,0 +1,9 @@
+Deuxième article
+################
+
+:tags: foo, bar, baz
+:date: 2012-02-29
+:lang: fr
+:slug: second-article
+
+Ceci est un article, en français.
diff --git a/samples/content/article2.rst b/samples/content/article2.rst
new file mode 100644
index 00000000..d07ddc8e
--- /dev/null
+++ b/samples/content/article2.rst
@@ -0,0 +1,10 @@
+Second article
+##############
+
+:tags: foo, bar, baz
+:date: 2012-02-29
+:lang: en
+:slug: second-article
+:authors: babar, celestine
+
+This is some article, in english
diff --git a/samples/content/cat1/article1.rst b/samples/content/cat1/article1.rst
new file mode 100644
index 00000000..1148a8f9
--- /dev/null
+++ b/samples/content/cat1/article1.rst
@@ -0,0 +1,7 @@
+Article 1
+#########
+
+:date: 2011-02-17
+:yeah: oh yeah !
+
+Article 1
diff --git a/samples/content/cat1/article2.rst b/samples/content/cat1/article2.rst
new file mode 100644
index 00000000..a4f87866
--- /dev/null
+++ b/samples/content/cat1/article2.rst
@@ -0,0 +1,6 @@
+Article 2
+#########
+
+:date: 2011-02-17
+
+Article 2
diff --git a/samples/content/cat1/article3.rst b/samples/content/cat1/article3.rst
new file mode 100644
index 00000000..53471177
--- /dev/null
+++ b/samples/content/cat1/article3.rst
@@ -0,0 +1,6 @@
+Article 3
+#########
+
+:date: 2011-02-17
+
+Article 3
diff --git a/samples/content/cat1/markdown-article.md b/samples/content/cat1/markdown-article.md
new file mode 100644
index 00000000..5307b47a
--- /dev/null
+++ b/samples/content/cat1/markdown-article.md
@@ -0,0 +1,7 @@
+Title: A markdown powered article
+Date: 2011-04-20
+
+You're mutually oblivious.
+
+[a root-relative link to unbelievable](|filename|/unbelievable.rst)
+[a file-relative link to unbelievable](|filename|../unbelievable.rst)
diff --git a/samples/content/draft_article.rst b/samples/content/draft_article.rst
new file mode 100644
index 00000000..76ce9a16
--- /dev/null
+++ b/samples/content/draft_article.rst
@@ -0,0 +1,7 @@
+A draft article
+###############
+
+:status: draft
+
+This is a draft article, it should live under the /drafts/ folder and not be
+listed anywhere else.
diff --git a/samples/content/extra/robots.txt b/samples/content/extra/robots.txt
new file mode 100644
index 00000000..19a6e299
--- /dev/null
+++ b/samples/content/extra/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /pictures
diff --git a/samples/content/pages/hidden_page.rst b/samples/content/pages/hidden_page.rst
new file mode 100644
index 00000000..ab8704ed
--- /dev/null
+++ b/samples/content/pages/hidden_page.rst
@@ -0,0 +1,9 @@
+This is a test hidden page
+##########################
+
+:category: test
+:status: hidden
+
+This is great for things like error(404) pages
+Anyone can see this page but it's not linked to anywhere!
+
diff --git a/samples/content/pages/jinja2_template.html b/samples/content/pages/jinja2_template.html
new file mode 100644
index 00000000..1b0dc4e4
--- /dev/null
+++ b/samples/content/pages/jinja2_template.html
@@ -0,0 +1,6 @@
+{% extends "base.html" %}
+{% block content %}
+
+Some text
+
+{% endblock %}
diff --git a/samples/content/pages/override_url_saveas.rst b/samples/content/pages/override_url_saveas.rst
new file mode 100644
index 00000000..8a515f60
--- /dev/null
+++ b/samples/content/pages/override_url_saveas.rst
@@ -0,0 +1,9 @@
+Override url/save_as
+####################
+
+:date: 2012-12-07
+:url: override/
+:save_as: override/index.html
+
+Test page which overrides save_as and url so that this page will be generated
+at a custom location.
diff --git a/samples/content/pages/test_page.rst b/samples/content/pages/test_page.rst
new file mode 100644
index 00000000..2285f17b
--- /dev/null
+++ b/samples/content/pages/test_page.rst
@@ -0,0 +1,12 @@
+This is a test page
+###################
+
+:category: test
+
+Just an image.
+
+.. image:: |filename|/pictures/Fat_Cat.jpg
+ :height: 450 px
+ :width: 600 px
+ :alt: alternate text
+
diff --git a/samples/content/pictures/Fat_Cat.jpg b/samples/content/pictures/Fat_Cat.jpg
new file mode 100644
index 00000000..d8a96d35
Binary files /dev/null and b/samples/content/pictures/Fat_Cat.jpg differ
diff --git a/samples/content/pictures/Sushi.jpg b/samples/content/pictures/Sushi.jpg
new file mode 100644
index 00000000..e49e5f0a
Binary files /dev/null and b/samples/content/pictures/Sushi.jpg differ
diff --git a/samples/content/pictures/Sushi_Macro.jpg b/samples/content/pictures/Sushi_Macro.jpg
new file mode 100644
index 00000000..21f935a1
Binary files /dev/null and b/samples/content/pictures/Sushi_Macro.jpg differ
diff --git a/samples/content/super_article.rst b/samples/content/super_article.rst
new file mode 100644
index 00000000..76e57683
--- /dev/null
+++ b/samples/content/super_article.rst
@@ -0,0 +1,36 @@
+This is a super article !
+#########################
+
+:tags: foo, bar, foobar
+:date: 2010-12-02 10:14
+:category: yeah
+:author: Alexis Métaireau
+:summary:
+ Multi-line metadata should be supported
+ as well as **inline markup**.
+
+Some content here !
+
+This is a simple title
+======================
+
+And here comes the cool stuff_.
+
+.. image:: |filename|/pictures/Sushi.jpg
+ :height: 450 px
+ :width: 600 px
+ :alt: alternate text
+
+.. image:: |filename|/pictures/Sushi_Macro.jpg
+ :height: 450 px
+ :width: 600 px
+ :alt: alternate text
+
+::
+
+ >>> from ipdb import set_trace
+ >>> set_trace()
+
+→ And now try with some utf8 hell: ééé
+
+.. _stuff: http://books.couchdb.org/relax/design-documents/views
diff --git a/samples/content/unbelievable.rst b/samples/content/unbelievable.rst
new file mode 100644
index 00000000..20cb9dc7
--- /dev/null
+++ b/samples/content/unbelievable.rst
@@ -0,0 +1,9 @@
+Unbelievable !
+##############
+
+:date: 2010-10-15 20:30
+
+Or completely awesome. Depends the needs.
+
+`a root-relative link to markdown-article <|filename|/cat1/markdown-article.md>`_
+`a file-relative link to markdown-article <|filename|cat1/markdown-article.md>`_
diff --git a/samples/content/unwanted_file b/samples/content/unwanted_file
new file mode 100644
index 00000000..591255ae
--- /dev/null
+++ b/samples/content/unwanted_file
@@ -0,0 +1 @@
+not to be parsed
diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py
new file mode 100755
index 00000000..4d5cd06d
--- /dev/null
+++ b/samples/pelican.conf.py
@@ -0,0 +1,53 @@
+# -*- 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 = "C"
+DEFAULT_PAGINATION = 4
+DEFAULT_DATE = (2012, 3, 2, 14, 1, 1)
+
+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'}
+
+# foobar will not be used, because it's not in caps. All configuration keys
+# have to be in caps
+foobar = "barbaz"
diff --git a/setup.py b/setup.py
new file mode 100755
index 00000000..83beb461
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+from setuptools import setup
+
+requires = ['feedgenerator >= 1.6', 'jinja2 >= 2.6', 'pygments', 'docutils',
+ 'pytz', 'blinker', 'unidecode', 'six']
+
+entry_points = {
+ 'console_scripts': [
+ 'pelican = pelican:main',
+ 'pelican-import = pelican.tools.pelican_import:main',
+ 'pelican-quickstart = pelican.tools.pelican_quickstart:main',
+ 'pelican-themes = pelican.tools.pelican_themes:main'
+ ]
+}
+
+
+README = open('README.rst').read()
+CHANGELOG = open('docs/changelog.rst').read()
+
+
+setup(
+ name="pelican",
+ version="3.3",
+ url='http://getpelican.com/',
+ author='Alexis Metaireau',
+ author_email='authors@getpelican.com',
+ description="A tool to generate a static blog from reStructuredText or "
+ "Markdown input files.",
+ long_description=README + '\n' + CHANGELOG,
+ packages=['pelican', 'pelican.tools'],
+ include_package_data=True,
+ install_requires=requires,
+ entry_points=entry_points,
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Console',
+ 'License :: OSI Approved :: GNU Affero General Public License v3',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.3',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ],
+ test_suite='pelican.tests',
+)
diff --git a/static/css/in.css b/static/css/in.css
deleted file mode 100644
index 041b7b4a..00000000
--- a/static/css/in.css
+++ /dev/null
@@ -1,108 +0,0 @@
-@tailwind base; /* Preflight is injected here */
-@tailwind components;
-@tailwind utilities;
-
-@layer base {
- @font-face {
- /*https://style64.org/c64-truetype*/
- font-family: "C64 Pro Mono";
- font-weight: 400;
- src: url(/theme/font/C64_Pro_Mono-STYLE.woff2) format("woff");
- }
-
- html {
- /* text-lg == prose-lg */
- /* text-xl == prose-xl */
- @apply text-lg md:text-lg lg:text-xl font-texts text-rp-dawn-text dark:text-rp-moon-iris;
- }
- text {
- @apply text-rp-dawn-text dark:text-rp-moon-iris;
- }
- a {
- @apply underline decoration-2 underline-offset-4 decoration-rp-dawn-gold dark:decoration-rp-moon-pine;
- }
- a:hover {
- @apply underline decoration-2 underline-offset-4 decoration-rp-dawn-foam dark:decoration-rp-moon-rose;
- }
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- @apply font-headings text-rp-dawn-text dark:text-rp-moon-love;
- }
- h1 {
- @apply text-5xl md:text-7xl mb-9;
- }
- h2 {
- @apply text-4xl md:text-5xl mb-9;
- }
- h3 {
- @apply text-3xl md:text-4xl;
- }
- /*for unknown reasons, h4 must be undefined to not make it bigger than h3*/
- /*h5-h6 are ignored by tailwind typography*/
- img {
- @apply drop-shadow-lg;
- }
- dd {
- @apply ml-12 md:ml-24;
- }
- ul {
- @apply pl-4 list-disc;
- }
-
- /* pelican tag_cloud */
- ul.tagcloud {
- @apply list-none p-0;
- }
- ul.tagcloud li {
- @apply inline-block p-1;
- }
-}
-
-@layer components {
- /*This does not seem to affect prismjs which is good*/
- code {
- @apply bg-rp-dawn-highlight-low dark:bg-rp-moon-highlight-low;
- }
-}
-
-/*Hide heading anchor links unless hovering over them*/
-.headerlink {
- @apply no-underline text-rp-dawn-love ml-1 md:ml-2 hover:no-underline;
-}
-
-.note {
- @apply bg-rp-dawn-highlight-med dark:bg-rp-moon-highlight-med m-8 p-4;
-}
-.warn {
- @apply bg-rp-dawn-text text-rp-dawn-base dark:bg-rp-moon-text dark:text-rp-moon-base m-8 p-4;
-}
-
-/*Attempt to only float thumbnails to the right*/
-.image-process-article-image,
-.image-process-thumb,
-.image-process-responsive {
- /*@apply lg:float-right p-2 lg:p-4;*/
- @apply lg:float-right px-1 lg:px-3;
-}
-
-#skiptocontent a {
- position: absolute;
- top: -40px;
- left: 0px;
- /*color: white;*/
- /*border-right: 1px solid white;*/
- /*border-bottom: 1px solid white;*/
- z-index: 100;
- @apply bg-rp-dawn-text text-rp-dawn-surface dark:bg-rp-moon-text dark:text-rp-moon-surface;
-}
-
-#skiptocontent a:focus {
- position: absolute;
- left: 0px;
- top: 0px;
- outline-color: transparent;
-}
diff --git a/static/css/out.css b/static/css/out.css
deleted file mode 100644
index e1870e6c..00000000
--- a/static/css/out.css
+++ /dev/null
@@ -1,2318 +0,0 @@
-*, ::before, ::after {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
-}
-
-::backdrop {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
-}
-
-/*
-! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
-*/
-
-/*
-1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
-2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
-*/
-
-*,
-::before,
-::after {
- box-sizing: border-box;
- /* 1 */
- border-width: 0;
- /* 2 */
- border-style: solid;
- /* 2 */
- border-color: #e5e7eb;
- /* 2 */
-}
-
-::before,
-::after {
- --tw-content: '';
-}
-
-/*
-1. Use a consistent sensible line-height in all browsers.
-2. Prevent adjustments of font size after orientation changes in iOS.
-3. Use a more readable tab size.
-4. Use the user's configured `sans` font-family by default.
-5. Use the user's configured `sans` font-feature-settings by default.
-6. Use the user's configured `sans` font-variation-settings by default.
-7. Disable tap highlights on iOS
-*/
-
-html,
-:host {
- line-height: 1.5;
- /* 1 */
- -webkit-text-size-adjust: 100%;
- /* 2 */
- -moz-tab-size: 4;
- /* 3 */
- -o-tab-size: 4;
- tab-size: 4;
- /* 3 */
- font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
- /* 4 */
- font-feature-settings: normal;
- /* 5 */
- font-variation-settings: normal;
- /* 6 */
- -webkit-tap-highlight-color: transparent;
- /* 7 */
-}
-
-/*
-1. Remove the margin in all browsers.
-2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
-*/
-
-body {
- margin: 0;
- /* 1 */
- line-height: inherit;
- /* 2 */
-}
-
-/*
-1. Add the correct height in Firefox.
-2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
-3. Ensure horizontal rules are visible by default.
-*/
-
-hr {
- height: 0;
- /* 1 */
- color: inherit;
- /* 2 */
- border-top-width: 1px;
- /* 3 */
-}
-
-/*
-Add the correct text decoration in Chrome, Edge, and Safari.
-*/
-
-abbr:where([title]) {
- -webkit-text-decoration: underline dotted;
- text-decoration: underline dotted;
-}
-
-/*
-Remove the default font size and weight for headings.
-*/
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- font-size: inherit;
- font-weight: inherit;
-}
-
-/*
-Reset links to optimize for opt-in styling instead of opt-out.
-*/
-
-a {
- color: inherit;
- text-decoration: inherit;
-}
-
-/*
-Add the correct font weight in Edge and Safari.
-*/
-
-b,
-strong {
- font-weight: bolder;
-}
-
-/*
-1. Use the user's configured `mono` font-family by default.
-2. Use the user's configured `mono` font-feature-settings by default.
-3. Use the user's configured `mono` font-variation-settings by default.
-4. Correct the odd `em` font sizing in all browsers.
-*/
-
-code,
-kbd,
-samp,
-pre {
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- /* 1 */
- font-feature-settings: normal;
- /* 2 */
- font-variation-settings: normal;
- /* 3 */
- font-size: 1em;
- /* 4 */
-}
-
-/*
-Add the correct font size in all browsers.
-*/
-
-small {
- font-size: 80%;
-}
-
-/*
-Prevent `sub` and `sup` elements from affecting the line height in all browsers.
-*/
-
-sub,
-sup {
- font-size: 75%;
- line-height: 0;
- position: relative;
- vertical-align: baseline;
-}
-
-sub {
- bottom: -0.25em;
-}
-
-sup {
- top: -0.5em;
-}
-
-/*
-1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
-2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
-3. Remove gaps between table borders by default.
-*/
-
-table {
- text-indent: 0;
- /* 1 */
- border-color: inherit;
- /* 2 */
- border-collapse: collapse;
- /* 3 */
-}
-
-/*
-1. Change the font styles in all browsers.
-2. Remove the margin in Firefox and Safari.
-3. Remove default padding in all browsers.
-*/
-
-button,
-input,
-optgroup,
-select,
-textarea {
- font-family: inherit;
- /* 1 */
- font-feature-settings: inherit;
- /* 1 */
- font-variation-settings: inherit;
- /* 1 */
- font-size: 100%;
- /* 1 */
- font-weight: inherit;
- /* 1 */
- line-height: inherit;
- /* 1 */
- letter-spacing: inherit;
- /* 1 */
- color: inherit;
- /* 1 */
- margin: 0;
- /* 2 */
- padding: 0;
- /* 3 */
-}
-
-/*
-Remove the inheritance of text transform in Edge and Firefox.
-*/
-
-button,
-select {
- text-transform: none;
-}
-
-/*
-1. Correct the inability to style clickable types in iOS and Safari.
-2. Remove default button styles.
-*/
-
-button,
-input:where([type='button']),
-input:where([type='reset']),
-input:where([type='submit']) {
- -webkit-appearance: button;
- /* 1 */
- background-color: transparent;
- /* 2 */
- background-image: none;
- /* 2 */
-}
-
-/*
-Use the modern Firefox focus style for all focusable elements.
-*/
-
-:-moz-focusring {
- outline: auto;
-}
-
-/*
-Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
-*/
-
-:-moz-ui-invalid {
- box-shadow: none;
-}
-
-/*
-Add the correct vertical alignment in Chrome and Firefox.
-*/
-
-progress {
- vertical-align: baseline;
-}
-
-/*
-Correct the cursor style of increment and decrement buttons in Safari.
-*/
-
-::-webkit-inner-spin-button,
-::-webkit-outer-spin-button {
- height: auto;
-}
-
-/*
-1. Correct the odd appearance in Chrome and Safari.
-2. Correct the outline style in Safari.
-*/
-
-[type='search'] {
- -webkit-appearance: textfield;
- /* 1 */
- outline-offset: -2px;
- /* 2 */
-}
-
-/*
-Remove the inner padding in Chrome and Safari on macOS.
-*/
-
-::-webkit-search-decoration {
- -webkit-appearance: none;
-}
-
-/*
-1. Correct the inability to style clickable types in iOS and Safari.
-2. Change font properties to `inherit` in Safari.
-*/
-
-::-webkit-file-upload-button {
- -webkit-appearance: button;
- /* 1 */
- font: inherit;
- /* 2 */
-}
-
-/*
-Add the correct display in Chrome and Safari.
-*/
-
-summary {
- display: list-item;
-}
-
-/*
-Removes the default spacing and border for appropriate elements.
-*/
-
-blockquote,
-dl,
-dd,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-hr,
-figure,
-p,
-pre {
- margin: 0;
-}
-
-fieldset {
- margin: 0;
- padding: 0;
-}
-
-legend {
- padding: 0;
-}
-
-ol,
-ul,
-menu {
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-/*
-Reset default styling for dialogs.
-*/
-
-dialog {
- padding: 0;
-}
-
-/*
-Prevent resizing textareas horizontally by default.
-*/
-
-textarea {
- resize: vertical;
-}
-
-/*
-1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
-2. Set the default placeholder color to the user's configured gray 400 color.
-*/
-
-input::-moz-placeholder, textarea::-moz-placeholder {
- opacity: 1;
- /* 1 */
- color: #9ca3af;
- /* 2 */
-}
-
-input::placeholder,
-textarea::placeholder {
- opacity: 1;
- /* 1 */
- color: #9ca3af;
- /* 2 */
-}
-
-/*
-Set the default cursor for buttons.
-*/
-
-button,
-[role="button"] {
- cursor: pointer;
-}
-
-/*
-Make sure disabled buttons don't get the pointer cursor.
-*/
-
-:disabled {
- cursor: default;
-}
-
-/*
-1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
-2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
- This can trigger a poorly considered lint error in some tools but is included by design.
-*/
-
-img,
-svg,
-video,
-canvas,
-audio,
-iframe,
-embed,
-object {
- display: block;
- /* 1 */
- vertical-align: middle;
- /* 2 */
-}
-
-/*
-Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
-*/
-
-img,
-video {
- max-width: 100%;
- height: auto;
-}
-
-/* Make elements with the HTML hidden attribute stay hidden by default */
-
-[hidden]:where(:not([hidden="until-found"])) {
- display: none;
-}
-
-@font-face {
- /*https://style64.org/c64-truetype*/
-
- font-family: "C64 Pro Mono";
-
- font-weight: 400;
-
- src: url(/theme/font/C64_Pro_Mono-STYLE.woff2) format("woff");
-}
-
-html {
- /* text-lg == prose-lg */
- /* text-xl == prose-xl */
- font-family: Erode, serif;
- font-size: 1.125rem;
- line-height: 1.75rem;
- --tw-text-opacity: 1;
- color: rgb(87 82 121 / var(--tw-text-opacity, 1));
-}
-
-@media (min-width: 768px) {
- html {
- font-size: 1.125rem;
- line-height: 1.75rem;
- }
-}
-
-@media (min-width: 1024px) {
- html {
- font-size: 1.25rem;
- line-height: 1.75rem;
- }
-}
-
-@media (prefers-color-scheme: dark) {
- html {
- --tw-text-opacity: 1;
- color: rgb(196 167 231 / var(--tw-text-opacity, 1));
- }
-}
-
-text {
- --tw-text-opacity: 1;
- color: rgb(87 82 121 / var(--tw-text-opacity, 1));
-}
-
-@media (prefers-color-scheme: dark) {
- text {
- --tw-text-opacity: 1;
- color: rgb(196 167 231 / var(--tw-text-opacity, 1));
- }
-}
-
-a {
- text-decoration-line: underline;
- text-decoration-color: #ea9d34;
- text-decoration-thickness: 2px;
- text-underline-offset: 4px;
-}
-
-@media (prefers-color-scheme: dark) {
- a {
- text-decoration-color: #3e8fb0;
- }
-}
-
-a:hover {
- text-decoration-line: underline;
- text-decoration-color: #56949f;
- text-decoration-thickness: 2px;
- text-underline-offset: 4px;
-}
-
-@media (prefers-color-scheme: dark) {
- a:hover {
- text-decoration-color: #ea9a97;
- }
-}
-
-h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- font-family: Fira Sans, sans-serif;
- --tw-text-opacity: 1;
- color: rgb(87 82 121 / var(--tw-text-opacity, 1));
-}
-
-@media (prefers-color-scheme: dark) {
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- --tw-text-opacity: 1;
- color: rgb(235 111 146 / var(--tw-text-opacity, 1));
- }
-}
-
-h1 {
- margin-bottom: 2.25rem;
- font-size: 3rem;
- line-height: 1;
-}
-
-@media (min-width: 768px) {
- h1 {
- font-size: 4.5rem;
- line-height: 1;
- }
-}
-
-h2 {
- margin-bottom: 2.25rem;
- font-size: 2.25rem;
- line-height: 2.5rem;
-}
-
-@media (min-width: 768px) {
- h2 {
- font-size: 3rem;
- line-height: 1;
- }
-}
-
-h3 {
- font-size: 1.875rem;
- line-height: 2.25rem;
-}
-
-@media (min-width: 768px) {
- h3 {
- font-size: 2.25rem;
- line-height: 2.5rem;
- }
-}
-
-/*for unknown reasons, h4 must be undefined to not make it bigger than h3*/
-
-/*h5-h6 are ignored by tailwind typography*/
-
-img {
- --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1));
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-
-dd {
- margin-left: 3rem;
-}
-
-@media (min-width: 768px) {
- dd {
- margin-left: 6rem;
- }
-}
-
-ul {
- list-style-type: disc;
- padding-left: 1rem;
-}
-
-/* pelican tag_cloud */
-
-ul.tagcloud {
- list-style-type: none;
- padding: 0px;
-}
-
-ul.tagcloud li {
- display: inline-block;
- padding: 0.25rem;
-}
-
-/* Preflight is injected here */
-
-.prose {
- color: #575279;
- max-width: 80ch;
-}
-
-.prose :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- margin-bottom: 1.25em;
-}
-
-.prose :where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: var(--tw-prose-lead);
- font-size: 1.25em;
- line-height: 1.6;
- margin-top: 1.2em;
- margin-bottom: 1.2em;
-}
-
-.prose :where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: #575279;
- text-decoration: underline;
- font-weight: 500;
- text-decoration-color: #ea9d34;
- text-decoration-thickness: 2px;
-}
-
-.prose :where(strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: #575279;
- font-weight: 800;
-}
-
-.prose :where(a strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
-}
-
-.prose :where(blockquote strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
-}
-
-.prose :where(thead th strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
-}
-
-.prose :where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: decimal;
- margin-top: 1.25em;
- margin-bottom: 1.25em;
- padding-inline-start: 1.625em;
-}
-
-.prose :where(ol[type="A"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: upper-alpha;
-}
-
-.prose :where(ol[type="a"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: lower-alpha;
-}
-
-.prose :where(ol[type="A" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: upper-alpha;
-}
-
-.prose :where(ol[type="a" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: lower-alpha;
-}
-
-.prose :where(ol[type="I"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: upper-roman;
-}
-
-.prose :where(ol[type="i"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: lower-roman;
-}
-
-.prose :where(ol[type="I" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: upper-roman;
-}
-
-.prose :where(ol[type="i" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: lower-roman;
-}
-
-.prose :where(ol[type="1"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: decimal;
-}
-
-.prose :where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- list-style-type: disc;
- margin-top: 1.25em;
- margin-bottom: 1.25em;
- padding-inline-start: 1.625em;
-}
-
-.prose :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *))::marker {
- font-weight: 400;
- color: var(--tw-prose-counters);
-}
-
-.prose :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *))::marker {
- color: var(--tw-prose-bullets);
-}
-
-.prose :where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: var(--tw-prose-headings);
- font-weight: 600;
- margin-top: 1.25em;
-}
-
-.prose :where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- border-color: var(--tw-prose-hr);
- border-top-width: 1px;
- margin-top: 3em;
- margin-bottom: 3em;
-}
-
-.prose :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-weight: 500;
- font-style: italic;
- color: #575279;
- border-inline-start-width: 0.25rem;
- border-inline-start-color: var(--tw-prose-quote-borders);
- quotes: "\201C""\201D""\2018""\2019";
- margin-top: 1.6em;
- margin-bottom: 1.6em;
- padding-inline-start: 1em;
- border-color: #9893a5;
- background-color: #f2e9e1;
-}
-
-.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"],[class~="not-prose"] *))::before {
- content: open-quote;
-}
-
-.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"],[class~="not-prose"] *))::after {
- content: close-quote;
-}
-
-.prose :where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: var(--tw-prose-headings);
- font-weight: 800;
- font-size: 2.25em;
- margin-top: 0;
- margin-bottom: 0.8888889em;
- line-height: 1.1111111;
-}
-
-.prose :where(h1 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-weight: 900;
- color: inherit;
-}
-
-.prose :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: var(--tw-prose-headings);
- font-weight: 700;
- font-size: 1.5em;
- margin-top: 2em;
- margin-bottom: 1em;
- line-height: 1.3333333;
-}
-
-.prose :where(h2 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-weight: 800;
- color: inherit;
-}
-
-.prose :where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: var(--tw-prose-headings);
- font-weight: 600;
- font-size: 1.25em;
- margin-top: 1.6em;
- margin-bottom: 0.6em;
- line-height: 1.6;
-}
-
-.prose :where(h3 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-weight: 700;
- color: inherit;
-}
-
-.prose :where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: var(--tw-prose-headings);
- font-weight: 600;
- margin-top: 1.5em;
- margin-bottom: 0.5em;
- line-height: 1.5;
-}
-
-.prose :where(h4 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-weight: 700;
- color: inherit;
-}
-
-.prose :where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 2em;
- margin-bottom: 2em;
-}
-
-.prose :where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- display: block;
- margin-top: 2em;
- margin-bottom: 2em;
-}
-
-.prose :where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 2em;
- margin-bottom: 2em;
-}
-
-.prose :where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-weight: 500;
- font-family: inherit;
- color: var(--tw-prose-kbd);
- box-shadow: 0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%);
- font-size: 0.875em;
- border-radius: 0.3125rem;
- padding-top: 0.1875em;
- padding-inline-end: 0.375em;
- padding-bottom: 0.1875em;
- padding-inline-start: 0.375em;
-}
-
-.prose :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *))::before {
- content: "";
-}
-
-.prose :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *))::after {
- content: "";
-}
-
-.prose :where(a code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
-}
-
-.prose :where(h1 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
-}
-
-.prose :where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
- font-size: 0.875em;
-}
-
-.prose :where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
- font-size: 0.9em;
-}
-
-.prose :where(h4 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
-}
-
-.prose :where(blockquote code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
-}
-
-.prose :where(thead th code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: inherit;
-}
-
-.prose :where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- width: 100%;
- table-layout: auto;
- margin-top: 2em;
- margin-bottom: 2em;
- font-size: 0.875em;
- line-height: 1.7142857;
-}
-
-.prose :where(thead):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- border-bottom-width: 1px;
- border-bottom-color: var(--tw-prose-th-borders);
-}
-
-.prose :where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: var(--tw-prose-headings);
- font-weight: 600;
- vertical-align: bottom;
- padding-inline-end: 0.5714286em;
- padding-bottom: 0.5714286em;
- padding-inline-start: 0.5714286em;
-}
-
-.prose :where(tbody tr):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- border-bottom-width: 1px;
- border-bottom-color: var(--tw-prose-td-borders);
-}
-
-.prose :where(tbody tr:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- border-bottom-width: 0;
-}
-
-.prose :where(tbody td):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- vertical-align: baseline;
-}
-
-.prose :where(tfoot):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- border-top-width: 1px;
- border-top-color: var(--tw-prose-th-borders);
-}
-
-.prose :where(tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- vertical-align: top;
-}
-
-.prose :where(th, td):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- text-align: start;
-}
-
-.prose :where(figure > *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- margin-bottom: 0;
-}
-
-.prose :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: var(--tw-prose-captions);
- font-size: 0.875em;
- line-height: 1.4285714;
- margin-top: 0.8571429em;
-}
-
-.prose {
- --tw-prose-body: #374151;
- --tw-prose-headings: #111827;
- --tw-prose-lead: #4b5563;
- --tw-prose-links: #111827;
- --tw-prose-bold: #111827;
- --tw-prose-counters: #6b7280;
- --tw-prose-bullets: #d1d5db;
- --tw-prose-hr: #e5e7eb;
- --tw-prose-quotes: #111827;
- --tw-prose-quote-borders: #e5e7eb;
- --tw-prose-captions: #6b7280;
- --tw-prose-kbd: #111827;
- --tw-prose-kbd-shadows: 17 24 39;
- --tw-prose-code: #111827;
- --tw-prose-pre-code: #e5e7eb;
- --tw-prose-pre-bg: #1f2937;
- --tw-prose-th-borders: #d1d5db;
- --tw-prose-td-borders: #e5e7eb;
- --tw-prose-invert-body: #d1d5db;
- --tw-prose-invert-headings: #fff;
- --tw-prose-invert-lead: #9ca3af;
- --tw-prose-invert-links: #fff;
- --tw-prose-invert-bold: #fff;
- --tw-prose-invert-counters: #9ca3af;
- --tw-prose-invert-bullets: #4b5563;
- --tw-prose-invert-hr: #374151;
- --tw-prose-invert-quotes: #f3f4f6;
- --tw-prose-invert-quote-borders: #374151;
- --tw-prose-invert-captions: #9ca3af;
- --tw-prose-invert-kbd: #fff;
- --tw-prose-invert-kbd-shadows: 255 255 255;
- --tw-prose-invert-code: #fff;
- --tw-prose-invert-pre-code: #d1d5db;
- --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);
- --tw-prose-invert-th-borders: #4b5563;
- --tw-prose-invert-td-borders: #374151;
- font-size: 1rem;
- line-height: 1.75;
-}
-
-.prose :where(picture > img):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- margin-bottom: 0;
-}
-
-.prose :where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.5em;
- margin-bottom: 0.5em;
-}
-
-.prose :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0.375em;
-}
-
-.prose :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0.375em;
-}
-
-.prose :where(.prose > ul > li p):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.75em;
- margin-bottom: 0.75em;
-}
-
-.prose :where(.prose > ul > li > p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
-}
-
-.prose :where(.prose > ul > li > p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 1.25em;
-}
-
-.prose :where(.prose > ol > li > p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
-}
-
-.prose :where(.prose > ol > li > p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 1.25em;
-}
-
-.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.75em;
- margin-bottom: 0.75em;
-}
-
-.prose :where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- margin-bottom: 1.25em;
-}
-
-.prose :where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.5em;
- padding-inline-start: 1.625em;
-}
-
-.prose :where(hr + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
-}
-
-.prose :where(h2 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
-}
-
-.prose :where(h3 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
-}
-
-.prose :where(h4 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
-}
-
-.prose :where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0;
-}
-
-.prose :where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-end: 0;
-}
-
-.prose :where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-top: 0.5714286em;
- padding-inline-end: 0.5714286em;
- padding-bottom: 0.5714286em;
- padding-inline-start: 0.5714286em;
-}
-
-.prose :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0;
-}
-
-.prose :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-end: 0;
-}
-
-.prose :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 2em;
- margin-bottom: 2em;
-}
-
-.prose :where(.prose > :first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
-}
-
-.prose :where(.prose > :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 0;
-}
-
-.prose :where(h1, h2, h3, h4, h5, h6):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: #575279;
-}
-
-.prose :where(th):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: #575279;
-}
-
-/*This does not seem to affect prismjs which is good*/
-
-code {
- --tw-bg-opacity: 1;
- background-color: rgb(244 237 232 / var(--tw-bg-opacity, 1));
-}
-
-@media (prefers-color-scheme: dark) {
- code {
- --tw-bg-opacity: 1;
- background-color: rgb(42 40 62 / var(--tw-bg-opacity, 1));
- }
-}
-
-.absolute {
- position: absolute;
-}
-
-.relative {
- position: relative;
-}
-
-.z-10 {
- z-index: 10;
-}
-
-.z-20 {
- z-index: 20;
-}
-
-.col-span-1 {
- grid-column: span 1 / span 1;
-}
-
-.col-span-2 {
- grid-column: span 2 / span 2;
-}
-
-.col-span-3 {
- grid-column: span 3 / span 3;
-}
-
-.col-span-9 {
- grid-column: span 9 / span 9;
-}
-
-.m-10 {
- margin: 2.5rem;
-}
-
-.m-4 {
- margin: 1rem;
-}
-
-.-mt-11 {
- margin-top: -2.75rem;
-}
-
-.mb-2 {
- margin-bottom: 0.5rem;
-}
-
-.mb-3 {
- margin-bottom: 0.75rem;
-}
-
-.ml-14 {
- margin-left: 3.5rem;
-}
-
-.mt-6 {
- margin-top: 1.5rem;
-}
-
-.block {
- display: block;
-}
-
-.inline-block {
- display: inline-block;
-}
-
-.flex {
- display: flex;
-}
-
-.grid {
- display: grid;
-}
-
-.inline-grid {
- display: inline-grid;
-}
-
-.size-5 {
- width: 1.25rem;
- height: 1.25rem;
-}
-
-.w-12 {
- width: 3rem;
-}
-
-.w-8 {
- width: 2rem;
-}
-
-.-rotate-3 {
- --tw-rotate: -3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.list-outside {
- list-style-position: outside;
-}
-
-.list-disc {
- list-style-type: disc;
-}
-
-.list-none {
- list-style-type: none;
-}
-
-.grid-cols-1 {
- grid-template-columns: repeat(1, minmax(0, 1fr));
-}
-
-.grid-cols-3 {
- grid-template-columns: repeat(3, minmax(0, 1fr));
-}
-
-.grid-cols-9 {
- grid-template-columns: repeat(9, minmax(0, 1fr));
-}
-
-.grid-cols-\[max-content_max-content_max-content_max-content_max-content\] {
- grid-template-columns: max-content max-content max-content max-content max-content;
-}
-
-.grid-cols-\[max-content_max-content_max-content_max-content_max-content_max-content\] {
- grid-template-columns: max-content max-content max-content max-content max-content max-content;
-}
-
-.items-center {
- align-items: center;
-}
-
-.justify-center {
- justify-content: center;
-}
-
-.gap-8 {
- gap: 2rem;
-}
-
-.hyphens-auto {
- -webkit-hyphens: auto;
- hyphens: auto;
-}
-
-.rounded-full {
- border-radius: 9999px;
-}
-
-.border-b-2 {
- border-bottom-width: 2px;
-}
-
-.border-t-2 {
- border-top-width: 2px;
-}
-
-.border-rp-dawn-gold {
- --tw-border-opacity: 1;
- border-color: rgb(234 157 52 / var(--tw-border-opacity, 1));
-}
-
-.border-rp-dawn-overlay {
- --tw-border-opacity: 1;
- border-color: rgb(242 233 225 / var(--tw-border-opacity, 1));
-}
-
-.bg-rp-dawn-base {
- --tw-bg-opacity: 1;
- background-color: rgb(250 244 237 / var(--tw-bg-opacity, 1));
-}
-
-.bg-rp-dawn-gold {
- --tw-bg-opacity: 1;
- background-color: rgb(234 157 52 / var(--tw-bg-opacity, 1));
-}
-
-.bg-rp-dawn-overlay {
- --tw-bg-opacity: 1;
- background-color: rgb(242 233 225 / var(--tw-bg-opacity, 1));
-}
-
-.bg-rp-dawn-surface {
- --tw-bg-opacity: 1;
- background-color: rgb(255 250 243 / var(--tw-bg-opacity, 1));
-}
-
-.bg-gradient-to-r {
- background-image: linear-gradient(to right, var(--tw-gradient-stops));
-}
-
-.from-rp-dawn-gold {
- --tw-gradient-from: #ea9d34 var(--tw-gradient-from-position);
- --tw-gradient-to: rgb(234 157 52 / 0) var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
-}
-
-.from-10\% {
- --tw-gradient-from-position: 10%;
-}
-
-.to-rp-dawn-pine {
- --tw-gradient-to: #286983 var(--tw-gradient-to-position);
-}
-
-.to-90\% {
- --tw-gradient-to-position: 90%;
-}
-
-.p-1 {
- padding: 0.25rem;
-}
-
-.p-3 {
- padding: 0.75rem;
-}
-
-.p-4 {
- padding: 1rem;
-}
-
-.p-8 {
- padding: 2rem;
-}
-
-.px-2 {
- padding-left: 0.5rem;
- padding-right: 0.5rem;
-}
-
-.pb-4 {
- padding-bottom: 1rem;
-}
-
-.pb-60 {
- padding-bottom: 15rem;
-}
-
-.pl-4 {
- padding-left: 1rem;
-}
-
-.pt-20 {
- padding-top: 5rem;
-}
-
-.text-center {
- text-align: center;
-}
-
-.align-top {
- vertical-align: top;
-}
-
-.text-2xl {
- font-size: 1.5rem;
- line-height: 2rem;
-}
-
-.text-3xl {
- font-size: 1.875rem;
- line-height: 2.25rem;
-}
-
-.text-4xl {
- font-size: 2.25rem;
- line-height: 2.5rem;
-}
-
-.text-base {
- font-size: 1rem;
- line-height: 1.5rem;
-}
-
-.text-sm {
- font-size: 0.875rem;
- line-height: 1.25rem;
-}
-
-.text-xl {
- font-size: 1.25rem;
- line-height: 1.75rem;
-}
-
-.font-bold {
- font-weight: 700;
-}
-
-.uppercase {
- text-transform: uppercase;
-}
-
-.capitalize {
- text-transform: capitalize;
-}
-
-.italic {
- font-style: italic;
-}
-
-.text-rp-dawn-foam {
- --tw-text-opacity: 1;
- color: rgb(86 148 159 / var(--tw-text-opacity, 1));
-}
-
-.text-rp-dawn-overlay {
- --tw-text-opacity: 1;
- color: rgb(242 233 225 / var(--tw-text-opacity, 1));
-}
-
-.text-rp-dawn-pine {
- --tw-text-opacity: 1;
- color: rgb(40 105 131 / var(--tw-text-opacity, 1));
-}
-
-.text-rp-dawn-text {
- --tw-text-opacity: 1;
- color: rgb(87 82 121 / var(--tw-text-opacity, 1));
-}
-
-.no-underline {
- text-decoration-line: none;
-}
-
-.shadow {
- --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
-}
-
-/*Hide heading anchor links unless hovering over them*/
-
-.headerlink {
- margin-left: 0.25rem;
- --tw-text-opacity: 1;
- color: rgb(180 99 122 / var(--tw-text-opacity, 1));
- text-decoration-line: none;
-}
-
-.headerlink:hover {
- text-decoration-line: none;
-}
-
-@media (min-width: 768px) {
- .headerlink {
- margin-left: 0.5rem;
- }
-}
-
-.note {
- margin: 2rem;
- --tw-bg-opacity: 1;
- background-color: rgb(223 218 217 / var(--tw-bg-opacity, 1));
- padding: 1rem;
-}
-
-@media (prefers-color-scheme: dark) {
- .note {
- --tw-bg-opacity: 1;
- background-color: rgb(68 65 90 / var(--tw-bg-opacity, 1));
- }
-}
-
-.warn {
- margin: 2rem;
- --tw-bg-opacity: 1;
- background-color: rgb(87 82 121 / var(--tw-bg-opacity, 1));
- padding: 1rem;
- --tw-text-opacity: 1;
- color: rgb(250 244 237 / var(--tw-text-opacity, 1));
-}
-
-@media (prefers-color-scheme: dark) {
- .warn {
- --tw-bg-opacity: 1;
- background-color: rgb(224 222 244 / var(--tw-bg-opacity, 1));
- --tw-text-opacity: 1;
- color: rgb(35 33 54 / var(--tw-text-opacity, 1));
- }
-}
-
-/*Attempt to only float thumbnails to the right*/
-
-.image-process-article-image,
-.image-process-thumb,
-.image-process-responsive {
- /*@apply lg:float-right p-2 lg:p-4;*/
- padding-left: 0.25rem;
- padding-right: 0.25rem;
-}
-
-@media (min-width: 1024px) {
- .image-process-article-image,
-.image-process-thumb,
-.image-process-responsive {
- float: right;
- padding-left: 0.75rem;
- padding-right: 0.75rem;
- }
-}
-
-#skiptocontent a {
- position: absolute;
- top: -40px;
- left: 0px;
- /*color: white;*/
- /*border-right: 1px solid white;*/
- /*border-bottom: 1px solid white;*/
- z-index: 100;
- --tw-bg-opacity: 1;
- background-color: rgb(87 82 121 / var(--tw-bg-opacity, 1));
- --tw-text-opacity: 1;
- color: rgb(255 250 243 / var(--tw-text-opacity, 1));
-}
-
-@media (prefers-color-scheme: dark) {
- #skiptocontent a {
- --tw-bg-opacity: 1;
- background-color: rgb(224 222 244 / var(--tw-bg-opacity, 1));
- --tw-text-opacity: 1;
- color: rgb(42 39 63 / var(--tw-text-opacity, 1));
- }
-}
-
-#skiptocontent a:focus {
- position: absolute;
- left: 0px;
- top: 0px;
- outline-color: transparent;
-}
-
-@media (min-width: 768px) {
- .md\:prose-base {
- font-size: 1rem;
- line-height: 1.75;
- }
-
- .md\:prose-base :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- margin-bottom: 1.25em;
- }
-
- .md\:prose-base :where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 1.25em;
- line-height: 1.6;
- margin-top: 1.2em;
- margin-bottom: 1.2em;
- }
-
- .md\:prose-base :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.6em;
- margin-bottom: 1.6em;
- padding-inline-start: 1em;
- }
-
- .md\:prose-base :where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 2.25em;
- margin-top: 0;
- margin-bottom: 0.8888889em;
- line-height: 1.1111111;
- }
-
- .md\:prose-base :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 1.5em;
- margin-top: 2em;
- margin-bottom: 1em;
- line-height: 1.3333333;
- }
-
- .md\:prose-base :where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 1.25em;
- margin-top: 1.6em;
- margin-bottom: 0.6em;
- line-height: 1.6;
- }
-
- .md\:prose-base :where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.5em;
- margin-bottom: 0.5em;
- line-height: 1.5;
- }
-
- .md\:prose-base :where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 2em;
- margin-bottom: 2em;
- }
-
- .md\:prose-base :where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 2em;
- margin-bottom: 2em;
- }
-
- .md\:prose-base :where(picture > img):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- margin-bottom: 0;
- }
-
- .md\:prose-base :where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 2em;
- margin-bottom: 2em;
- }
-
- .md\:prose-base :where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.875em;
- border-radius: 0.3125rem;
- padding-top: 0.1875em;
- padding-inline-end: 0.375em;
- padding-bottom: 0.1875em;
- padding-inline-start: 0.375em;
- }
-
- .md\:prose-base :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.875em;
- }
-
- .md\:prose-base :where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.875em;
- }
-
- .md\:prose-base :where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.9em;
- }
-
- .md\:prose-base :where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.875em;
- line-height: 1.7142857;
- margin-top: 1.7142857em;
- margin-bottom: 1.7142857em;
- border-radius: 0.375rem;
- padding-top: 0.8571429em;
- padding-inline-end: 1.1428571em;
- padding-bottom: 0.8571429em;
- padding-inline-start: 1.1428571em;
- }
-
- .md\:prose-base :where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- margin-bottom: 1.25em;
- padding-inline-start: 1.625em;
- }
-
- .md\:prose-base :where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- margin-bottom: 1.25em;
- padding-inline-start: 1.625em;
- }
-
- .md\:prose-base :where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.5em;
- margin-bottom: 0.5em;
- }
-
- .md\:prose-base :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0.375em;
- }
-
- .md\:prose-base :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0.375em;
- }
-
- .md\:prose-base :where(.md\:prose-base > ul > li p):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.75em;
- margin-bottom: 0.75em;
- }
-
- .md\:prose-base :where(.md\:prose-base > ul > li > p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- }
-
- .md\:prose-base :where(.md\:prose-base > ul > li > p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 1.25em;
- }
-
- .md\:prose-base :where(.md\:prose-base > ol > li > p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- }
-
- .md\:prose-base :where(.md\:prose-base > ol > li > p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 1.25em;
- }
-
- .md\:prose-base :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.75em;
- margin-bottom: 0.75em;
- }
-
- .md\:prose-base :where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- margin-bottom: 1.25em;
- }
-
- .md\:prose-base :where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.25em;
- }
-
- .md\:prose-base :where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.5em;
- padding-inline-start: 1.625em;
- }
-
- .md\:prose-base :where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 3em;
- margin-bottom: 3em;
- }
-
- .md\:prose-base :where(hr + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .md\:prose-base :where(h2 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .md\:prose-base :where(h3 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .md\:prose-base :where(h4 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .md\:prose-base :where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.875em;
- line-height: 1.7142857;
- }
-
- .md\:prose-base :where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-end: 0.5714286em;
- padding-bottom: 0.5714286em;
- padding-inline-start: 0.5714286em;
- }
-
- .md\:prose-base :where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0;
- }
-
- .md\:prose-base :where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-end: 0;
- }
-
- .md\:prose-base :where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-top: 0.5714286em;
- padding-inline-end: 0.5714286em;
- padding-bottom: 0.5714286em;
- padding-inline-start: 0.5714286em;
- }
-
- .md\:prose-base :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0;
- }
-
- .md\:prose-base :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-end: 0;
- }
-
- .md\:prose-base :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 2em;
- margin-bottom: 2em;
- }
-
- .md\:prose-base :where(figure > *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- margin-bottom: 0;
- }
-
- .md\:prose-base :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.875em;
- line-height: 1.4285714;
- margin-top: 0.8571429em;
- }
-
- .md\:prose-base :where(.md\:prose-base > :first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .md\:prose-base :where(.md\:prose-base > :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 0;
- }
-}
-
-@media (min-width: 1024px) {
- .lg\:prose-lg {
- font-size: 1.125rem;
- line-height: 1.7777778;
- }
-
- .lg\:prose-lg :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.3333333em;
- margin-bottom: 1.3333333em;
- }
-
- .lg\:prose-lg :where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 1.2222222em;
- line-height: 1.4545455;
- margin-top: 1.0909091em;
- margin-bottom: 1.0909091em;
- }
-
- .lg\:prose-lg :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.6666667em;
- margin-bottom: 1.6666667em;
- padding-inline-start: 1em;
- }
-
- .lg\:prose-lg :where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 2.6666667em;
- margin-top: 0;
- margin-bottom: 0.8333333em;
- line-height: 1;
- }
-
- .lg\:prose-lg :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 1.6666667em;
- margin-top: 1.8666667em;
- margin-bottom: 1.0666667em;
- line-height: 1.3333333;
- }
-
- .lg\:prose-lg :where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 1.3333333em;
- margin-top: 1.6666667em;
- margin-bottom: 0.6666667em;
- line-height: 1.5;
- }
-
- .lg\:prose-lg :where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.7777778em;
- margin-bottom: 0.4444444em;
- line-height: 1.5555556;
- }
-
- .lg\:prose-lg :where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.7777778em;
- margin-bottom: 1.7777778em;
- }
-
- .lg\:prose-lg :where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.7777778em;
- margin-bottom: 1.7777778em;
- }
-
- .lg\:prose-lg :where(picture > img):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- margin-bottom: 0;
- }
-
- .lg\:prose-lg :where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.7777778em;
- margin-bottom: 1.7777778em;
- }
-
- .lg\:prose-lg :where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.8888889em;
- border-radius: 0.3125rem;
- padding-top: 0.2222222em;
- padding-inline-end: 0.4444444em;
- padding-bottom: 0.2222222em;
- padding-inline-start: 0.4444444em;
- }
-
- .lg\:prose-lg :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.8888889em;
- }
-
- .lg\:prose-lg :where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.8666667em;
- }
-
- .lg\:prose-lg :where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.875em;
- }
-
- .lg\:prose-lg :where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.8888889em;
- line-height: 1.75;
- margin-top: 2em;
- margin-bottom: 2em;
- border-radius: 0.375rem;
- padding-top: 1em;
- padding-inline-end: 1.5em;
- padding-bottom: 1em;
- padding-inline-start: 1.5em;
- }
-
- .lg\:prose-lg :where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.3333333em;
- margin-bottom: 1.3333333em;
- padding-inline-start: 1.5555556em;
- }
-
- .lg\:prose-lg :where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.3333333em;
- margin-bottom: 1.3333333em;
- padding-inline-start: 1.5555556em;
- }
-
- .lg\:prose-lg :where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.6666667em;
- margin-bottom: 0.6666667em;
- }
-
- .lg\:prose-lg :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0.4444444em;
- }
-
- .lg\:prose-lg :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0.4444444em;
- }
-
- .lg\:prose-lg :where(.lg\:prose-lg > ul > li p):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.8888889em;
- margin-bottom: 0.8888889em;
- }
-
- .lg\:prose-lg :where(.lg\:prose-lg > ul > li > p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.3333333em;
- }
-
- .lg\:prose-lg :where(.lg\:prose-lg > ul > li > p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 1.3333333em;
- }
-
- .lg\:prose-lg :where(.lg\:prose-lg > ol > li > p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.3333333em;
- }
-
- .lg\:prose-lg :where(.lg\:prose-lg > ol > li > p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 1.3333333em;
- }
-
- .lg\:prose-lg :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.8888889em;
- margin-bottom: 0.8888889em;
- }
-
- .lg\:prose-lg :where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.3333333em;
- margin-bottom: 1.3333333em;
- }
-
- .lg\:prose-lg :where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.3333333em;
- }
-
- .lg\:prose-lg :where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0.6666667em;
- padding-inline-start: 1.5555556em;
- }
-
- .lg\:prose-lg :where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 3.1111111em;
- margin-bottom: 3.1111111em;
- }
-
- .lg\:prose-lg :where(hr + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .lg\:prose-lg :where(h2 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .lg\:prose-lg :where(h3 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .lg\:prose-lg :where(h4 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .lg\:prose-lg :where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.8888889em;
- line-height: 1.5;
- }
-
- .lg\:prose-lg :where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-end: 0.75em;
- padding-bottom: 0.75em;
- padding-inline-start: 0.75em;
- }
-
- .lg\:prose-lg :where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0;
- }
-
- .lg\:prose-lg :where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-end: 0;
- }
-
- .lg\:prose-lg :where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-top: 0.75em;
- padding-inline-end: 0.75em;
- padding-bottom: 0.75em;
- padding-inline-start: 0.75em;
- }
-
- .lg\:prose-lg :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-start: 0;
- }
-
- .lg\:prose-lg :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- padding-inline-end: 0;
- }
-
- .lg\:prose-lg :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 1.7777778em;
- margin-bottom: 1.7777778em;
- }
-
- .lg\:prose-lg :where(figure > *):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- margin-bottom: 0;
- }
-
- .lg\:prose-lg :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- font-size: 0.8888889em;
- line-height: 1.5;
- margin-top: 1em;
- }
-
- .lg\:prose-lg :where(.lg\:prose-lg > :first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-top: 0;
- }
-
- .lg\:prose-lg :where(.lg\:prose-lg > :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- margin-bottom: 0;
- }
-}
-
-@media (prefers-color-scheme: dark) {
- .dark\:prose-dark {
- color: #c4a7e7;
- }
-
- .dark\:prose-dark :where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: #c4a7e7;
- text-decoration-color: #3e8fb0;
- text-decoration-thickness: 2px;
- }
-
- .dark\:prose-dark :where(h1, h2, h3, h4, h5, h6):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: #eb6f92;
- }
-
- .dark\:prose-dark :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- border-color: #393552;
- background-color: #2a273f;
- color: #e0def4;
- }
-
- .dark\:prose-dark :where(strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: #c4a7e7;
- font-weight: 800;
- }
-
- .dark\:prose-dark :where(th):not(:where([class~="not-prose"],[class~="not-prose"] *)) {
- color: #c4a7e7;
- }
-}
-
-.hover\:no-underline:hover {
- text-decoration-line: none;
-}
-
-.prose-img\:mx-auto :is(:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *))) {
- margin-left: auto;
- margin-right: auto;
-}
-
-.prose-img\:rounded :is(:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *))) {
- border-radius: 0.25rem;
-}
-
-@media (min-width: 768px) {
- .md\:col-span-3 {
- grid-column: span 3 / span 3;
- }
-
- .md\:col-span-6 {
- grid-column: span 6 / span 6;
- }
-
- .md\:col-span-7 {
- grid-column: span 7 / span 7;
- }
-
- .md\:grid-cols-6 {
- grid-template-columns: repeat(6, minmax(0, 1fr));
- }
-
- .md\:border-l-4 {
- border-left-width: 4px;
- }
-
- .md\:text-3xl {
- font-size: 1.875rem;
- line-height: 2.25rem;
- }
-
- .md\:text-5xl {
- font-size: 3rem;
- line-height: 1;
- }
-
- .md\:text-7xl {
- font-size: 4.5rem;
- line-height: 1;
- }
-
- .md\:text-base {
- font-size: 1rem;
- line-height: 1.5rem;
- }
-
- .md\:text-xl {
- font-size: 1.25rem;
- line-height: 1.75rem;
- }
-}
-
-@media (min-width: 1024px) {
- .lg\:grid-cols-2 {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .lg\:text-lg {
- font-size: 1.125rem;
- line-height: 1.75rem;
- }
-}
-
-@media (min-width: 1280px) {
- .xl\:grid-cols-3 {
- grid-template-columns: repeat(3, minmax(0, 1fr));
- }
-}
-
-@media (prefers-color-scheme: dark) {
- .dark\:border-rp-moon-overlay {
- --tw-border-opacity: 1;
- border-color: rgb(57 53 82 / var(--tw-border-opacity, 1));
- }
-
- .dark\:bg-rp-moon-base {
- --tw-bg-opacity: 1;
- background-color: rgb(35 33 54 / var(--tw-bg-opacity, 1));
- }
-
- .dark\:bg-rp-moon-overlay {
- --tw-bg-opacity: 1;
- background-color: rgb(57 53 82 / var(--tw-bg-opacity, 1));
- }
-
- .dark\:bg-rp-moon-pine {
- --tw-bg-opacity: 1;
- background-color: rgb(62 143 176 / var(--tw-bg-opacity, 1));
- }
-
- .dark\:bg-rp-moon-surface {
- --tw-bg-opacity: 1;
- background-color: rgb(42 39 63 / var(--tw-bg-opacity, 1));
- }
-
- .dark\:text-rp-moon-foam {
- --tw-text-opacity: 1;
- color: rgb(156 207 216 / var(--tw-text-opacity, 1));
- }
-
- .dark\:text-rp-moon-highlight-med {
- --tw-text-opacity: 1;
- color: rgb(68 65 90 / var(--tw-text-opacity, 1));
- }
-
- .dark\:text-rp-moon-iris {
- --tw-text-opacity: 1;
- color: rgb(196 167 231 / var(--tw-text-opacity, 1));
- }
-
- .dark\:text-rp-moon-rose {
- --tw-text-opacity: 1;
- color: rgb(234 154 151 / var(--tw-text-opacity, 1));
- }
-
- .dark\:text-rp-moon-surface {
- --tw-text-opacity: 1;
- color: rgb(42 39 63 / var(--tw-text-opacity, 1));
- }
-
- .dark\:text-rp-moon-text {
- --tw-text-opacity: 1;
- color: rgb(224 222 244 / var(--tw-text-opacity, 1));
- }
-}
diff --git a/static/css/prism-rose-pine-moon-alt.css b/static/css/prism-rose-pine-moon-alt.css
deleted file mode 100644
index 6ceaebae..00000000
--- a/static/css/prism-rose-pine-moon-alt.css
+++ /dev/null
@@ -1,201 +0,0 @@
-/**
- * MIT License
- * Rosé Pine Theme
- * https://github.com/rose-pine
- * Ported for PrismJS by fvrests [@fvrests]
- */
-
-code[class*="language-"],
-pre[class*="language-"] {
- color: #e0def4;
- background: #2a273f;
- font-family: "Cartograph CF", ui-monospace, SFMono-Regular, Menlo, Monaco,
- Consolas, "Liberation Mono", "Courier New", monospace;
- text-align: left;
- white-space: pre;
- word-spacing: normal;
- word-break: normal;
- word-wrap: normal;
- line-height: 1.5;
-
- -moz-tab-size: 4;
- -o-tab-size: 4;
- tab-size: 4;
-
- -webkit-hyphens: none;
- -moz-hyphens: none;
- -ms-hyphens: none;
- hyphens: none;
-
- @media print {
- text-shadow: none;
- }
-}
-
-/* Selection */
-code[class*="language-"]::-moz-selection,
-pre[class*="language-"]::-moz-selection,
-code[class*="language-"] ::-moz-selection,
-pre[class*="language-"] ::-moz-selection {
- background: #44415a;
-}
-
-code[class*="language-"]::selection,
-pre[class*="language-"]::selection,
-code[class*="language-"] ::selection,
-pre[class*="language-"] ::selection {
- background: #44415a;
-}
-
-/* Code (block & inline) */
-:not(pre) > code[class*="language-"],
-pre[class*="language-"] {
- color: #e0def4;
- background: #2a273f;
-}
-
-/* Code blocks */
-pre[class*="language-"] {
- padding: 1em;
- margin: 0.5em 0;
- overflow: auto;
-}
-
-/* Inline code */
-:not(pre) > code[class*="language-"] {
- padding: 0.1em;
- border-radius: 0.3em;
- white-space: normal;
- color: #e0def4;
- background: #2a273f;
-}
-
-/* Text style & opacity */
-.token.entity {
- cursor: help;
-}
-
-.token.important,
-.token.bold {
- font-weight: bold;
-}
-
-.token.italic,
-.token.selector,
-.token.doctype,
-.token.attr-name,
-.token.inserted,
-.token.deleted,
-.token.comment,
-.token.prolog,
-.token.cdata,
-.token.constant,
-.token.parameter,
-.token.url {
- font-style: italic;
-}
-
-.token.url {
- text-decoration: underline;
-}
-
-.namespace {
- opacity: 0.7;
-}
-
-/* Syntax highlighting */
-.token.constant {
- color: #e0def4;
-}
-
-.token.comment,
-.token.prolog,
-.token.cdata,
-.token.punctuation {
- color: #908caa;
-}
-
-.token.delimiter,
-.token.important,
-.token.atrule,
-.token.operator,
-.token.keyword {
- color: #3e8fb0;
-}
-
-.token.tag,
-.token.tag .punctuation,
-.token.doctype,
-.token.variable,
-.token.regex,
-.token.class-name,
-.token.selector,
-.token.inserted {
- color: #9ccfd8;
-}
-
-.token.boolean,
-.token.entity,
-.token.number,
-.token.symbol,
-.token.function {
- color: #ea9a97;
-}
-
-.token.string,
-.token.char,
-.token.property,
-.token.attr-value,
-.token.attr-value .punctuation {
- color: #f6c177;
-}
-
-.token.parameter,
-.token.url,
-.token.name,
-.token.attr-name,
-.token.builtin {
- color: #c4a7e7;
-}
-
-.token.deleted {
- color: #eb6f92;
-}
-
-/* Insertions & deletions */
-.token.inserted {
- background: rgba(156 207 216 0.12);
-}
-
-.token.deleted {
- background: rgba(235 111 146 0.12);
-}
-
-/* Line highlighting */
-pre[data-line] {
- position: relative;
-}
-
-pre[class*="language-"] > code[class*="language-"] {
- position: relative;
- z-index: 1;
-}
-
-.line-highlight,
-.highlight-lines .highlighted {
- position: absolute;
- left: 0;
- right: 0;
- padding: inherit 0;
- margin-top: 1em;
-
- background: #44415a;
- box-shadow: inset 5px 0 0 #e0def4;
-
- z-index: 0;
-
- pointer-events: none;
-
- line-height: inherit;
- white-space: pre;
-}
diff --git a/static/css/prism-rose-pine-moon.css b/static/css/prism-rose-pine-moon.css
deleted file mode 100644
index fd4353cc..00000000
--- a/static/css/prism-rose-pine-moon.css
+++ /dev/null
@@ -1,201 +0,0 @@
-/**
- * MIT License
- * Rosé Pine Theme
- * https://github.com/rose-pine
- * Ported for PrismJS by fvrests [@fvrests]
- */
-
-code[class*="language-"],
-pre[class*="language-"] {
- color: #e0def4;
- background: #232136;
- font-family: "Cartograph CF", ui-monospace, SFMono-Regular, Menlo, Monaco,
- Consolas, "Liberation Mono", "Courier New", monospace;
- text-align: left;
- white-space: pre;
- word-spacing: normal;
- word-break: normal;
- word-wrap: normal;
- line-height: 1.5;
-
- -moz-tab-size: 4;
- -o-tab-size: 4;
- tab-size: 4;
-
- -webkit-hyphens: none;
- -moz-hyphens: none;
- -ms-hyphens: none;
- hyphens: none;
-
- @media print {
- text-shadow: none;
- }
-}
-
-/* Selection */
-code[class*="language-"]::-moz-selection,
-pre[class*="language-"]::-moz-selection,
-code[class*="language-"] ::-moz-selection,
-pre[class*="language-"] ::-moz-selection {
- background: #44415a;
-}
-
-code[class*="language-"]::selection,
-pre[class*="language-"]::selection,
-code[class*="language-"] ::selection,
-pre[class*="language-"] ::selection {
- background: #44415a;
-}
-
-/* Code (block & inline) */
-:not(pre) > code[class*="language-"],
-pre[class*="language-"] {
- color: #e0def4;
- background: #232136;
-}
-
-/* Code blocks */
-pre[class*="language-"] {
- padding: 1em;
- margin: 0.5em 0;
- overflow: auto;
-}
-
-/* Inline code */
-:not(pre) > code[class*="language-"] {
- padding: 0.1em;
- border-radius: 0.3em;
- white-space: normal;
- color: #e0def4;
- background: #232136;
-}
-
-/* Text style & opacity */
-.token.entity {
- cursor: help;
-}
-
-.token.important,
-.token.bold {
- font-weight: bold;
-}
-
-.token.italic,
-.token.selector,
-.token.doctype,
-.token.attr-name,
-.token.inserted,
-.token.deleted,
-.token.comment,
-.token.prolog,
-.token.cdata,
-.token.constant,
-.token.parameter,
-.token.url {
- font-style: italic;
-}
-
-.token.url {
- text-decoration: underline;
-}
-
-.namespace {
- opacity: 0.7;
-}
-
-/* Syntax highlighting */
-.token.constant {
- color: #e0def4;
-}
-
-.token.comment,
-.token.prolog,
-.token.cdata,
-.token.punctuation {
- color: #908caa;
-}
-
-.token.delimiter,
-.token.important,
-.token.atrule,
-.token.operator,
-.token.keyword {
- color: #3e8fb0;
-}
-
-.token.tag,
-.token.tag .punctuation,
-.token.doctype,
-.token.variable,
-.token.regex,
-.token.class-name,
-.token.selector,
-.token.inserted {
- color: #9ccfd8;
-}
-
-.token.boolean,
-.token.entity,
-.token.number,
-.token.symbol,
-.token.function {
- color: #ea9a97;
-}
-
-.token.string,
-.token.char,
-.token.property,
-.token.attr-value,
-.token.attr-value .punctuation {
- color: #f6c177;
-}
-
-.token.parameter,
-.token.url,
-.token.name,
-.token.attr-name,
-.token.builtin {
- color: #c4a7e7;
-}
-
-.token.deleted {
- color: #eb6f92;
-}
-
-/* Insertions & deletions */
-.token.inserted {
- background: rgba(156 207 216 0.12);
-}
-
-.token.deleted {
- background: rgba(235 111 146 0.12);
-}
-
-/* Line highlighting */
-pre[data-line] {
- position: relative;
-}
-
-pre[class*="language-"] > code[class*="language-"] {
- position: relative;
- z-index: 1;
-}
-
-.line-highlight,
-.highlight-lines .highlighted {
- position: absolute;
- left: 0;
- right: 0;
- padding: inherit 0;
- margin-top: 1em;
-
- background: #44415a;
- box-shadow: inset 5px 0 0 #e0def4;
-
- z-index: 0;
-
- pointer-events: none;
-
- line-height: inherit;
- white-space: pre;
-}
diff --git a/static/css/prism.css b/static/css/prism.css
deleted file mode 100644
index 6d73b1fb..00000000
--- a/static/css/prism.css
+++ /dev/null
@@ -1,3 +0,0 @@
-/* PrismJS 1.29.0
-https://prismjs.com/download.html?themes#themes=prism&languages=markup+css+clike+javascript+bash+diff+django+dns-zone-file+docker+dot+elixir+erlang+git+go+go-module+hcl+ini+jq+lua+makefile+markdown+markup-templating+nginx+properties+python+r+rest+sql+toml+vim+yaml */
-code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
diff --git a/static/font/C64_Pro_Mono-STYLE.woff2 b/static/font/C64_Pro_Mono-STYLE.woff2
deleted file mode 100644
index a1b72c55..00000000
Binary files a/static/font/C64_Pro_Mono-STYLE.woff2 and /dev/null differ
diff --git a/static/js/prism.js b/static/js/prism.js
deleted file mode 100644
index f65c9412..00000000
--- a/static/js/prism.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* PrismJS 1.29.0
-https://prismjs.com/download.html?themes#themes=prism&languages=markup+css+clike+javascript+bash+diff+django+dns-zone-file+docker+dot+elixir+erlang+git+go+go-module+hcl+ini+jq+lua+makefile+markdown+markup-templating+nginx+properties+python+r+rest+sql+toml+vim+yaml */
-var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
-Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
-!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism);
-Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
-Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
-!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",a={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},n={bash:a,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:a}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:n},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:n.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},a.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=n.variable[1].inside,i=0;i",unchanged:" ",diff:"!"};Object.keys(n).forEach((function(a){var i=n[a],r=[];/^\w+$/.test(a)||r.push(/\w+/.exec(a)[0]),"diff"===a&&r.push("bold"),e.languages.diff[a]={pattern:RegExp("^(?:["+i+"].*(?:\r\n?|\n|(?![\\s\\S])))+","m"),alias:r,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(a)[0]}}}})),Object.defineProperty(e.languages.diff,"PREFIXES",{value:n})}(Prism);
-!function(e){function n(e,n){return"___"+e.toUpperCase()+n+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(t,a,r,o){if(t.language===a){var c=t.tokenStack=[];t.code=t.code.replace(r,(function(e){if("function"==typeof o&&!o(e))return e;for(var r,i=c.length;-1!==t.code.indexOf(r=n(a,i));)++i;return c[i]=e,r})),t.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(t,a){if(t.language===a&&t.tokenStack){t.grammar=e.languages[a];var r=0,o=Object.keys(t.tokenStack);!function c(i){for(var u=0;u=o.length);u++){var g=i[u];if("string"==typeof g||g.content&&"string"==typeof g.content){var l=o[r],s=t.tokenStack[l],f="string"==typeof g?g:g.content,p=n(a,l),k=f.indexOf(p);if(k>-1){++r;var m=f.substring(0,k),d=new e.Token(a,e.tokenize(s,t.grammar),"language-"+a,s),h=f.substring(k+p.length),v=[];m&&v.push.apply(v,c([m])),v.push(d),h&&v.push.apply(v,c([h])),"string"==typeof g?i.splice.apply(i,[u,1].concat(v)):g.content=v}}else g.content&&c(g.content)}return i}(t.tokens)}}}})}(Prism);
-!function(e){e.languages.django={comment:/^\{#[\s\S]*?#\}$/,tag:{pattern:/(^\{%[+-]?\s*)\w+/,lookbehind:!0,alias:"keyword"},delimiter:{pattern:/^\{[{%][+-]?|[+-]?[}%]\}$/,alias:"punctuation"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},filter:{pattern:/(\|)\w+/,lookbehind:!0,alias:"function"},test:{pattern:/(\bis\s+(?:not\s+)?)(?!not\b)\w+/,lookbehind:!0,alias:"function"},function:/\b[a-z_]\w+(?=\s*\()/i,keyword:/\b(?:and|as|by|else|for|if|import|in|is|loop|not|or|recursive|with|without)\b/,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,number:/\b\d+(?:\.\d+)?\b/,boolean:/[Ff]alse|[Nn]one|[Tt]rue/,variable:/\b\w+\b/,punctuation:/[{}[\](),.:;]/};var n=/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}|\{#[\s\S]*?#\}/g,o=e.languages["markup-templating"];e.hooks.add("before-tokenize",(function(e){o.buildPlaceholders(e,"django",n)})),e.hooks.add("after-tokenize",(function(e){o.tokenizePlaceholders(e,"django")})),e.languages.jinja2=e.languages.django,e.hooks.add("before-tokenize",(function(e){o.buildPlaceholders(e,"jinja2",n)})),e.hooks.add("after-tokenize",(function(e){o.tokenizePlaceholders(e,"jinja2")}))}(Prism);
-Prism.languages["dns-zone-file"]={comment:/;.*/,string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},variable:[{pattern:/(^\$ORIGIN[ \t]+)\S+/m,lookbehind:!0},{pattern:/(^|\s)@(?=\s|$)/,lookbehind:!0}],keyword:/^\$(?:INCLUDE|ORIGIN|TTL)(?=\s|$)/m,class:{pattern:/(^|\s)(?:CH|CS|HS|IN)(?=\s|$)/,lookbehind:!0,alias:"keyword"},type:{pattern:/(^|\s)(?:A|A6|AAAA|AFSDB|APL|ATMA|CAA|CDNSKEY|CDS|CERT|CNAME|DHCID|DLV|DNAME|DNSKEY|DS|EID|GID|GPOS|HINFO|HIP|IPSECKEY|ISDN|KEY|KX|LOC|MAILA|MAILB|MB|MD|MF|MG|MINFO|MR|MX|NAPTR|NB|NBSTAT|NIMLOC|NINFO|NS|NSAP|NSAP-PTR|NSEC|NSEC3|NSEC3PARAM|NULL|NXT|OPENPGPKEY|PTR|PX|RKEY|RP|RRSIG|RT|SIG|SINK|SMIMEA|SOA|SPF|SRV|SSHFP|TA|TKEY|TLSA|TSIG|TXT|UID|UINFO|UNSPEC|URI|WKS|X25)(?=\s|$)/,lookbehind:!0,alias:"keyword"},punctuation:/[()]/},Prism.languages["dns-zone"]=Prism.languages["dns-zone-file"];
-!function(e){var n="(?:[ \t]+(?![ \t])(?:)?|)".replace(//g,(function(){return"\\\\[\r\n](?:\\s|\\\\[\r\n]|#.*(?!.))*(?![\\s#]|\\\\[\r\n])"})),r="\"(?:[^\"\\\\\r\n]|\\\\(?:\r\n|[^]))*\"|'(?:[^'\\\\\r\n]|\\\\(?:\r\n|[^]))*'",t="--[\\w-]+=(?:|(?![\"'])(?:[^\\s\\\\]|\\\\.)+)".replace(//g,(function(){return r})),o={pattern:RegExp(r),greedy:!0},i={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function a(e,r){return e=e.replace(//g,(function(){return t})).replace(//g,(function(){return n})),RegExp(e,r)}e.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:a("(^(?:ONBUILD)?\\w+)(?:)*","i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[o,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:a("(^(?:ONBUILD)?HEALTHCHECK(?:)*)(?:CMD|NONE)\\b","i"),lookbehind:!0,greedy:!0},{pattern:a("(^(?:ONBUILD)?FROM(?:)*(?!--)[^ \t\\\\]+)AS","i"),lookbehind:!0,greedy:!0},{pattern:a("(^ONBUILD)\\w+","i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:i,string:o,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,operator:/\\$/m}},comment:i},e.languages.dockerfile=e.languages.docker}(Prism);
-!function(e){var a="(?:"+["[a-zA-Z_\\x80-\\uFFFF][\\w\\x80-\\uFFFF]*","-?(?:\\.\\d+|\\d+(?:\\.\\d*)?)",'"[^"\\\\]*(?:\\\\[^][^"\\\\]*)*"',"<(?:[^<>]|(?!\x3c!--)<(?:[^<>\"']|\"[^\"]*\"|'[^']*')+>|\x3c!--(?:[^-]|-(?!->))*--\x3e)*>"].join("|")+")",n={markup:{pattern:/(^<)[\s\S]+(?=>$)/,lookbehind:!0,alias:["language-markup","language-html","language-xml"],inside:e.languages.markup}};function r(e,n){return RegExp(e.replace(//g,(function(){return a})),n)}e.languages.dot={comment:{pattern:/\/\/.*|\/\*[\s\S]*?\*\/|^#.*/m,greedy:!0},"graph-name":{pattern:r("(\\b(?:digraph|graph|subgraph)[ \t\r\n]+)","i"),lookbehind:!0,greedy:!0,alias:"class-name",inside:n},"attr-value":{pattern:r("(=[ \t\r\n]*)"),lookbehind:!0,greedy:!0,inside:n},"attr-name":{pattern:r("([\\[;, \t\r\n])(?=[ \t\r\n]*=)"),lookbehind:!0,greedy:!0,inside:n},keyword:/\b(?:digraph|edge|graph|node|strict|subgraph)\b/i,"compass-point":{pattern:/(:[ \t\r\n]*)(?:[ewc_]|[ns][ew]?)(?![\w\x80-\uFFFF])/,lookbehind:!0,alias:"builtin"},node:{pattern:r("(^|[^-.\\w\\x80-\\uFFFF\\\\])"),lookbehind:!0,greedy:!0,inside:n},operator:/[=:]|-[->]/,punctuation:/[\[\]{};,]/},e.languages.gv=e.languages.dot}(Prism);
-Prism.languages.elixir={doc:{pattern:/@(?:doc|moduledoc)\s+(?:("""|''')[\s\S]*?\1|("|')(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2)/,inside:{attribute:/^@\w+/,string:/['"][\s\S]+/}},comment:{pattern:/#.*/,greedy:!0},regex:{pattern:/~[rR](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|[^\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[uismxfr]*/,greedy:!0},string:[{pattern:/~[cCsSwW](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|#\{[^}]+\}|#(?!\{)|[^#\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[csa]?/,greedy:!0,inside:{}},{pattern:/("""|''')[\s\S]*?\1/,greedy:!0,inside:{}},{pattern:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{}}],atom:{pattern:/(^|[^:]):\w+/,lookbehind:!0,alias:"symbol"},module:{pattern:/\b[A-Z]\w*\b/,alias:"class-name"},"attr-name":/\b\w+\??:(?!:)/,argument:{pattern:/(^|[^&])&\d+/,lookbehind:!0,alias:"variable"},attribute:{pattern:/@\w+/,alias:"variable"},function:/\b[_a-zA-Z]\w*[?!]?(?:(?=\s*(?:\.\s*)?\()|(?=\/\d))/,number:/\b(?:0[box][a-f\d_]+|\d[\d_]*)(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?\b/i,keyword:/\b(?:after|alias|and|case|catch|cond|def(?:callback|delegate|exception|impl|macro|module|n|np|p|protocol|struct)?|do|else|end|fn|for|if|import|not|or|quote|raise|require|rescue|try|unless|unquote|use|when)\b/,boolean:/\b(?:false|nil|true)\b/,operator:[/\bin\b|&&?|\|[|>]?|\\\\|::|\.\.\.?|\+\+?|-[->]?|<[-=>]|>=|!==?|\B!|=(?:==?|[>~])?|[*\/^]/,{pattern:/([^<])<(?!<)/,lookbehind:!0},{pattern:/([^>])>(?!>)/,lookbehind:!0}],punctuation:/<<|>>|[.,%\[\]{}()]/},Prism.languages.elixir.string.forEach((function(e){e.inside={interpolation:{pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"},rest:Prism.languages.elixir}}}}));
-Prism.languages.erlang={comment:/%.+/,string:{pattern:/"(?:\\.|[^\\"\r\n])*"/,greedy:!0},"quoted-function":{pattern:/'(?:\\.|[^\\'\r\n])+'(?=\()/,alias:"function"},"quoted-atom":{pattern:/'(?:\\.|[^\\'\r\n])+'/,alias:"atom"},boolean:/\b(?:false|true)\b/,keyword:/\b(?:after|begin|case|catch|end|fun|if|of|receive|try|when)\b/,number:[/\$\\?./,/\b\d+#[a-z0-9]+/i,/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i],function:/\b[a-z][\w@]*(?=\()/,variable:{pattern:/(^|[^@])(?:\b|\?)[A-Z_][\w@]*/,lookbehind:!0},operator:[/[=\/<>:]=|=[:\/]=|\+\+?|--?|[=*\/!]|\b(?:and|andalso|band|bnot|bor|bsl|bsr|bxor|div|not|or|orelse|rem|xor)\b/,{pattern:/(^|[^<])<(?!<)/,lookbehind:!0},{pattern:/(^|[^>])>(?!>)/,lookbehind:!0}],atom:/\b[a-z][\w@]*/,punctuation:/[()[\]{}:;,.#|]|<<|>>/};
-Prism.languages.git={comment:/^#.*/m,deleted:/^[-–].*/m,inserted:/^\+.*/m,string:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s--?\w+/}},coord:/^@@.*@@$/m,"commit-sha1":/^commit \w{40}$/m};
-Prism.languages.go=Prism.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),Prism.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete Prism.languages.go["class-name"];
-Prism.languages["go-mod"]=Prism.languages["go-module"]={comment:{pattern:/\/\/.*/,greedy:!0},version:{pattern:/(^|[\s()[\],])v\d+\.\d+\.\d+(?:[+-][-+.\w]*)?(?![^\s()[\],])/,lookbehind:!0,alias:"number"},"go-version":{pattern:/((?:^|\s)go\s+)\d+(?:\.\d+){1,2}/,lookbehind:!0,alias:"number"},keyword:{pattern:/^([ \t]*)(?:exclude|go|module|replace|require|retract)\b/m,lookbehind:!0},operator:/=>/,punctuation:/[()[\],]/};
-Prism.languages.hcl={comment:/(?:\/\/|#).*|\/\*[\s\S]*?(?:\*\/|$)/,heredoc:{pattern:/<<-?(\w+\b)[\s\S]*?^[ \t]*\1/m,greedy:!0,alias:"string"},keyword:[{pattern:/(?:data|resource)\s+(?:"(?:\\[\s\S]|[^\\"])*")(?=\s+"[\w-]+"\s+\{)/i,inside:{type:{pattern:/(resource|data|\s+)(?:"(?:\\[\s\S]|[^\\"])*")/i,lookbehind:!0,alias:"variable"}}},{pattern:/(?:backend|module|output|provider|provisioner|variable)\s+(?:[\w-]+|"(?:\\[\s\S]|[^\\"])*")\s+(?=\{)/i,inside:{type:{pattern:/(backend|module|output|provider|provisioner|variable)\s+(?:[\w-]+|"(?:\\[\s\S]|[^\\"])*")\s+/i,lookbehind:!0,alias:"variable"}}},/[\w-]+(?=\s+\{)/],property:[/[-\w\.]+(?=\s*=(?!=))/,/"(?:\\[\s\S]|[^\\"])+"(?=\s*[:=])/],string:{pattern:/"(?:[^\\$"]|\\[\s\S]|\$(?:(?=")|\$+(?!\$)|[^"${])|\$\{(?:[^{}"]|"(?:[^\\"]|\\[\s\S])*")*\})*"/,greedy:!0,inside:{interpolation:{pattern:/(^|[^$])\$\{(?:[^{}"]|"(?:[^\\"]|\\[\s\S])*")*\}/,lookbehind:!0,inside:{type:{pattern:/(\b(?:count|data|local|module|path|self|terraform|var)\b\.)[\w\*]+/i,lookbehind:!0,alias:"variable"},keyword:/\b(?:count|data|local|module|path|self|terraform|var)\b/i,function:/\w+(?=\()/,string:{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0},number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?(?:e[+-]?\d+)?/i,punctuation:/[!\$#%&'()*+,.\/;<=>@\[\\\]^`{|}~?:]/}}}},number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?(?:e[+-]?\d+)?/i,boolean:/\b(?:false|true)\b/i,punctuation:/[=\[\]{}]/};
-Prism.languages.ini={comment:{pattern:/(^[ \f\t\v]*)[#;][^\n\r]*/m,lookbehind:!0},section:{pattern:/(^[ \f\t\v]*)\[[^\n\r\]]*\]?/m,lookbehind:!0,inside:{"section-name":{pattern:/(^\[[ \f\t\v]*)[^ \f\t\v\]]+(?:[ \f\t\v]+[^ \f\t\v\]]+)*/,lookbehind:!0,alias:"selector"},punctuation:/\[|\]/}},key:{pattern:/(^[ \f\t\v]*)[^ \f\n\r\t\v=]+(?:[ \f\t\v]+[^ \f\n\r\t\v=]+)*(?=[ \f\t\v]*=)/m,lookbehind:!0,alias:"attr-name"},value:{pattern:/(=[ \f\t\v]*)[^ \f\n\r\t\v]+(?:[ \f\t\v]+[^ \f\n\r\t\v]+)*/,lookbehind:!0,alias:"attr-value",inside:{"inner-value":{pattern:/^("|').+(?=\1$)/,lookbehind:!0}}},punctuation:/=/};
-!function(e){var n="\\\\\\((?:[^()]|\\([^()]*\\))*\\)",t=RegExp('(^|[^\\\\])"(?:[^"\r\n\\\\]|\\\\[^\r\n(]|__)*"'.replace(/__/g,(function(){return n}))),i={interpolation:{pattern:RegExp("((?:^|[^\\\\])(?:\\\\{2})*)"+n),lookbehind:!0,inside:{content:{pattern:/^(\\\()[\s\S]+(?=\)$)/,lookbehind:!0,inside:null},punctuation:/^\\\(|\)$/}}},a=e.languages.jq={comment:/#.*/,property:{pattern:RegExp(t.source+"(?=\\s*:(?!:))"),lookbehind:!0,greedy:!0,inside:i},string:{pattern:t,lookbehind:!0,greedy:!0,inside:i},function:{pattern:/(\bdef\s+)[a-z_]\w+/i,lookbehind:!0},variable:/\B\$\w+/,"property-literal":{pattern:/\b[a-z_]\w*(?=\s*:(?!:))/i,alias:"property"},keyword:/\b(?:as|break|catch|def|elif|else|end|foreach|if|import|include|label|module|modulemeta|null|reduce|then|try|while)\b/,boolean:/\b(?:false|true)\b/,number:/(?:\b\d+\.|\B\.)?\b\d+(?:[eE][+-]?\d+)?\b/,operator:[{pattern:/\|=?/,alias:"pipe"},/\.\.|[!=<>]?=|\?\/\/|\/\/=?|[-+*/%]=?|[<>?]|\b(?:and|not|or)\b/],"c-style-function":{pattern:/\b[a-z_]\w*(?=\s*\()/i,alias:"function"},punctuation:/::|[()\[\]{},:;]|\.(?=\s*[\[\w$])/,dot:{pattern:/\./,alias:"important"}};i.interpolation.inside.content.inside=a}(Prism);
-Prism.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[^z]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+(?:\.[a-f\d]*)?(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|(?:\.\d*)?(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/};
-Prism.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"builtin-target":{pattern:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,alias:"builtin"},target:{pattern:/^(?:[^:=\s]|[ \t]+(?![\s:]))+(?=\s*:(?!=))/m,alias:"symbol",inside:{variable:/\$+(?:(?!\$)[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:(?!\$)[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,function:{pattern:/(\()(?:abspath|addsuffix|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:list|s)?)(?=[ \t])/,lookbehind:!0},operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/};
-!function(n){function e(n){return n=n.replace(//g,(function(){return"(?:\\\\.|[^\\\\\n\r]|(?:\n|\r\n?)(?![\r\n]))"})),RegExp("((?:^|[^\\\\])(?:\\\\{2})*)(?:"+n+")")}var t="(?:\\\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\\\|\r\n`])+",a="\\|?__(?:\\|__)+\\|?(?:(?:\n|\r\n?)|(?![^]))".replace(/__/g,(function(){return t})),i="\\|?[ \t]*:?-{3,}:?[ \t]*(?:\\|[ \t]*:?-{3,}:?[ \t]*)+\\|?(?:\n|\r\n?)";n.languages.markdown=n.languages.extend("markup",{}),n.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:n.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+i+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+i+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(t),inside:n.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+i+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(t),alias:"important",inside:n.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:e("\\b__(?:(?!_)|_(?:(?!_))+_)+__\\b|\\*\\*(?:(?!\\*)|\\*(?:(?!\\*))+\\*)+\\*\\*"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:e("\\b_(?:(?!_)|__(?:(?!_))+__)+_\\b|\\*(?:(?!\\*)|\\*\\*(?:(?!\\*))+\\*\\*)+\\*"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:e("(~~?)(?:(?!~))+\\2"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:e('!?\\[(?:(?!\\]))+\\](?:\\([^\\s)]+(?:[\t ]+"(?:\\\\.|[^"\\\\])*")?\\)|[ \t]?\\[(?:(?!\\]))+\\])'),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(e){["url","bold","italic","strike","code-snippet"].forEach((function(t){e!==t&&(n.languages.markdown[e].inside.content.inside[t]=n.languages.markdown[t])}))})),n.hooks.add("after-tokenize",(function(n){"markdown"!==n.language&&"md"!==n.language||function n(e){if(e&&"string"!=typeof e)for(var t=0,a=e.length;t",quot:'"'},l=String.fromCodePoint||String.fromCharCode;n.languages.md=n.languages.markdown}(Prism);
-!function(e){var n=/\$(?:\w[a-z\d]*(?:_[^\x00-\x1F\s"'\\()$]*)?|\{[^}\s"'\\]+\})/i;e.languages.nginx={comment:{pattern:/(^|[\s{};])#.*/,lookbehind:!0,greedy:!0},directive:{pattern:/(^|\s)\w(?:[^;{}"'\\\s]|\\.|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\s+(?:#.*(?!.)|(?![#\s])))*?(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:{string:{pattern:/((?:^|[^\\])(?:\\\\)*)(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/,lookbehind:!0,greedy:!0,inside:{escape:{pattern:/\\["'\\nrt]/,alias:"entity"},variable:n}},comment:{pattern:/(\s)#.*/,lookbehind:!0,greedy:!0},keyword:{pattern:/^\S+/,greedy:!0},boolean:{pattern:/(\s)(?:off|on)(?!\S)/,lookbehind:!0},number:{pattern:/(\s)\d+[a-z]*(?!\S)/i,lookbehind:!0},variable:n}},punctuation:/[{};]/}}(Prism);
-Prism.languages.properties={comment:/^[ \t]*[#!].*$/m,value:{pattern:/(^[ \t]*(?:\\(?:\r\n|[\s\S])|[^\\\s:=])+(?: *[=:] *(?! )| ))(?:\\(?:\r\n|[\s\S])|[^\\\r\n])+/m,lookbehind:!0,alias:"attr-value"},key:{pattern:/^[ \t]*(?:\\(?:\r\n|[\s\S])|[^\\\s:=])+(?= *[=:]| )/m,alias:"attr-name"},punctuation:/[=:]/};
-Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern://,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python;
-Prism.languages.r={comment:/#.*/,string:{pattern:/(['"])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},"percent-operator":{pattern:/%[^%\s]*%/,alias:"operator"},boolean:/\b(?:FALSE|TRUE)\b/,ellipsis:/\.\.(?:\.|\d+)/,number:[/\b(?:Inf|NaN)\b/,/(?:\b0x[\dA-Fa-f]+(?:\.\d*)?|\b\d+(?:\.\d*)?|\B\.\d+)(?:[EePp][+-]?\d+)?[iL]?/],keyword:/\b(?:NA|NA_character_|NA_complex_|NA_integer_|NA_real_|NULL|break|else|for|function|if|in|next|repeat|while)\b/,operator:/->?>?|<(?:=|-)?|[>=!]=?|::?|&&?|\|\|?|[+*\/^$@~]/,punctuation:/[(){}\[\],;]/};
-Prism.languages.rest={table:[{pattern:/(^[\t ]*)(?:\+[=-]+)+\+(?:\r?\n|\r)(?:\1[+|].+[+|](?:\r?\n|\r))+\1(?:\+[=-]+)+\+/m,lookbehind:!0,inside:{punctuation:/\||(?:\+[=-]+)+\+/}},{pattern:/(^[\t ]*)=+ [ =]*=(?:(?:\r?\n|\r)\1.+)+(?:\r?\n|\r)\1=+ [ =]*=(?=(?:\r?\n|\r){2}|\s*$)/m,lookbehind:!0,inside:{punctuation:/[=-]+/}}],"substitution-def":{pattern:/(^[\t ]*\.\. )\|(?:[^|\s](?:[^|]*[^|\s])?)\| [^:]+::/m,lookbehind:!0,inside:{substitution:{pattern:/^\|(?:[^|\s]|[^|\s][^|]*[^|\s])\|/,alias:"attr-value",inside:{punctuation:/^\||\|$/}},directive:{pattern:/( )(?! )[^:]+::/,lookbehind:!0,alias:"function",inside:{punctuation:/::$/}}}},"link-target":[{pattern:/(^[\t ]*\.\. )\[[^\]]+\]/m,lookbehind:!0,alias:"string",inside:{punctuation:/^\[|\]$/}},{pattern:/(^[\t ]*\.\. )_(?:`[^`]+`|(?:[^:\\]|\\.)+):/m,lookbehind:!0,alias:"string",inside:{punctuation:/^_|:$/}}],directive:{pattern:/(^[\t ]*\.\. )[^:]+::/m,lookbehind:!0,alias:"function",inside:{punctuation:/::$/}},comment:{pattern:/(^[\t ]*\.\.)(?:(?: .+)?(?:(?:\r?\n|\r).+)+| .+)(?=(?:\r?\n|\r){2}|$)/m,lookbehind:!0},title:[{pattern:/^(([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])\2+)(?:\r?\n|\r).+(?:\r?\n|\r)\1$/m,inside:{punctuation:/^[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]+|[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]+$/,important:/.+/}},{pattern:/(^|(?:\r?\n|\r){2}).+(?:\r?\n|\r)([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])\2+(?=\r?\n|\r|$)/,lookbehind:!0,inside:{punctuation:/[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]+$/,important:/.+/}}],hr:{pattern:/((?:\r?\n|\r){2})([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])\2{3,}(?=(?:\r?\n|\r){2})/,lookbehind:!0,alias:"punctuation"},field:{pattern:/(^[\t ]*):[^:\r\n]+:(?= )/m,lookbehind:!0,alias:"attr-name"},"command-line-option":{pattern:/(^[\t ]*)(?:[+-][a-z\d]|(?:--|\/)[a-z\d-]+)(?:[ =](?:[a-z][\w-]*|<[^<>]+>))?(?:, (?:[+-][a-z\d]|(?:--|\/)[a-z\d-]+)(?:[ =](?:[a-z][\w-]*|<[^<>]+>))?)*(?=(?:\r?\n|\r)? {2,}\S)/im,lookbehind:!0,alias:"symbol"},"literal-block":{pattern:/::(?:\r?\n|\r){2}([ \t]+)(?![ \t]).+(?:(?:\r?\n|\r)\1.+)*/,inside:{"literal-block-punctuation":{pattern:/^::/,alias:"punctuation"}}},"quoted-literal-block":{pattern:/::(?:\r?\n|\r){2}([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~]).*(?:(?:\r?\n|\r)\1.*)*/,inside:{"literal-block-punctuation":{pattern:/^(?:::|([!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~])\1*)/m,alias:"punctuation"}}},"list-bullet":{pattern:/(^[\t ]*)(?:[*+\-•‣⁃]|\(?(?:\d+|[a-z]|[ivxdclm]+)\)|(?:\d+|[a-z]|[ivxdclm]+)\.)(?= )/im,lookbehind:!0,alias:"punctuation"},"doctest-block":{pattern:/(^[\t ]*)>>> .+(?:(?:\r?\n|\r).+)*/m,lookbehind:!0,inside:{punctuation:/^>>>/}},inline:[{pattern:/(^|[\s\-:\/'"<(\[{])(?::[^:]+:`.*?`|`.*?`:[^:]+:|(\*\*?|``?|\|)(?!\s)(?:(?!\2).)*\S\2(?=[\s\-.,:;!?\\\/'")\]}]|$))/m,lookbehind:!0,inside:{bold:{pattern:/(^\*\*).+(?=\*\*$)/,lookbehind:!0},italic:{pattern:/(^\*).+(?=\*$)/,lookbehind:!0},"inline-literal":{pattern:/(^``).+(?=``$)/,lookbehind:!0,alias:"symbol"},role:{pattern:/^:[^:]+:|:[^:]+:$/,alias:"function",inside:{punctuation:/^:|:$/}},"interpreted-text":{pattern:/(^`).+(?=`$)/,lookbehind:!0,alias:"attr-value"},substitution:{pattern:/(^\|).+(?=\|$)/,lookbehind:!0,alias:"attr-value"},punctuation:/\*\*?|``?|\|/}}],link:[{pattern:/\[[^\[\]]+\]_(?=[\s\-.,:;!?\\\/'")\]}]|$)/,alias:"string",inside:{punctuation:/^\[|\]_$/}},{pattern:/(?:\b[a-z\d]+(?:[_.:+][a-z\d]+)*_?_|`[^`]+`_?_|_`[^`]+`)(?=[\s\-.,:;!?\\\/'")\]}]|$)/i,alias:"string",inside:{punctuation:/^_?`|`$|`?_?_$/}}],punctuation:{pattern:/(^[\t ]*)(?:\|(?= |$)|(?:---?|—|\.\.|__)(?= )|\.\.$)/m,lookbehind:!0}};
-Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/};
-!function(e){function n(e){return e.replace(/__/g,(function(){return"(?:[\\w-]+|'[^'\n\r]*'|\"(?:\\\\.|[^\\\\\"\r\n])*\")"}))}e.languages.toml={comment:{pattern:/#.*/,greedy:!0},table:{pattern:RegExp(n("(^[\t ]*\\[\\s*(?:\\[\\s*)?)__(?:\\s*\\.\\s*__)*(?=\\s*\\])"),"m"),lookbehind:!0,greedy:!0,alias:"class-name"},key:{pattern:RegExp(n("(^[\t ]*|[{,]\\s*)__(?:\\s*\\.\\s*__)*(?=\\s*=)"),"m"),lookbehind:!0,greedy:!0,alias:"property"},string:{pattern:/"""(?:\\[\s\S]|[^\\])*?"""|'''[\s\S]*?'''|'[^'\n\r]*'|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},date:[{pattern:/\b\d{4}-\d{2}-\d{2}(?:[T\s]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?\b/i,alias:"number"},{pattern:/\b\d{2}:\d{2}:\d{2}(?:\.\d+)?\b/,alias:"number"}],number:/(?:\b0(?:x[\da-zA-Z]+(?:_[\da-zA-Z]+)*|o[0-7]+(?:_[0-7]+)*|b[10]+(?:_[10]+)*))\b|[-+]?\b\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?\b|[-+]?\b(?:inf|nan)\b/,boolean:/\b(?:false|true)\b/,punctuation:/[.,=[\]{}]/}}(Prism);
-Prism.languages.vim={string:/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\r\n]|'')*'/,comment:/".*/,function:/\b\w+(?=\()/,keyword:/\b(?:N|Next|P|Print|X|XMLent|XMLns|ab|abbreviate|abc|abclear|abo|aboveleft|al|all|ar|arga|argadd|argd|argdelete|argdo|arge|argedit|argg|argglobal|argl|arglocal|args|argu|argument|as|ascii|b|bN|bNext|ba|bad|badd|ball|bd|bdelete|be|bel|belowright|bf|bfirst|bl|blast|bm|bmodified|bn|bnext|bo|botright|bp|bprevious|br|brea|break|breaka|breakadd|breakd|breakdel|breakl|breaklist|brewind|bro|browse|bufdo|buffer|buffers|bun|bunload|bw|bwipeout|c|cN|cNext|cNfcNfile|ca|cabbrev|cabc|cabclear|cad|caddb|caddbuffer|caddexpr|caddf|caddfile|cal|call|cat|catch|cb|cbuffer|cc|ccl|cclose|cd|ce|center|cex|cexpr|cf|cfile|cfir|cfirst|cg|cgetb|cgetbuffer|cgete|cgetexpr|cgetfile|change|changes|chd|chdir|che|checkpath|checkt|checktime|cl|cla|clast|clist|clo|close|cmapc|cmapclear|cn|cnew|cnewer|cnext|cnf|cnfile|cnorea|cnoreabbrev|co|col|colder|colo|colorscheme|comc|comclear|comp|compiler|con|conf|confirm|continue|cope|copen|copy|cp|cpf|cpfile|cprevious|cq|cquit|cr|crewind|cu|cuna|cunabbrev|cunmap|cw|cwindow|d|debugg|debuggreedy|delc|delcommand|delete|delf|delfunction|delm|delmarks|di|diffg|diffget|diffoff|diffpatch|diffpu|diffput|diffsplit|diffthis|diffu|diffupdate|dig|digraphs|display|dj|djump|dl|dlist|dr|drop|ds|dsearch|dsp|dsplit|e|earlier|echoe|echoerr|echom|echomsg|echon|edit|el|else|elsei|elseif|em|emenu|en|endf|endfo|endfor|endfun|endfunction|endif|endt|endtry|endw|endwhile|ene|enew|ex|exi|exit|exu|exusage|f|file|files|filetype|fin|fina|finally|find|fini|finish|fir|first|fix|fixdel|fo|fold|foldc|foldclose|foldd|folddoc|folddoclosed|folddoopen|foldo|foldopen|for|fu|fun|function|go|goto|gr|grep|grepa|grepadd|h|ha|hardcopy|help|helpf|helpfind|helpg|helpgrep|helpt|helptags|hid|hide|his|history|ia|iabbrev|iabc|iabclear|if|ij|ijump|il|ilist|imapc|imapclear|in|inorea|inoreabbrev|isearch|isp|isplit|iu|iuna|iunabbrev|iunmap|j|join|ju|jumps|k|kee|keepalt|keepj|keepjumps|keepmarks|l|lN|lNext|lNf|lNfile|la|lad|laddb|laddbuffer|laddexpr|laddf|laddfile|lan|language|last|later|lb|lbuffer|lc|lcd|lch|lchdir|lcl|lclose|left|lefta|leftabove|let|lex|lexpr|lf|lfile|lfir|lfirst|lg|lgetb|lgetbuffer|lgete|lgetexpr|lgetfile|lgr|lgrep|lgrepa|lgrepadd|lh|lhelpgrep|list|ll|lla|llast|lli|llist|lm|lmak|lmake|lmap|lmapc|lmapclear|ln|lne|lnew|lnewer|lnext|lnf|lnfile|lnoremap|lo|loadview|loc|lockmarks|lockv|lockvar|lol|lolder|lop|lopen|lp|lpf|lpfile|lprevious|lr|lrewind|ls|lt|ltag|lu|lunmap|lv|lvimgrep|lvimgrepa|lvimgrepadd|lw|lwindow|m|ma|mak|make|mark|marks|mat|match|menut|menutranslate|mk|mkexrc|mks|mksession|mksp|mkspell|mkv|mkvie|mkview|mkvimrc|mod|mode|move|mz|mzf|mzfile|mzscheme|n|nbkey|new|next|nmapc|nmapclear|noh|nohlsearch|norea|noreabbrev|nu|number|nun|nunmap|o|omapc|omapclear|on|only|open|opt|options|ou|ounmap|p|pc|pclose|pe|ped|pedit|perl|perld|perldo|po|pop|popu|popup|pp|ppop|pre|preserve|prev|previous|print|prof|profd|profdel|profile|promptf|promptfind|promptr|promptrepl|ps|psearch|ptN|ptNext|pta|ptag|ptf|ptfirst|ptj|ptjump|ptl|ptlast|ptn|ptnext|ptp|ptprevious|ptr|ptrewind|pts|ptselect|pu|put|pw|pwd|py|pyf|pyfile|python|q|qa|qall|quit|quita|quitall|r|read|rec|recover|red|redi|redir|redo|redr|redraw|redraws|redrawstatus|reg|registers|res|resize|ret|retab|retu|return|rew|rewind|ri|right|rightb|rightbelow|ru|rub|ruby|rubyd|rubydo|rubyf|rubyfile|runtime|rv|rviminfo|sN|sNext|sa|sal|sall|san|sandbox|sargument|sav|saveas|sb|sbN|sbNext|sba|sball|sbf|sbfirst|sbl|sblast|sbm|sbmodified|sbn|sbnext|sbp|sbprevious|sbr|sbrewind|sbuffer|scrip|scripte|scriptencoding|scriptnames|se|set|setf|setfiletype|setg|setglobal|setl|setlocal|sf|sfind|sfir|sfirst|sh|shell|sign|sil|silent|sim|simalt|sl|sla|slast|sleep|sm|smagic|smap|smapc|smapclear|sme|smenu|sn|snext|sni|sniff|sno|snomagic|snor|snoremap|snoreme|snoremenu|so|sor|sort|source|sp|spe|spelld|spelldump|spellgood|spelli|spellinfo|spellr|spellrepall|spellu|spellundo|spellw|spellwrong|split|spr|sprevious|sre|srewind|st|sta|stag|star|startg|startgreplace|startinsert|startr|startreplace|stj|stjump|stop|stopi|stopinsert|sts|stselect|sun|sunhide|sunm|sunmap|sus|suspend|sv|sview|syncbind|t|tN|tNext|ta|tab|tabN|tabNext|tabc|tabclose|tabd|tabdo|tabe|tabedit|tabf|tabfind|tabfir|tabfirst|tabl|tablast|tabm|tabmove|tabn|tabnew|tabnext|tabo|tabonly|tabp|tabprevious|tabr|tabrewind|tabs|tag|tags|tc|tcl|tcld|tcldo|tclf|tclfile|te|tearoff|tf|tfirst|th|throw|tj|tjump|tl|tlast|tm|tmenu|tn|tnext|to|topleft|tp|tprevious|tr|trewind|try|ts|tselect|tu|tunmenu|u|una|unabbreviate|undo|undoj|undojoin|undol|undolist|unh|unhide|unlet|unlo|unlockvar|unm|unmap|up|update|ve|verb|verbose|version|vert|vertical|vi|vie|view|vim|vimgrep|vimgrepa|vimgrepadd|visual|viu|viusage|vmapc|vmapclear|vne|vnew|vs|vsplit|vu|vunmap|w|wN|wNext|wa|wall|wh|while|win|winc|wincmd|windo|winp|winpos|winsize|wn|wnext|wp|wprevious|wq|wqa|wqall|write|ws|wsverb|wv|wviminfo|x|xa|xall|xit|xm|xmap|xmapc|xmapclear|xme|xmenu|xn|xnoremap|xnoreme|xnoremenu|xu|xunmap|y|yank)\b/,builtin:/\b(?:acd|ai|akm|aleph|allowrevins|altkeymap|ambiwidth|ambw|anti|antialias|arab|arabic|arabicshape|ari|arshape|autochdir|autocmd|autoindent|autoread|autowrite|autowriteall|aw|awa|background|backspace|backup|backupcopy|backupdir|backupext|backupskip|balloondelay|ballooneval|balloonexpr|bdir|bdlay|beval|bex|bexpr|bg|bh|bin|binary|biosk|bioskey|bk|bkc|bomb|breakat|brk|browsedir|bs|bsdir|bsk|bt|bufhidden|buflisted|buftype|casemap|ccv|cdpath|cedit|cfu|ch|charconvert|ci|cin|cindent|cink|cinkeys|cino|cinoptions|cinw|cinwords|clipboard|cmdheight|cmdwinheight|cmp|cms|columns|com|comments|commentstring|compatible|complete|completefunc|completeopt|consk|conskey|copyindent|cot|cpo|cpoptions|cpt|cscopepathcomp|cscopeprg|cscopequickfix|cscopetag|cscopetagorder|cscopeverbose|cspc|csprg|csqf|cst|csto|csverb|cuc|cul|cursorcolumn|cursorline|cwh|debug|deco|def|define|delcombine|dex|dg|dict|dictionary|diff|diffexpr|diffopt|digraph|dip|dir|directory|dy|ea|ead|eadirection|eb|ed|edcompatible|ef|efm|ei|ek|enc|encoding|endofline|eol|ep|equalalways|equalprg|errorbells|errorfile|errorformat|esckeys|et|eventignore|expandtab|exrc|fcl|fcs|fdc|fde|fdi|fdl|fdls|fdm|fdn|fdo|fdt|fen|fenc|fencs|fex|ff|ffs|fileencoding|fileencodings|fileformat|fileformats|fillchars|fk|fkmap|flp|fml|fmr|foldcolumn|foldenable|foldexpr|foldignore|foldlevel|foldlevelstart|foldmarker|foldmethod|foldminlines|foldnestmax|foldtext|formatexpr|formatlistpat|formatoptions|formatprg|fp|fs|fsync|ft|gcr|gd|gdefault|gfm|gfn|gfs|gfw|ghr|gp|grepformat|grepprg|gtl|gtt|guicursor|guifont|guifontset|guifontwide|guiheadroom|guioptions|guipty|guitablabel|guitabtooltip|helpfile|helpheight|helplang|hf|hh|hi|hidden|highlight|hk|hkmap|hkmapp|hkp|hl|hlg|hls|hlsearch|ic|icon|iconstring|ignorecase|im|imactivatekey|imak|imc|imcmdline|imd|imdisable|imi|iminsert|ims|imsearch|inc|include|includeexpr|incsearch|inde|indentexpr|indentkeys|indk|inex|inf|infercase|insertmode|invacd|invai|invakm|invallowrevins|invaltkeymap|invanti|invantialias|invar|invarab|invarabic|invarabicshape|invari|invarshape|invautochdir|invautoindent|invautoread|invautowrite|invautowriteall|invaw|invawa|invbackup|invballooneval|invbeval|invbin|invbinary|invbiosk|invbioskey|invbk|invbl|invbomb|invbuflisted|invcf|invci|invcin|invcindent|invcompatible|invconfirm|invconsk|invconskey|invcopyindent|invcp|invcscopetag|invcscopeverbose|invcst|invcsverb|invcuc|invcul|invcursorcolumn|invcursorline|invdeco|invdelcombine|invdg|invdiff|invdigraph|invdisable|invea|inveb|inved|invedcompatible|invek|invendofline|inveol|invequalalways|inverrorbells|invesckeys|invet|invex|invexpandtab|invexrc|invfen|invfk|invfkmap|invfoldenable|invgd|invgdefault|invguipty|invhid|invhidden|invhk|invhkmap|invhkmapp|invhkp|invhls|invhlsearch|invic|invicon|invignorecase|invim|invimc|invimcmdline|invimd|invincsearch|invinf|invinfercase|invinsertmode|invis|invjoinspaces|invjs|invlazyredraw|invlbr|invlinebreak|invlisp|invlist|invloadplugins|invlpl|invlz|invma|invmacatsui|invmagic|invmh|invml|invmod|invmodeline|invmodifiable|invmodified|invmore|invmousef|invmousefocus|invmousehide|invnu|invnumber|invodev|invopendevice|invpaste|invpi|invpreserveindent|invpreviewwindow|invprompt|invpvw|invreadonly|invremap|invrestorescreen|invrevins|invri|invrightleft|invrightleftcmd|invrl|invrlc|invro|invrs|invru|invruler|invsb|invsc|invscb|invscrollbind|invscs|invsecure|invsft|invshellslash|invshelltemp|invshiftround|invshortname|invshowcmd|invshowfulltag|invshowmatch|invshowmode|invsi|invsm|invsmartcase|invsmartindent|invsmarttab|invsmd|invsn|invsol|invspell|invsplitbelow|invsplitright|invspr|invsr|invssl|invsta|invstartofline|invstmp|invswapfile|invswf|invta|invtagbsearch|invtagrelative|invtagstack|invtbi|invtbidi|invtbs|invtermbidi|invterse|invtextauto|invtextmode|invtf|invtgst|invtildeop|invtimeout|invtitle|invto|invtop|invtr|invttimeout|invttybuiltin|invttyfast|invtx|invvb|invvisualbell|invwa|invwarn|invwb|invweirdinvert|invwfh|invwfw|invwildmenu|invwinfixheight|invwinfixwidth|invwiv|invwmnu|invwrap|invwrapscan|invwrite|invwriteany|invwritebackup|invws|isf|isfname|isi|isident|isk|iskeyword|isprint|joinspaces|js|key|keymap|keymodel|keywordprg|km|kmp|kp|langmap|langmenu|laststatus|lazyredraw|lbr|lcs|linebreak|lines|linespace|lisp|lispwords|listchars|loadplugins|lpl|lsp|lz|macatsui|magic|makeef|makeprg|matchpairs|matchtime|maxcombine|maxfuncdepth|maxmapdepth|maxmem|maxmempattern|maxmemtot|mco|mef|menuitems|mfd|mh|mis|mkspellmem|ml|mls|mm|mmd|mmp|mmt|modeline|modelines|modifiable|modified|more|mouse|mousef|mousefocus|mousehide|mousem|mousemodel|mouses|mouseshape|mouset|mousetime|mp|mps|msm|mzq|mzquantum|nf|noacd|noai|noakm|noallowrevins|noaltkeymap|noanti|noantialias|noar|noarab|noarabic|noarabicshape|noari|noarshape|noautochdir|noautoindent|noautoread|noautowrite|noautowriteall|noaw|noawa|nobackup|noballooneval|nobeval|nobin|nobinary|nobiosk|nobioskey|nobk|nobl|nobomb|nobuflisted|nocf|noci|nocin|nocindent|nocompatible|noconfirm|noconsk|noconskey|nocopyindent|nocp|nocscopetag|nocscopeverbose|nocst|nocsverb|nocuc|nocul|nocursorcolumn|nocursorline|nodeco|nodelcombine|nodg|nodiff|nodigraph|nodisable|noea|noeb|noed|noedcompatible|noek|noendofline|noeol|noequalalways|noerrorbells|noesckeys|noet|noex|noexpandtab|noexrc|nofen|nofk|nofkmap|nofoldenable|nogd|nogdefault|noguipty|nohid|nohidden|nohk|nohkmap|nohkmapp|nohkp|nohls|noic|noicon|noignorecase|noim|noimc|noimcmdline|noimd|noincsearch|noinf|noinfercase|noinsertmode|nois|nojoinspaces|nojs|nolazyredraw|nolbr|nolinebreak|nolisp|nolist|noloadplugins|nolpl|nolz|noma|nomacatsui|nomagic|nomh|noml|nomod|nomodeline|nomodifiable|nomodified|nomore|nomousef|nomousefocus|nomousehide|nonu|nonumber|noodev|noopendevice|nopaste|nopi|nopreserveindent|nopreviewwindow|noprompt|nopvw|noreadonly|noremap|norestorescreen|norevins|nori|norightleft|norightleftcmd|norl|norlc|noro|nors|noru|noruler|nosb|nosc|noscb|noscrollbind|noscs|nosecure|nosft|noshellslash|noshelltemp|noshiftround|noshortname|noshowcmd|noshowfulltag|noshowmatch|noshowmode|nosi|nosm|nosmartcase|nosmartindent|nosmarttab|nosmd|nosn|nosol|nospell|nosplitbelow|nosplitright|nospr|nosr|nossl|nosta|nostartofline|nostmp|noswapfile|noswf|nota|notagbsearch|notagrelative|notagstack|notbi|notbidi|notbs|notermbidi|noterse|notextauto|notextmode|notf|notgst|notildeop|notimeout|notitle|noto|notop|notr|nottimeout|nottybuiltin|nottyfast|notx|novb|novisualbell|nowa|nowarn|nowb|noweirdinvert|nowfh|nowfw|nowildmenu|nowinfixheight|nowinfixwidth|nowiv|nowmnu|nowrap|nowrapscan|nowrite|nowriteany|nowritebackup|nows|nrformats|numberwidth|nuw|odev|oft|ofu|omnifunc|opendevice|operatorfunc|opfunc|osfiletype|pa|para|paragraphs|paste|pastetoggle|patchexpr|patchmode|path|pdev|penc|pex|pexpr|pfn|ph|pheader|pi|pm|pmbcs|pmbfn|popt|preserveindent|previewheight|previewwindow|printdevice|printencoding|printexpr|printfont|printheader|printmbcharset|printmbfont|printoptions|prompt|pt|pumheight|pvh|pvw|qe|quoteescape|readonly|remap|report|restorescreen|revins|rightleft|rightleftcmd|rl|rlc|ro|rs|rtp|ruf|ruler|rulerformat|runtimepath|sbo|sc|scb|scr|scroll|scrollbind|scrolljump|scrolloff|scrollopt|scs|sect|sections|secure|sel|selection|selectmode|sessionoptions|sft|shcf|shellcmdflag|shellpipe|shellquote|shellredir|shellslash|shelltemp|shelltype|shellxquote|shiftround|shiftwidth|shm|shortmess|shortname|showbreak|showcmd|showfulltag|showmatch|showmode|showtabline|shq|si|sidescroll|sidescrolloff|siso|sj|slm|smartcase|smartindent|smarttab|smc|smd|softtabstop|sol|spc|spell|spellcapcheck|spellfile|spelllang|spellsuggest|spf|spl|splitbelow|splitright|sps|sr|srr|ss|ssl|ssop|stal|startofline|statusline|stl|stmp|su|sua|suffixes|suffixesadd|sw|swapfile|swapsync|swb|swf|switchbuf|sws|sxq|syn|synmaxcol|syntax|t_AB|t_AF|t_AL|t_CS|t_CV|t_Ce|t_Co|t_Cs|t_DL|t_EI|t_F1|t_F2|t_F3|t_F4|t_F5|t_F6|t_F7|t_F8|t_F9|t_IE|t_IS|t_K1|t_K3|t_K4|t_K5|t_K6|t_K7|t_K8|t_K9|t_KA|t_KB|t_KC|t_KD|t_KE|t_KF|t_KG|t_KH|t_KI|t_KJ|t_KK|t_KL|t_RI|t_RV|t_SI|t_Sb|t_Sf|t_WP|t_WS|t_ZH|t_ZR|t_al|t_bc|t_cd|t_ce|t_cl|t_cm|t_cs|t_da|t_db|t_dl|t_fs|t_k1|t_k2|t_k3|t_k4|t_k5|t_k6|t_k7|t_k8|t_k9|t_kB|t_kD|t_kI|t_kN|t_kP|t_kb|t_kd|t_ke|t_kh|t_kl|t_kr|t_ks|t_ku|t_le|t_mb|t_md|t_me|t_mr|t_ms|t_nd|t_op|t_se|t_so|t_sr|t_te|t_ti|t_ts|t_ue|t_us|t_ut|t_vb|t_ve|t_vi|t_vs|t_xs|tabline|tabpagemax|tabstop|tagbsearch|taglength|tagrelative|tagstack|tal|tb|tbi|tbidi|tbis|tbs|tenc|term|termbidi|termencoding|terse|textauto|textmode|textwidth|tgst|thesaurus|tildeop|timeout|timeoutlen|title|titlelen|titleold|titlestring|toolbar|toolbariconsize|top|tpm|tsl|tsr|ttimeout|ttimeoutlen|ttm|tty|ttybuiltin|ttyfast|ttym|ttymouse|ttyscroll|ttytype|tw|tx|uc|ul|undolevels|updatecount|updatetime|ut|vb|vbs|vdir|verbosefile|vfile|viewdir|viewoptions|viminfo|virtualedit|visualbell|vop|wak|warn|wb|wc|wcm|wd|weirdinvert|wfh|wfw|whichwrap|wi|wig|wildchar|wildcharm|wildignore|wildmenu|wildmode|wildoptions|wim|winaltkeys|window|winfixheight|winfixwidth|winheight|winminheight|winminwidth|winwidth|wiv|wiw|wm|wmh|wmnu|wmw|wop|wrap|wrapmargin|wrapscan|writeany|writebackup|writedelay|ww)\b/,number:/\b(?:0x[\da-f]+|\d+(?:\.\d+)?)\b/i,operator:/\|\||&&|[-+.]=?|[=!](?:[=~][#?]?)?|[<>]=?[#?]?|[*\/%?]|\b(?:is(?:not)?)\b/,punctuation:/[{}[\](),;:]/};
-!function(e){var n=/[*&][^\s[\]{},]+/,r=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,t="(?:"+r.source+"(?:[ \t]+"+n.source+")?|"+n.source+"(?:[ \t]+"+r.source+")?)",a="(?:[^\\s\\x00-\\x08\\x0e-\\x1f!\"#%&'*,\\-:>?@[\\]`{|}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*".replace(//g,(function(){return"[^\\s\\x00-\\x08\\x0e-\\x1f,[\\]{}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]"})),d="\"(?:[^\"\\\\\r\n]|\\\\.)*\"|'(?:[^'\\\\\r\n]|\\\\.)*'";function o(e,n){n=(n||"").replace(/m/g,"")+"m";var r="([:\\-,[{]\\s*(?:\\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\\]|\\}|(?:[\r\n]\\s*)?#))".replace(/<>/g,(function(){return t})).replace(/<>/g,(function(){return e}));return RegExp(r,n)}e.languages.yaml={scalar:{pattern:RegExp("([\\-:]\\s*(?:\\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\\S[^\r\n]*(?:\\2[^\r\n]+)*)".replace(/<>/g,(function(){return t}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp("((?:^|[:\\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\\s*:\\s)".replace(/<>/g,(function(){return t})).replace(/<>/g,(function(){return"(?:"+a+"|"+d+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:o("\\d{4}-\\d\\d?-\\d\\d?(?:[tT]|[ \t]+)\\d\\d?:\\d{2}:\\d{2}(?:\\.\\d*)?(?:[ \t]*(?:Z|[-+]\\d\\d?(?::\\d{2})?))?|\\d{4}-\\d{2}-\\d{2}|\\d\\d?:\\d{2}(?::\\d{2}(?:\\.\\d*)?)?"),lookbehind:!0,alias:"number"},boolean:{pattern:o("false|true","i"),lookbehind:!0,alias:"important"},null:{pattern:o("null|~","i"),lookbehind:!0,alias:"important"},string:{pattern:o(d),lookbehind:!0,greedy:!0},number:{pattern:o("[+-]?(?:0x[\\da-f]+|0o[0-7]+|(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?|\\.inf|\\.nan)","i"),lookbehind:!0},tag:r,important:n,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(Prism);
diff --git a/tailwind.config.js b/tailwind.config.js
deleted file mode 100644
index b7b9c40d..00000000
--- a/tailwind.config.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-module.exports = {
- content: ["./templates/*.html"],
- plugins: [require("@tailwindcss/typography")],
- variants: {
- typography: ["dark"],
- },
- theme: {
- fontFamily: {
- texts: ["Erode", "serif"],
- headings: ["Fira Sans", "sans-serif"],
- },
- extend: {
- // NOTE: this is for non-prose (so not Markdown rendered with typography plugin)
- colors: {
- // https://rosepinetheme.com/palette/
- "rp-dawn-base": "#faf4ed", // main background
- "rp-dawn-surface": "#fffaf3", // navigation background
- "rp-dawn-overlay": "#f2e9e1", // content background
- "rp-dawn-muted": "#9893a5",
- "rp-dawn-subtle": "#797593",
- "rp-dawn-text": "#575279", // text
- "rp-dawn-love": "#b4637a",
- "rp-dawn-gold": "#ea9d34",
- "rp-dawn-rose": "#d7827e",
- "rp-dawn-pine": "#286983",
- "rp-dawn-foam": "#56949f", // main site title
- "rp-dawn-iris": "#907aa9", // links
- "rp-dawn-highlight-low": "#f4ede8",
- "rp-dawn-highlight-med": "#dfdad9", // footer
- "rp-dawn-highlight-high": "#cecacd",
- "rp-moon-base": "#232136", // main background
- "rp-moon-surface": "#2a273f", // navigation background
- "rp-moon-overlay": "#393552", // content background
- "rp-moon-muted": "#6e6a86",
- "rp-moon-subtle": "#908caa",
- "rp-moon-text": "#e0def4",
- "rp-moon-love": "#eb6f92",
- "rp-moon-gold": "#f6c177",
- "rp-moon-rose": "#ea9a97",
- "rp-moon-pine": "#3e8fb0",
- "rp-moon-foam": "#9ccfd8", // main site title
- "rp-moon-iris": "#c4a7e7", // text,
- "rp-moon-highlight-low": "#2a283e",
- "rp-moon-highlight-med": "#44415a", // footer
- "rp-moon-highlight-high": "#56526e",
- },
- typography: (theme) => ({
- // NOTE: This is for prose (Markdown) in LIGHT mode
- DEFAULT: {
- css: {
- maxWidth: "80ch",
- pre: null,
- code: null,
- "code::before": null,
- "code::after": null,
- "pre code": null,
- "pre code::before": null,
- "pre code::after": null,
- // remove backticks from typography for inline code
- "code::before": {
- content: '""',
- },
- "code::after": {
- content: '""',
- },
- color: theme("colors.rp-dawn-text"), // main text
- a: {
- color: theme("colors.rp-dawn-text"), // align w/ in.css
- "text-decoration-color": theme("colors.rp-dawn-gold"), // align w/ in.css
- "text-decoration-thickness": "2px", // align w/ in.css
- },
- "h1, h2, h3, h4, h5, h6": {
- color: theme("colors.rp-dawn-text"),
- },
- blockquote: {
- "border-color": theme("colors.rp-dawn-muted"),
- "background-color": theme("colors.rp-dawn-overlay"),
- color: theme("colors.rp-dawn-text"),
- },
- strong: {
- color: theme("colors.rp-dawn-text"), // align w/ main text color
- fontWeight: "800",
- },
- th: {
- color: theme("colors.rp-dawn-text"),
- },
- },
- },
- // NOTE: This is for prose (Markdown) in DARK mode
- dark: {
- css: {
- pre: null,
- code: null,
- "pre code": null,
- color: theme("colors.rp-moon-iris"), // main text
- a: {
- color: theme("colors.rp-moon-iris"), // align w/ in.css
- "text-decoration-color": theme("colors.rp-moon-pine"), // align w/ in.css
- "text-decoration-thickness": "2px", // align w/ in.css
- },
- "h1, h2, h3, h4, h5, h6": {
- color: theme("colors.rp-moon-love"),
- },
- blockquote: {
- "border-color": theme("colors.rp-moon-overlay"),
- "background-color": theme("colors.rp-moon-surface"),
- color: theme("colors.rp-moon-text"),
- },
- strong: {
- color: theme("colors.rp-moon-iris"), // align w/ main text color
- fontWeight: "800",
- },
- th: {
- color: theme("colors.rp-moon-iris"),
- },
- },
- },
- }),
- },
- },
- corePlugins: {
- // preflight: false,
- divideStyle: true,
- },
-};
diff --git a/templates/archives.html b/templates/archives.html
deleted file mode 100644
index 9b15a164..00000000
--- a/templates/archives.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Blog archive - {{ SITENAME|striptags }}{% endblock %}
-
-{% block content %}
-
-
Blog post archive
-
- {% for article in dates %}
- {% set year = article.date.strftime('%Y') %}
- {% if loop.first %}
-
- {% else %}
- {% set prevyear = loop.previtem.date.strftime('%Y') %}
- {% if prevyear != year %}
-
- {% endif %}
- {% endif %}
- {% set month = article.date.strftime('%m') %}
- {% set day = article.date.strftime('%d') %}
- {{ article.locale_date }}: {{ article.title }}
- {%if article.subtitle %}
- {{ article.subtitle }}
- {% endif %}
- {% endfor %}
-
-
-{% endblock %}
diff --git a/templates/article.html b/templates/article.html
deleted file mode 100644
index c4dc973d..00000000
--- a/templates/article.html
+++ /dev/null
@@ -1,139 +0,0 @@
-{% extends "base.html" %}
-{% block html_lang %}{{ article.lang }}{% endblock %}
-
-{% block title %}{{ article.title|striptags }} - {{ SITENAME|striptags }}{% endblock %}
-
-{% block head %}
- {{ super() }}
-
- {% if article.description %}
-
- {% endif %}
-
- {% for tag in article.tags %}
-
- {% endfor %}
-
-{% endblock %}
-
-{% block content %}
-
-
- {{ article.content }}
-
-
-
- {% if article.category %}
-
- Category
-
- {% endif %}
- {% if article.tags %}
-
- Tags
-
- {% endif %}
-
-
- {% if article.category %}
-
- {{ article.category }}
-
- {% endif %}
- {% if article.tags %}
-
- {% for tag in article.tags %}
- {{ tag }} {{"," if not loop.last }}
- {% endfor %}
-
- {% endif %}
-
-
- {% if article.share_post and article.status != 'draft' %}
-
- {% endif %}
-
-
-{% endblock %}
diff --git a/templates/author.html b/templates/author.html
deleted file mode 100644
index 1a5a8903..00000000
--- a/templates/author.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends "index.html" %}
-
-{% block title %}{{ SITENAME|striptags }} - Articles by {{ author }}{% endblock %}
-
-{% block content_title %}
- Articles by {{ author }}
-{% endblock %}
diff --git a/templates/authors.html b/templates/authors.html
deleted file mode 100644
index 548421fb..00000000
--- a/templates/authors.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Authors - {{ SITENAME|striptags }}{% endblock %}
-
-{% block content %}
- Authors
- It's all me, silly you.
-
-
-
-
-
-{% endblock %}
diff --git a/templates/base.html b/templates/base.html
deleted file mode 100644
index ad7df453..00000000
--- a/templates/base.html
+++ /dev/null
@@ -1,160 +0,0 @@
-
-
-
- {% block head %}
- {% block title %}{{ SITENAME|striptags }}{% endblock title %}
-
-
-
- {% if SITESUBTITLE %}
-
- {% endif %}
-
- {% if STYLESHEET_URL %}
-
- {% endif %}
- {% if FEED_ALL_ATOM %}
-
- {% endif %}
- {% if FEED_ALL_RSS %}
-
- {% endif %}
- {% if FEED_ATOM %}
-
- {% endif %}
- {% if FEED_RSS %}
-
- {% endif %}
- {% if CATEGORY_FEED_ATOM and category %}
-
- {% endif %}
- {% if CATEGORY_FEED_RSS and category %}
-
- {% endif %}
- {% if TAG_FEED_ATOM and tag %}
-
- {% endif %}
- {% if TAG_FEED_RSS and tag %}
-
- {% endif %}
-
-
-
-
- {% endblock head %}
-
-
-
-
-
-
-
-
-
- {% if SITESUBTITLE %}
- {{ SITESUBTITLE }}
- {% endif %}
-
-
-
-
-
-
- {% if DISPLAY_PAGES_ON_MENU %}
-
- {% endif %}
- {% for title, link in MENUITEMS %}
- {{ title }}
- {% endfor %}
-
- {% for title, link in LINKS %}
-
{{ title }}
- {% endfor %}
-
- {% if DISPLAY_CATEGORIES_ON_MENU %}
-
- {% for cat, null in categories %}
-
{{ cat}}
- {% endfor %}
-
- {% endif %}
-
-
-
-
-
-
-
-
-
- {% block content %}
- {% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/templates/categories.html b/templates/categories.html
deleted file mode 100644
index 0f298e8c..00000000
--- a/templates/categories.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Categories - {{ SITENAME|striptags }}{% endblock %}
-
-{% block content %}
-
-
Blog categories
-
- {% for category, articles in categories|sort %}
- {{ category }} ({{ articles|count }})
- {% endfor %}
-
-
-{% endblock %}
diff --git a/templates/category.html b/templates/category.html
deleted file mode 100644
index 8edc3bc1..00000000
--- a/templates/category.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends "index.html" %}
-
-{% block title %}{{ category }} - {{ SITENAME|striptags }}{% endblock %}
-
-{% block content_title %}
-Tales from the {{ category }} department
-{% endblock %}
diff --git a/templates/index.html b/templates/index.html
deleted file mode 100644
index dcdd4cbc..00000000
--- a/templates/index.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{% extends "base.html" %}
-{% block content %}
- {% block content_title %}
- Recent blog posts
- {% endblock %}
-
-
- {% for article in articles_page.object_list %}
-
-
-
- {{ article.locale_date }}
-
-
-
-
-
- {% endfor %}
-
-
- {% if articles_page.has_other_pages() %}
- {% include 'pagination.html' %}
- {% endif %}
-{% endblock content %}
diff --git a/templates/page.html b/templates/page.html
deleted file mode 100644
index fbc1985d..00000000
--- a/templates/page.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% extends "base.html" %}
-{% block html_lang %}{{ page.lang }}{% endblock %}
-{% block title %}{{ page.title|striptags }} - {{ SITENAME|striptags }} {%endblock%}
-
-{% block head %}
- {{ super() }}
-{% endblock %}
-
-{% block content %}
-
-
-
{{ page.title }}
- {{ page.content }}
-
-
-{% endblock %}
diff --git a/templates/pagination.html b/templates/pagination.html
deleted file mode 100644
index 933904be..00000000
--- a/templates/pagination.html
+++ /dev/null
@@ -1,60 +0,0 @@
-{% if DEFAULT_PAGINATION %}
-{% set first_page = articles_paginator.page(1) %}
-{% set last_page = articles_paginator.page(articles_paginator.num_pages) %}
-
- {% if articles_page.has_previous() %}
-
-
-
- «
-
-
- {% else %}
-
-
- {% endif %}
-
-
-
- {{ articles_page.number }}
-
-
- {% if articles_page.has_next() %}
-
-
- {{ articles_page.next_page_number() }}
-
-
-
- …
-
-
-
- {{ articles_paginator.num_pages }}
-
-
-
-
- »
-
-
- {% endif %}
-
-
-{% endif %}
diff --git a/templates/tag.html b/templates/tag.html
deleted file mode 100644
index 0696ceb2..00000000
--- a/templates/tag.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends "index.html" %}
-
-{% block title %} {{ tag|capitalize }} - {{ SITENAME|striptags }}{% endblock %}
-
-{% block content_title %}
-Tales tagged with {{ tag }}
-{% endblock %}
diff --git a/templates/tags.html b/templates/tags.html
deleted file mode 100644
index 3280842f..00000000
--- a/templates/tags.html
+++ /dev/null
@@ -1,26 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Tags - {{ SITENAME|striptags }}{% endblock %}
-
-{% block content %}
- Article tags
-
-
- {% for tag in tag_cloud %}
-
-
- {{ tag.0 }}
-
- {% if TAG_CLOUD_BADGE %}
- {{ tag.2 }}
- {% endif %}
-
- {% endfor %}
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000..8763c963
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,27 @@
+# This tests the unified codebase (py27, py33) of Pelican.
+# depends on some external libraries that aren't released yet.
+
+[tox]
+envlist = py27,py33
+
+[testenv]
+commands =
+ python -m unittest discover
+deps =
+
+[testenv:py27]
+deps =
+ mock
+ Markdown
+ BeautifulSoup4
+ typogrify
+ lxml
+
+[testenv:py33]
+deps =
+ mock
+ Markdown
+ BeautifulSoup4
+ git+https://github.com/dmdm/smartypants.git#egg=smartypants
+ git+https://github.com/dmdm/typogrify.git@py3k#egg=typogrify
+ lxml