Blogdown (empty) → 0.1.0
raw patch · 22 files changed
+2082/−0 lines, 22 filesdep +MissingHdep +basedep +containerssetup-changed
Dependencies added: MissingH, base, containers, parsec
Files
- Blogdown.cabal +47/−0
- ChangeLog.md +5/−0
- LICENSE +661/−0
- Readme.html +189/−0
- Readme.md +163/−0
- Setup.hs +2/−0
- assets/footnotes.css +18/−0
- assets/footnotes.js +41/−0
- src/AST.hs +65/−0
- src/Blogdown.hs +11/−0
- src/Parsing/Parse.hs +18/−0
- src/Parsing/ParseBlock.hs +83/−0
- src/Parsing/ParseFootnotes.hs +27/−0
- src/Parsing/ParseHtml.hs +51/−0
- src/Parsing/ParseInline.hs +71/−0
- src/Parsing/State.hs +19/−0
- src/Parsing/Text.hs +49/−0
- src/Parsing/TextUtils.hs +40/−0
- src/Parsing/Utils.hs +26/−0
- src/Rendering/Render.hs +132/−0
- src/Rendering/RenderOptions.hs +37/−0
- test/Test.hs +327/−0
+ Blogdown.cabal view
@@ -0,0 +1,47 @@+name: Blogdown+version: 0.1.0+synopsis: A markdown-like markup language designed for blog posts+description: A library and executable that implement a modified, extended version of Markdown designed for writing blog posts.+license: AGPL-3+license-file: LICENSE+author: Alex Becker+maintainer: acbecker@uchicago.edu+copyright: (c) 2017 Alex Becker+category: Web+build-type: Simple+extra-source-files:+ Readme.html+ ChangeLog.md+ Readme.md+data-files: assets/footnotes.css, assets/footnotes.js+cabal-version: >=1.10++library+ exposed-modules: Parsing.Parse, Rendering.Render, Rendering.RenderOptions+ other-modules: AST, Parsing.ParseBlock, Parsing.ParseInline, Parsing.Text, Parsing.Utils, Parsing.ParseFootnotes, Parsing.ParseHtml, Parsing.State, Parsing.TextUtils, Paths_Blogdown+ other-extensions: CPP, DeriveGeneric+ build-depends: base >=4.9 && <4.10, parsec >=3.1 && <3.2, containers >=0.5 && <0.6, MissingH >=1.4 && <1.5+ hs-source-dirs: src+ default-language: Haskell2010+ cpp-options: -DCABAL++executable Blogdown+ main-is: Blogdown.hs+ other-extensions: CPP, DeriveGeneric+ build-depends: base >=4.9 && <4.10, parsec >=3.1 && <3.2, containers >=0.5 && <0.6, MissingH >=1.4 && <1.5+ hs-source-dirs: src+ default-language: Haskell2010+ cpp-options: -DCABAL++test-suite Test+ type: exitcode-stdio-1.0+ main-is: Test.hs+ other-extensions: CPP, DeriveGeneric+ build-depends: base >=4.9 && <4.10, parsec >=3.1 && <3.2, containers >=0.5 && <0.6, MissingH >=1.4 && <1.5+ hs-source-dirs: src, test+ default-language: Haskell2010+ cpp-options: -DCABAL++source-repository Head+ type: git+ location: https://github.com/alexbecker/blogdown
+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for Blogdown++## 0.1.0 -- 2017-04-02++* Released as a Cabal package.
+ LICENSE view
@@ -0,0 +1,661 @@+ GNU AFFERO GENERAL PUBLIC LICENSE+ Version 3, 19 November 2007++ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++ Preamble++ 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 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.++ <one line to give the program's name and a brief idea of what it does.>+ Copyright (C) <year> <name of author>++ 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 <http://www.gnu.org/licenses/>.++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+<http://www.gnu.org/licenses/>.
+ Readme.html view
@@ -0,0 +1,189 @@+<p><i>Note: If you are viewing this Readme on GitHub, its Blogdown-specific features will not render correctly.+The Blogdown-formatted output is in Readme.html.</i></p>+<h1>Blogdown</h1>+<p>Blogdown is a markup language based on Markdown, designed for writing blog posts.+Blogdown's goals are:</p>+<ul><li>Clean syntax for common blog post features absent from Markdown.</li>+<li>Ability to handle untrusted input.</li>+<li>Simple syntax, with no surprises.</li>+<li>Debuggability.</li>+<li>Near compatibility with common Markdown implementations.</li>+</ul>+<p>Because there is no Markdown standard and existing Markdown implementations+<a href="http://johnmacfarlane.net/babelmark2/?text=Hello+world%0A*+this+is+a+list%0A%3E+this+is+a+quote">disagree wildly even on simple cases</a>,+Blogdown cannot be 100% compatible with even a majority of Markdown implementations.+While there have been attemps to create a common Markdown standard—most notably <a href="http://commonmark.org/">CommonMark</a>—they+are necessarily quite complex. The primary cause of this complexity is that Markdown insists on rendering <i>something</i> for every input,+no matter how malformed. Blogdown is considerably simpler, and hopefully easier for authors to debug, because it fails on malformed inputs.+With full compatability out of the window, I have chosen to make some other small improvements on Markdown syntax.</p>+<h2>Installation</h2>+<h3>With Cabal or Stack</h3>+<p>The recommended way to install any Haskell project is using <a href="https://www.haskell.org/cabal/">Cabal</a> or <a href="https://docs.haskellstack.org/en/stable/README/">Stack</a>.+With these tools, you can simply run <code>cabal install</code> or <code>stack build</code> respectively.</p>+<h3>Without Haskell Tooling</h3>+<p>Because configuring Cabal and Stack can be tricky for Haskell beginners, Blogdown supports installation without any Haskell tooling.</p>+<p>First, install <a href="https://www.haskell.org/ghc/">GHC</a>, <a href="https://hackage.haskell.org/package/parsec">Parsec</a> and <a href="https://hackage.haskell.org/package/MissingH">MissingH</a>,+all of which are available through common Linux package managers.+Then run <code>ghc -isrc -o Blogdown src/Blogdown.hs</code> in the repository's base directory.</p>+<h2>Usage</h2>+<p>The <code>Blogdown</code> binary reads from <code>stdin</code> and writes to <code>stdout</code>. Typical usage looks like:</p>+<pre><code>cat blogpost.md | ./Blogdown > blogpost.html+</code></pre>+<h3>Optional Styling and Scripts</h3>+<p>It is recommended to include <code>footnotes.css</code> and <code>footnotes.js</code> on any pages which make use of Blogdown-generated footnotes,+which improve the appearance of footnotes and allow them to be shown inline.+These can be inlined using the <code>--inline-css</code> and <code>--inline-js</code> flags respectively<sup><a href="#-footnote-0" id="a--footnote-0">[0]</a></sup>.</p>+<h3>Optional Flags</h3>+<p><code>Blogdown</code> accepts the following long-style flags:</p>+<ul><li><code>--footnote-prefix</code>: Defines a prefix for the <code>id</code>s of footnotes. Recommended if multiple output files are included in a single HTML page, to avoid <code>id</code> collisions.</li>+<li><code>--footnote-index-from</code>: The index from which footnotes are numbered. Defaults to 0.</li>+<li><code>--footnote-backlinks</code>: If this flag is passed, footnotes will be preceded by a caret linking back to the point where the footnote is referenced.</li>+<li><code>--em-dashes</code>: If this flag is passed, <code>--</code> will be replaced with "—" in text.</li>+<li><code>--inline-css</code>: If this flag is passed, the recommended CSS will be inlined at the end of the output document.</li>+<li><code>--inline-js</code>: If this flag is passed, the recommended JS will be inlined at the end of the output document.</li>+</ul>+<h2>Syntax</h2>+<h3>Differences from Markdown</h3>+<p>Most of the syntax of Blogdown should be familiar to Markdown users, but some new syntax has been added, and some existing syntax has changed.</p>+<h4>New Features</h4>+<p>Blogdown adds footnote support to Markdown.+Footnotes can be referenced inline with ^[<i>footnote-name</i>], which will render as a superscript link to a <i>footnote-definition</i>+at the end of the document, which is defined by ~[<i>footnote-name</i>] followed by the footnote contents.</p>+<h4>Markdown Incompatibilities</h4>+<p>Blogdown does not support the Markdown syntax of underlining text with <code>=</code> or <code>-</code> characters to define a header,+as this comes at a large cost in the parser implementation<sup><a href="#-footnote-1" id="a--footnote-1">[1]</a></sup>.+The <code>#</code> syntax for headers is supported instead.</p>+<p>It also does not support using multiple trailing spaces to force a breakpoint at the end of a line.+The <code><br/></code> tag is supported instead.</p>+<p>The <code>~</code> and <code>^</code> characters are now special, and must be escaped to be used in text.+Additionally, while most Markdown implementations do not require escaping many special characters when their special meaning would+not be valid, Blogdown always requires they be escaped.</p>+<h3>Formal Description</h3>+<p>The body of a Blogdown document consists of a sequence of <i>block nodes</i>, which in turn consist of <i>inline nodes</i>.</p>+<h4>Block Nodes</h4>+<p>Block nodes can contain any sequence of inline nodes, with the exception of code and HTML blocks, whose contents are rendered verbatim.+Block nodes can span multiple lines and are terminated by a blank line, the beginning of another type of block, or the end of the document.</p>+<p>The following block node types are supported:</p>+<ul><li><b>Paragraph</b>: The default block type; any content not in another block is part of a paragraph.+Paragraphs must be separated by a blank line.+Can contain arbitrary inline nodes.</li>+<li><b>Header</b>: 1-6 <code>#</code> characters at the beginning of a line begins a header, with the number of <code>#</code> characters determining the header level.+Can contain arbitrary inline nodes.</li>+<li><b>Ordered Lists</b>: A <code> - </code> begins an ordered list item, which itself is a block.+Sequential ordered list items form an ordered list.+Can contain arbitrary inline nodes.</li>+<li><b>Unordered Lists</b>: A <code> * </code> begins an unordered list item, which itself is a block.+Sequential unordered list items form an unordered list.+Can contain arbitrary inline nodes.</li>+<li><b>Blockquote</b>: Lines beginning with <code>> </code> define a blockquote.+Can contain arbitrary inline nodes.+Note that the first line not beginning with <code>> </code> will start a new block.</li>+<li><b>Code Block</b>: Lines indented with 4+ spaces or a tab define a code block.+Code blocks are rendered verbatim, ignoring special characters.+Note that the first un-indented line will start a new block.</li>+<li><b>HTML Block</b>: An HTML tag at the beginning of a line starts an HTML block.+Its contents must be valid HTML, and it is ended by the corresponding closing tag.+HTML blocks are rendered verbatim, unless HTML bleaching is enabled.</li>+<li><b>Hard Rule</b>: A line consisting of 3+ <code>-</code> defines a hard rule.</li>+<li><b>Table</b>: A <code>|</code>character at the beginning of a line begins a table row, consisting of table cells separated by <code>|</code> characters.+The cells are themselves blocks, and as such can contain newlines. The rows are terminated by a <code>|</code> followed by a newline.+By default the table has only a body, but if rows are separated by an alternating string of <code>+</code> and multiple <code>-</code> characters,+e.g. <code>+---+---+</code>, then every row above the separator will be in the header and every row below will be in the body.+Optionally the table may start and end with such a separator as well.</li>+</ul>+<h4>Inline Nodes</h4>+<p>Inline nodes can generally contain a sequence of other inline nodes, but cannot contain nodes of the same type.+Despite the name, inline nodes can span multiple lines, e.g. to accomodate line length limits.</p>+<p>The following inline node types are supported:</p>+<ul><li><b>Plaintext</b>: The default inline type; any text not in another inline node is plaintext. Rendered verbatim.</li>+<li><b>Italic</b>: Surrounding text with <code>*</code> <i>italicizes</i> it. Italic nodes can contain any other type of inline node.</li>+<li><b>Bold</b>: Surrounding text with <code>**</code> <b>bolds</b> it. Bold nodes can contain any other type of inline node.</li>+<li><b>Code</b>: Surrounding text with <code>`</code> renders it as <code>code</code>. The content is rendered verbatim.</li>+<li><b>Link</b>: A <a href="#">link</a> is written as [<i>text</i>](<i>href</i>). The <i>text</i> portion can contain any other type of inline node.+The <i>href</i> portion is the link destination, and is parsed verbatim except that any literal <code>(</code> or <code>)</code> must be escaped.</li>+<li><b>Footnote Reference</b>: Writing ^[<i>footnote-name</i>] defines a footnote reference.+It is rendered as a superscript footnote number<sup><a href="#-footnote-2" id="a--footnote-2">[2]</a></sup>, and links to the footnote named <i>footnote-name</i>+if it is present in the footer.</li>+</ul>+<h4>Footer</h4>+<p>A Blogdown document may optionally include a footer after the body.+The footer consists of a sequence of <i>footnote definitions</i>, each of which begins on a new line with ~[<i>footnote-name</i>] and consists of an arbitrary sequence of blocks.+A footnote definition is only terminated by another footnote definition or the end of the document.</p>+<h3>Escaping</h3>+<p>Any character (special or not) can be escaped with <code>\</code>. For a literal backslash, use <code>\\</code>.+A backslash before a newline acts as a continuation character.</p>+<h2>Planned improvements</h2>+<ul><li>Nested lists</li>+<li>Better error messages on parse failures</li>+<li>Windows support</li>+<li>Better tests</li>+<li>Document building & running tests</li>+<li>Comments</li>+</ul>+<hr/>+<ol start="0" class="footnotes"><li id="-footnote-0"><p>Inlining CSS and JS is not recommended if you will be rendering multiple Blogdown documents on a single page, e.g. multiple blog posts on a blog.+Doing so will degrade network and browser performance slightly.</p></li>+<li id="-footnote-1"><p>Supporting underlines for headers requires the parser to look-ahead arbitrarily far, resulting in quadratic time complexity.</p></li>+<li id="-footnote-2"><p>Footnotes are auto-numbered in order of appearance, starting from 0 by default (this can be changed by passing the <code>--footnote-index-from</code> flag).</p></li>+</ol>+<style>.footnotes > li {+ margin: 15px 0;+}++/* Keeps the back-link on the same line as the first block. */+.footnotes > li > *:nth-child(2) {+ display: inline;+}++.inline-footnote {+ margin: .5em;+ padding: .5em;+ border-left: 2px solid #555;+}++.inline-footnote > p {+ margin: 0;+}+</style>+<script>function inlineFootnote(footnote) {+ var ref = document.getElementById("a-" + footnote.id);+ var inline = document.createElement("div");+ for (var i=0; i<footnote.children.length;) {+ var child = footnote.children[i];+ if (child.tagName == "A" && child.getAttribute("href") == "#" + ref.id) {+ i++;+ continue;+ }+ inline.appendChild(child);+ }+ inline.className = "inline-footnote";+ inline.style.display = "none";+ ref.parentNode.parentNode.insertBefore(inline, ref.parentNode.nextSibling);+ ref.origIndex = ref.textContent;+ ref.expanded = false;+ ref.addEventListener("click", function (e) {+ e.preventDefault();+ if (ref.expanded) {+ inline.style.display = "none";+ ref.textContent = ref.origIndex;+ } else {+ inline.style.display = "block";+ ref.textContent = "X";+ }+ ref.expanded = !ref.expanded;+ });+}++function inlineFootnotes() {+ var footnoteLists = document.getElementsByClassName("footnotes");+ while (footnoteLists.length > 0) {+ var footnotes = footnoteLists[0].children;+ for (var j=0; j<footnotes.length; j++) {+ inlineFootnote(footnotes[j]);+ }+ footnoteLists[0].remove();+ }+}++inlineFootnotes()+</script>
+ Readme.md view
@@ -0,0 +1,163 @@+*Note: If you are viewing this Readme on GitHub, its Blogdown-specific features will not render correctly.+The Blogdown-formatted output is in Readme.html.*++# Blogdown++Blogdown is a markup language based on Markdown, designed for writing blog posts.+Blogdown's goals are:+ * Clean syntax for common blog post features absent from Markdown.+ * Ability to handle untrusted input.+ * Simple syntax, with no surprises.+ * Debuggability.+ * Near compatibility with common Markdown implementations.++Because there is no Markdown standard and existing Markdown implementations+[disagree wildly even on simple cases](http://johnmacfarlane.net/babelmark2/?text=Hello+world%0A*+this+is+a+list%0A%3E+this+is+a+quote),+Blogdown cannot be 100% compatible with even a majority of Markdown implementations.+While there have been attemps to create a common Markdown standard--most notably [CommonMark](http://commonmark.org/)--they+are necessarily quite complex. The primary cause of this complexity is that Markdown insists on rendering *something* for every input,+no matter how malformed. Blogdown is considerably simpler, and hopefully easier for authors to debug, because it fails on malformed inputs.+With full compatability out of the window, I have chosen to make some other small improvements on Markdown syntax.++## Installation++### With Cabal or Stack++The recommended way to install any Haskell project is using [Cabal](https://www.haskell.org/cabal/) or [Stack](https://docs.haskellstack.org/en/stable/README/).+With these tools, you can simply run `cabal install` or `stack build` respectively.++### Without Haskell Tooling++Because configuring Cabal and Stack can be tricky for Haskell beginners, Blogdown supports installation without any Haskell tooling.++First, install [GHC](https://www.haskell.org/ghc/), [Parsec](https://hackage.haskell.org/package/parsec) and [MissingH](https://hackage.haskell.org/package/MissingH),+all of which are available through common Linux package managers.+Then run `ghc -isrc -o Blogdown src/Blogdown.hs` in the repository's base directory.++## Usage++The `Blogdown` binary reads from `stdin` and writes to `stdout`. Typical usage looks like:++ cat blogpost.md | ./Blogdown > blogpost.html++### Optional Styling and Scripts++It is recommended to include `footnotes.css` and `footnotes.js` on any pages which make use of Blogdown-generated footnotes,+which improve the appearance of footnotes and allow them to be shown inline.+These can be inlined using the `--inline-css` and `--inline-js` flags respectively^[inline].++### Optional Flags++`Blogdown` accepts the following long-style flags:+ * `--footnote-prefix`: Defines a prefix for the `id`s of footnotes. Recommended if multiple output files are included in a single HTML page, to avoid `id` collisions.+ * `--footnote-index-from`: The index from which footnotes are numbered. Defaults to 0.+ * `--footnote-backlinks`: If this flag is passed, footnotes will be preceded by a caret linking back to the point where the footnote is referenced.+ * `--em-dashes`: If this flag is passed, `--` will be replaced with "—" in text.+ * `--inline-css`: If this flag is passed, the recommended CSS will be inlined at the end of the output document.+ * `--inline-js`: If this flag is passed, the recommended JS will be inlined at the end of the output document.++## Syntax++### Differences from Markdown++Most of the syntax of Blogdown should be familiar to Markdown users, but some new syntax has been added, and some existing syntax has changed.++#### New Features++Blogdown adds footnote support to Markdown.+Footnotes can be referenced inline with \^\[*footnote-name*\], which will render as a superscript link to a *footnote-definition*+at the end of the document, which is defined by \~\[*footnote-name*\] followed by the footnote contents.++#### Markdown Incompatibilities++Blogdown does not support the Markdown syntax of underlining text with `=` or `-` characters to define a header,+as this comes at a large cost in the parser implementation^[underline-parser-complexity].+The `#` syntax for headers is supported instead.++It also does not support using multiple trailing spaces to force a breakpoint at the end of a line.+The `<br/>` tag is supported instead.++The `\~` and `\^` characters are now special, and must be escaped to be used in text.+Additionally, while most Markdown implementations do not require escaping many special characters when their special meaning would+not be valid, Blogdown always requires they be escaped.++### Formal Description++The body of a Blogdown document consists of a sequence of *block nodes*, which in turn consist of *inline nodes*.++#### Block Nodes++Block nodes can contain any sequence of inline nodes, with the exception of code and HTML blocks, whose contents are rendered verbatim.+Block nodes can span multiple lines and are terminated by a blank line, the beginning of another type of block, or the end of the document.++The following block node types are supported:+ * **Paragraph**: The default block type; any content not in another block is part of a paragraph.+Paragraphs must be separated by a blank line.+Can contain arbitrary inline nodes.+ * **Header**: 1-6 `#` characters at the beginning of a line begins a header, with the number of `#` characters determining the header level.+Can contain arbitrary inline nodes.+ * **Ordered Lists**: A ` - ` begins an ordered list item, which itself is a block.+Sequential ordered list items form an ordered list.+Can contain arbitrary inline nodes.+ * **Unordered Lists**: A ` * ` begins an unordered list item, which itself is a block.+Sequential unordered list items form an unordered list.+Can contain arbitrary inline nodes.+ * **Blockquote**: Lines beginning with `> ` define a blockquote.+Can contain arbitrary inline nodes.+Note that the first line not beginning with `> ` will start a new block.+ * **Code Block**: Lines indented with 4+ spaces or a tab define a code block.+Code blocks are rendered verbatim, ignoring special characters.+Note that the first un-indented line will start a new block.+ * **HTML Block**: An HTML tag at the beginning of a line starts an HTML block.+Its contents must be valid HTML, and it is ended by the corresponding closing tag.+HTML blocks are rendered verbatim, unless HTML bleaching is enabled.+ * **Hard Rule**: A line consisting of 3+ `-` defines a hard rule.+ * **Table**: A `|`character at the beginning of a line begins a table row, consisting of table cells separated by `|` characters.+The cells are themselves blocks, and as such can contain newlines. The rows are terminated by a `|` followed by a newline.+By default the table has only a body, but if rows are separated by an alternating string of `+` and multiple `-` characters,+e.g. `+---+---+`, then every row above the separator will be in the header and every row below will be in the body.+Optionally the table may start and end with such a separator as well.++#### Inline Nodes++Inline nodes can generally contain a sequence of other inline nodes, but cannot contain nodes of the same type.+Despite the name, inline nodes can span multiple lines, e.g. to accomodate line length limits.++The following inline node types are supported:+ * **Plaintext**: The default inline type; any text not in another inline node is plaintext. Rendered verbatim.+ * **Italic**: Surrounding text with `*` *italicizes* it. Italic nodes can contain any other type of inline node.+ * **Bold**: Surrounding text with `**` **bolds** it. Bold nodes can contain any other type of inline node.+ * **Code**: Surrounding text with `\`` renders it as `code`. The content is rendered verbatim.+ * **Link**: A [link](#) is written as \[*text*\]\(*href*\). The *text* portion can contain any other type of inline node.+The *href* portion is the link destination, and is parsed verbatim except that any literal `(` or `)` must be escaped.+ * **Footnote Reference**: Writing \^\[*footnote-name*\] defines a footnote reference.+It is rendered as a superscript footnote number^[footnote-numbering], and links to the footnote named *footnote-name*+if it is present in the footer.++#### Footer++A Blogdown document may optionally include a footer after the body.+The footer consists of a sequence of *footnote definitions*, each of which begins on a new line with \~\[*footnote-name*\] and consists of an arbitrary sequence of blocks.+A footnote definition is only terminated by another footnote definition or the end of the document.++### Escaping++Any character (special or not) can be escaped with `\\`. For a literal backslash, use `\\\\`.+A backslash before a newline acts as a continuation character.++## Planned improvements+ * Nested lists+ * Better error messages on parse failures+ * Windows support+ * Better tests+ * Document building & running tests+ * Comments++---++~[inline] Inlining CSS and JS is not recommended if you will be rendering multiple Blogdown documents on a single page, e.g. multiple blog posts on a blog.+Doing so will degrade network and browser performance slightly.++~[underline-parser-complexity] Supporting underlines for headers requires the parser to look-ahead arbitrarily far, resulting in quadratic time complexity.++~[footnote-numbering] Footnotes are auto-numbered in order of appearance, starting from 0 by default (this can be changed by passing the `--footnote-index-from` flag).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ assets/footnotes.css view
@@ -0,0 +1,18 @@+.footnotes > li {+ margin: 15px 0;+}++/* Keeps the back-link on the same line as the first block. */+.footnotes > li > *:nth-child(2) {+ display: inline;+}++.inline-footnote {+ margin: .5em;+ padding: .5em;+ border-left: 2px solid #555;+}++.inline-footnote > p {+ margin: 0;+}
+ assets/footnotes.js view
@@ -0,0 +1,41 @@+function inlineFootnote(footnote) {+ var ref = document.getElementById("a-" + footnote.id);+ var inline = document.createElement("div");+ for (var i=0; i<footnote.children.length;) {+ var child = footnote.children[i];+ if (child.tagName == "A" && child.getAttribute("href") == "#" + ref.id) {+ i++;+ continue;+ }+ inline.appendChild(child);+ }+ inline.className = "inline-footnote";+ inline.style.display = "none";+ ref.parentNode.parentNode.insertBefore(inline, ref.parentNode.nextSibling);+ ref.origIndex = ref.textContent;+ ref.expanded = false;+ ref.addEventListener("click", function (e) {+ e.preventDefault();+ if (ref.expanded) {+ inline.style.display = "none";+ ref.textContent = ref.origIndex;+ } else {+ inline.style.display = "block";+ ref.textContent = "X";+ }+ ref.expanded = !ref.expanded;+ });+}++function inlineFootnotes() {+ var footnoteLists = document.getElementsByClassName("footnotes");+ while (footnoteLists.length > 0) {+ var footnotes = footnoteLists[0].children;+ for (var j=0; j<footnotes.length; j++) {+ inlineFootnote(footnotes[j]);+ }+ footnoteLists[0].remove();+ }+}++inlineFootnotes()
+ src/AST.hs view
@@ -0,0 +1,65 @@+module AST where++data AST = AST [Block] (Maybe FootnoteDefs)+ deriving (Show)++data FootnoteDefs = FootnoteDefs [FootnoteDef]+ deriving (Show)++data FootnoteDef = FootnoteDef {+ index :: Int,+ content :: [Block]+}+ deriving (Show)++data ListItem = ListItem Bool [Inline]+ deriving (Show)++data TableCell = TableHeaderCell [Inline]+ | TableBodyCell [Inline]+ deriving (Show)++data TableRow = TableRow [TableCell]+ deriving (Show)++data Block = HardRule+ | Paragraph [Inline]+ | Header Int [Inline]+ | OrderedList [ListItem]+ | UnorderedList [ListItem]+ | BlockQuote [Inline]+ | BlockCode String+ | BlockHtml Html+ | Table {+ thead :: Maybe [TableRow],+ body :: [TableRow]+}+ deriving (Show)++data Inline = Italics [Inline]+ | Bold [Inline]+ | Code String+ | FootnoteRef Int+ | Plaintext String+ | InlineHtml Html+ | Link {+ text :: [Inline],+ href :: String+}+ deriving (Show)++data HtmlTagType = Open | Close | SelfClosing+ deriving (Show, Eq)++data HtmlTag = HtmlTag {+ tagname :: String,+ attrs :: [Attr]+}+ deriving (Show)++data Attr = Attr String String+ deriving (Show)++data Html = PairTag HtmlTag [Either String Html]+ | SingleTag HtmlTag+ deriving (Show)
+ src/Blogdown.hs view
@@ -0,0 +1,11 @@+module Main where++import Parsing.Parse+import Rendering.Render+import Rendering.RenderOptions++main :: IO ()+main = do+ renderOptions <- getRenderOptions+ input <- getContents+ either (putStrLn . show) (putStr . toHtml renderOptions) $ parse input
+ src/Parsing/Parse.hs view
@@ -0,0 +1,18 @@+module Parsing.Parse where++import Text.Parsec hiding (parse)++import AST+import Parsing.State+import Parsing.ParseBlock+import Parsing.ParseFootnotes++ast :: Parser AST+ast = do+ blocks <- many block+ footnotes <- optionMaybe footnoteDefs+ eof+ return $ AST blocks footnotes++parse :: String -> Either ParseError AST+parse = runParser ast initialState ""
+ src/Parsing/ParseBlock.hs view
@@ -0,0 +1,83 @@+module Parsing.ParseBlock where++import Text.Parsec++import AST+import Parsing.State+import Parsing.TextUtils+import Parsing.Utils+import Parsing.ParseHtml+import Parsing.ParseInline++hardRule :: Parser Block+hardRule = do+ try (string "---") <?> "\"---\" (hard rule)"+ many (char '-')+ many1 (char '\n')+ return HardRule++paragraph :: Parser Block+paragraph = fmap Paragraph $ many1 inline++header :: Parser Block+header = do+ hashes <- (many1 $ char' '#') <?> "\"#\" (header)"+ many1 $ oneOf " \t"+ text <- many1 inline+ return $ Header (length hashes) text++listItem :: Bool -> Parser ListItem+listItem ordered = fmap (ListItem ordered) $ do+ let identifier = if ordered then " - " else " * "+ (try $ string' identifier) <?> ("\"" ++ identifier ++ "\" (list item)")+ many1 $ inline++orderedList :: Parser Block+orderedList = fmap OrderedList $ many1 $ listItem True++unorderedList :: Parser Block+unorderedList = fmap UnorderedList $ many1 $ listItem False++blockQuoteLineStart :: Parser String+blockQuoteLineStart = try (string "> ") <?> "\"> \" (blockquote)"++blockQuote :: Parser Block+blockQuote = fmap BlockQuote $ do+ blockQuoteLineStart+ withModifiedState (many1 inline) $ \s -> s {prevCharIsNewline=False, skipPrefix=(blockQuoteLineStart >> many (char ' '))}++blockCodeLineStart :: Parser String+blockCodeLineStart = try (string "\t" <|> string " ") <?> "\" \" or tab (code block)"++blockCode :: Parser Block+blockCode = fmap (BlockCode . unlines) $ many1 $ blockCodeLineStart >> manyTill (noneOf "\n") (char' '\n')++blockHtml :: Parser Block+blockHtml = fmap BlockHtml html++-- Does not return TableRow because we don't know what type the cells are until the whole table is parsed.+tableRow :: Parser [[Inline]]+tableRow = manyTill ((char' '|' <?> "\"|\" (table cell)") >> many1 inline) (try $ string' "|\n")++tableSeparator :: Parser ()+tableSeparator = optional $ do+ sepBy1 (char '+' <?> "\"+\" (table)") (optionMaybe (char ' ') >> many1 (char '-') >> optionMaybe (char ' '))+ char' '\n'++table :: Parser Block+table = do+ tableSeparator+ rows <- many1 $ tableRow+ tableSeparator+ rows2 <- many $ tableRow+ let headerRows = if null rows2+ then Nothing+ else Just $ map (TableRow . map TableHeaderCell) rows+ let bodyRows = map (TableRow . map TableBodyCell) $ if null rows2 then rows else rows2+ if null rows2+ then return ()+ else tableSeparator+ return $ Table headerRows bodyRows++block :: Parser Block+block = (many $ char '\n') >> choice [blockHtml, hardRule, header, orderedList, unorderedList, blockQuote, table, blockCode, paragraph]
+ src/Parsing/ParseFootnotes.hs view
@@ -0,0 +1,27 @@+module Parsing.ParseFootnotes where++import Data.Maybe+import Text.Parsec+import qualified Data.Map.Strict as M++import AST+import Parsing.State+import Parsing.TextUtils+import Parsing.ParseBlock++footnoteDef :: Parser FootnoteDef+footnoteDef = do+ many $ char '\n'+ identifier <- betweenWithErrors' "~[" "]" "footnote definition" $ many1 $ escapableNoneOf "[]"+ state <- getState+ let maybeIndex = M.lookup identifier $ footnoteIndices state+ index <- if isNothing maybeIndex+ then fail $ "unreferenced footnote identifier: " ++ identifier+ else return $ fromJust maybeIndex+ many1 $ oneOf " \t"+ modifyState (\s -> s {prevCharIsNewline=False})+ content <- many1 $ try block+ return $ FootnoteDef index content++footnoteDefs :: Parser FootnoteDefs+footnoteDefs = fmap FootnoteDefs $ many1 footnoteDef
+ src/Parsing/ParseHtml.hs view
@@ -0,0 +1,51 @@+module Parsing.ParseHtml (html) where++import Text.Parsec++import AST+import Parsing.State+import Parsing.Utils++htmlTag :: HtmlTagType -> Parser HtmlTag+htmlTag tagType = do+ if tagType == Close+ then try (string "</") <?> "\"</\" (closing html tag)"+ else string "<" <?> "\"<\" (html tag)"+ spaces+ tagname <- many1 (letter <?> "rest of tag name") <?> "html tag name"+ attrs <- many attr+ if tagType == SelfClosing+ then try (string "/>") <?> "closing \"/>\" (self-closing html tag)"+ else string ">" <?> "closing \">\" (html tag)"+ return $ HtmlTag {tagname=tagname, attrs=attrs}++htmlContent :: Parser (Either String Html)+htmlContent = fmap Left (many1 $ noneOf "<") <|> fmap Right html++pairTag :: Parser Html+pairTag = do+ open <- htmlTag Open+ content <- option [] $ many $ try htmlContent+ close <- htmlTag Close+ if tagname open /= tagname close+ then fail $ "mismatched tags: '" ++ tagname open ++ "' and '" ++ tagname close ++ "'"+ else return $ PairTag open content++singleTag :: Parser Html+singleTag = fmap SingleTag $ htmlTag SelfClosing++-- TODO: consider eliminating arbitrary-length backtracking on [singleTag]+-- by limiting tag name length and checking against self-closing tag names.+html :: Parser Html+html = try singleTag <|> pairTag++attr :: Parser Attr+attr = do+ space >> spaces+ name <- many1 letter <?> "html attribute name"+ char '='+ val <- attrVal+ return $ Attr name val++attrVal :: Parser String+attrVal = betweenWithErrors "\"" "\"" "html attribute value" (many $ noneOf "\"")
+ src/Parsing/ParseInline.hs view
@@ -0,0 +1,71 @@+module Parsing.ParseInline (inline) where++import Data.Maybe+import Text.Parsec+import qualified Data.Map.Strict as M++import AST+import Parsing.State+import Parsing.Text+import Parsing.TextUtils+import Parsing.Utils+import Parsing.ParseHtml++nestedBold :: Parser Inline+nestedBold = suppressErr (try (string "****")) >> fail "cannot have empty or nested bold nodes"++bold :: Parser Inline+bold = do+ state <- getState+ let currentParserStack = inlineParserStack state+ failIf $ elem "bold" currentParserStack+ s <- betweenWithErrors' "**" "**" "bold"+ $ withModifiedState (many1 inline) $ \s -> s {inlineParserStack=("bold" : currentParserStack)}+ return $ Bold s++-- The bold and italics parsers are tricky because they both use the same special character.+italics :: Parser Inline+italics = do+ state <- getState+ let currentParserStack = inlineParserStack state+ failIf $ elem "italics" currentParserStack+ s <- between+ (try ((char' '*' <?> "\"*\" (italics)") >> suppressErr (notFollowedBy (char '*'))))+ (char' '*' <?> "closing \"*\" (italics)")+ ((withModifiedState (many1 inline) $ \s -> s {inlineParserStack=("italics" : currentParserStack)}) <?>+ if elem "bold" currentParserStack+ then "content in italics node or extra \"*\" to close bold node"+ else "content in italics node")+ return $ Italics s++code :: Parser Inline+code = fmap Code $ betweenWithErrors' "`" "`" "code" $ many1 $ escapableNoneOf "`"++footnoteRef :: Parser Inline+footnoteRef = do+ identifier <- betweenWithErrors' "^[" "]" "footnote reference" $ many1 $ escapableNoneOf "[]"+ state <- getState+ let f = footnoteIndices state+ let maybeIndex = M.lookup identifier f+ if isJust maybeIndex+ then fail $ "repeated footnote identifier: " ++ (show $ fromJust maybeIndex)+ else return ()+ let index = M.size f+ let f' = M.insert identifier index f+ putState $ state {footnoteIndices=f'}+ return $ FootnoteRef index++link :: Parser Inline+link = do+ state <- getState+ let currentParserStack = inlineParserStack state+ lookAhead (char '[' <?> "\"[\" (link text)")+ if elem "link" currentParserStack+ then fail "links cannot be nested"+ else return ()+ text <- betweenWithErrors' "[" "]" "link text" $ withModifiedState (many1 inline) $ \s -> s {inlineParserStack=("link" : currentParserStack)}+ href <- betweenWithErrors' "(" ")" "link href" $ many $ escapableNoneOf "()"+ return $ Link {text=text, href=href}++inline :: Parser Inline+inline = choice [nestedBold, bold, italics, code, footnoteRef, link, fmap InlineHtml html, fmap Plaintext plaintext]
+ src/Parsing/State.hs view
@@ -0,0 +1,19 @@+module Parsing.State where++import Text.Parsec+import qualified Data.Map.Strict as M++data ParserState = ParserState {+ prevCharIsNewline :: Bool,+ skipPrefix :: Parser String,+ inlineParserStack :: [String],+ footnoteIndices :: M.Map String Int+}+type Parser = Parsec String ParserState++initialState = ParserState{+ prevCharIsNewline=False,+ skipPrefix=string "",+ inlineParserStack=[],+ footnoteIndices=M.fromList []+}
+ src/Parsing/Text.hs view
@@ -0,0 +1,49 @@+module Parsing.Text (plaintext) where++import Data.Maybe+import Text.Parsec++import Parsing.State+import Parsing.Utils++specials = "*`^<>[]|\\"+firstCharSpecials = " \t#~+\n" ++ specials++-- Parse a single non-special character, allowing for escaping and continuation.+nonSpecial :: String -> Parser Char+nonSpecial blacklist = do+ escape <- suppressErr (optionMaybe $ char '\\')+ if isJust escape+ then do+ optional $ char '\n' -- continuation+ anyChar+ else noneOf blacklist++-- Parse one or more non-special characters, allowing for escaping and incorporating the+-- rules for special characters at the start of a line. Note that this does not consume+-- as many non-special characters as possible, for ease of implementation.+nonSpecials :: Parser String+nonSpecials = do+ state <- getState+ str <- if prevCharIsNewline state+ then do+ s <- skipPrefix state+ if null s+ then do+ c <- nonSpecial firstCharSpecials+ return [c]+ else return s+ else do+ s <- many $ nonSpecial ('\n' : specials)+ c <- suppressErr (optionMaybe $ char '\n')+ if isJust c+ then return (s ++ "\n")+ else if null s+ then fail "" -- Don't succeed without consuming any input.+ else return s+ putState $ state {prevCharIsNewline=(last str == '\n')}+ return str++-- Parse as many non-special characters as possible.+plaintext :: Parser String+plaintext = fmap concat $ many1 nonSpecials
+ src/Parsing/TextUtils.hs view
@@ -0,0 +1,40 @@+module Parsing.TextUtils where++import Data.Maybe+import Text.Parsec++import Parsing.State+import Parsing.Utils++-- Parse a single character and update parsing state accordingly.+char' :: Char -> Parser Char+char' c = if c == '\n'+ then do+ newline <|> (eof >> return '\n')+ modifyState $ \s -> s {prevCharIsNewline=True}+ return '\n'+ else do+ char c+ modifyState $ \s -> s {prevCharIsNewline=False}+ return c++-- Parse a string of characters and update parsing state accordingly.+string' :: String -> Parser String+string' = mapM char'++-- Parse a string of characters not in [blacklist], unless they are escaped,+-- and update state like [string'].+escapableNoneOf :: String -> Parser Char+escapableNoneOf blacklist = do+ escape <- suppressErr (optionMaybe $ char '\\')+ c <- if isJust escape+ then anyChar+ else noneOf blacklist+ modifyState $ \s -> s {prevCharIsNewline=(c == '\n')}+ return c++-- Like [betweenWithErrors], but updates state when parsing the start and end strings.+betweenWithErrors' :: String -> String -> String -> Parser a -> Parser a+betweenWithErrors' open close name = between+ (try (string' open) <?> "\"" ++ open ++ "\" (" ++ name ++ ")")+ (try (string' close) <?> "closing \"" ++ close ++ "\" (" ++ name ++ ")")
+ src/Parsing/Utils.hs view
@@ -0,0 +1,26 @@+module Parsing.Utils where++import Text.Parsec++suppressErr :: Parsec s u a -> Parsec s u a+suppressErr p = p <?> ""++failWithIf :: String -> Bool -> Parsec s u ()+failWithIf msg cond = if cond then fail msg else return ()++failIf :: Bool -> Parsec s u ()+failIf = failWithIf ""++withModifiedState :: Parsec s u a -> (u -> u) -> Parsec s u a+withModifiedState p modifier = do+ state <- getState+ putState $ modifier state+ result <- p+ putState state+ return result++-- Like [between], but with more helpful error messages on failure.+betweenWithErrors :: String -> String -> String -> Parsec String u a -> Parsec String u a+betweenWithErrors open close name = between+ (try (string open) <?> "\"" ++ open ++ "\" (" ++ name ++ ")")+ (try (string close) <?> "closing \"" ++ close ++ "\" (" ++ name ++ ")")
+ src/Rendering/Render.hs view
@@ -0,0 +1,132 @@+{-# Language CPP #-}++module Rendering.Render (ToHtml, toHtml) where++import Data.List+import Data.List.Utils+import System.IO.Unsafe++import AST+#ifdef CABAL+import Paths_Blogdown+#endif+import Rendering.RenderOptions++class ToHtml a where+ toHtml :: RenderOptions -> a -> String++dataFileContents :: FilePath -> String+dataFileContents relPath = unsafePerformIO $ do+#ifdef CABAL+ newPath <- getDataFileName relPath+ readFile newPath+#else+ readFile relPath+#endif++optionalJS :: RenderOptions -> String+optionalJS r = if (inlineJS r)+ then "<script>" ++ dataFileContents "assets/footnotes.js" ++ "</script>\n"+ else ""++optionalCSS :: RenderOptions -> String+optionalCSS r = if (inlineCSS r)+ then "<style>" ++ dataFileContents "assets/footnotes.css" ++ "</style>\n"+ else ""++instance ToHtml AST where+ toHtml r (AST bs Nothing) = (unlines $ map (toHtml r) bs) ++ optionalCSS r ++ optionalJS r+ toHtml r (AST bs (Just f)) = body ++ footnotes ++ "\n" ++ optionalCSS r ++ optionalJS r where+ body = unlines $ map (toHtml r) bs+ footnotes = toHtml r f++withTag :: String -> String -> String+withTag tag content = "<" ++ tag ++ ">" ++ content ++ "</" ++ tag ++ ">"++showAttrs :: [(String, String)] -> String+showAttrs attrs = unwords $ map (\(name, val) -> name ++ "=\"" ++ val ++ "\"") attrs++withTagAttrs :: String -> [(String, String)] -> String -> String+withTagAttrs tag attrs content = "<" ++ tag ++ " " ++ showAttrs attrs ++ ">" ++ content ++ "</" ++ tag ++ ">"++fancyUnlines :: [String] -> String+fancyUnlines = concat . intersperse "\n"++escapeHtml :: String -> String+escapeHtml = concatMap escapeChar where+ escapeChar '<' = "<"+ escapeChar '>' = ">"+ escapeChar c = [c]++instance ToHtml FootnoteDefs where+ toHtml r (FootnoteDefs fs) = withTagAttrs "ol"+ [("start", show $ footnoteIndexFrom r), ("class", "footnotes")]+ $ unlines $ map (toHtml r) $ sortOn index fs++instance ToHtml FootnoteDef where+ toHtml r (FootnoteDef index ls) = withTagAttrs "li" [("id", (footnotePrefix r) ++ "-footnote-" ++ show index)] content' where+ content = fancyUnlines $ map (toHtml r) ls+ content' = if footnoteBacklinks r+ then withTagAttrs "a" [("href", "#a-" ++ (footnotePrefix r) ++ "-footnote-" ++ show index)] "^" ++ "\n" ++ content+ else content++stripEndingNewline :: String -> String+stripEndingNewline s = if last s == '\n'+ then init s+ else s++instance ToHtml Block where+ toHtml _ HardRule = "<hr/>"+ toHtml r (Paragraph ls) = withTag "p" $ stripEndingNewline $ concatMap (toHtml r) ls+ toHtml r (Header level text) = withTag ("h" ++ show level) $ stripEndingNewline $ concatMap (toHtml r) text+ toHtml r (OrderedList ls) = withTag "ol" $ unlines $ map (toHtml r) ls+ toHtml r (UnorderedList ls) = withTag "ul" $ unlines $ map (toHtml r) ls+ toHtml r (BlockQuote ls) = withTag "blockquote" $ stripEndingNewline $ concatMap (toHtml r) ls+ toHtml _ (BlockCode s) = withTag "pre" $ withTag "code" $ escapeHtml s+ toHtml r (BlockHtml h) = toHtml r h+ toHtml r (Table Nothing trs) = withTag "table" $ withTag "tbody" body where+ body = unlines $ map (toHtml r) trs+ toHtml r (Table (Just ths) trs) = withTag "table" $ (withTag "thead" header) ++ (withTag "tbody" body) where+ header = unlines $ map (toHtml r) ths+ body = unlines $ map (toHtml r) trs++instance ToHtml ListItem where+ toHtml r (ListItem _ ls) = withTag "li" $ stripEndingNewline $ concatMap (toHtml r) ls++instance ToHtml TableCell where+ toHtml r (TableHeaderCell tds) = withTag "th" $ concatMap (toHtml r) tds+ toHtml r (TableBodyCell tds) = withTag "td" $ concatMap (toHtml r) tds++instance ToHtml TableRow where+ toHtml r (TableRow tcs) = withTag "tr" $ unlines $ map (toHtml r) tcs++instance ToHtml Inline where+ toHtml r (Italics ls) = withTag "i" $ concatMap (toHtml r) ls+ toHtml r (Bold ls) = withTag "b" $ concatMap (toHtml r) ls+ toHtml _ (Code s) = withTag "code" $ escapeHtml s+ toHtml r (FootnoteRef index) = withTag "sup" $ withTagAttrs "a"+ [("href", "#" ++ (footnotePrefix r) ++ "-footnote-" ++ show index),+ ("id", "a-" ++ (footnotePrefix r) ++ "-footnote-" ++ show index)]+ ("[" ++ show (index + footnoteIndexFrom r) ++ "]")+ toHtml r (Plaintext s) = escapeHtml $ if emDashes r+ then replace "--" "—" s+ else s+ toHtml r (InlineHtml h) = toHtml r h+ toHtml r (Link text href) = withTagAttrs "a" [("href", href)] $ concatMap (toHtml r) text++showAttr :: Attr -> String+showAttr (Attr s t) = s ++ "=\"" ++ t ++ "\""++showInnerTag :: HtmlTag -> String+showInnerTag t = unwords (tagname t : (map showAttr $ attrs t))++showHtmlContent :: Either String Html -> String+showHtmlContent (Left s) = s+showHtmlContent (Right h) = showHtml h++showHtml :: Html -> String+showHtml (PairTag open content) = "<" ++ showInnerTag open ++ ">" ++ concatMap showHtmlContent content ++ "</" ++ tagname open ++ ">"+showHtml (SingleTag tag) = "<" ++ showInnerTag tag ++ "/>"++instance ToHtml Html where+ toHtml _ = showHtml
+ src/Rendering/RenderOptions.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE DeriveGeneric #-}++module Rendering.RenderOptions where++import GHC.Generics (Generic)+import System.Environment++data RenderOptions = RenderOptions {+ footnotePrefix :: String,+ footnoteIndexFrom :: Int,+ footnoteBacklinks :: Bool,+ emDashes :: Bool,+ inlineCSS :: Bool,+ inlineJS :: Bool+} deriving (Generic)++defaultRenderOptions = RenderOptions {+ footnotePrefix = "",+ footnoteIndexFrom = 0,+ footnoteBacklinks = False,+ emDashes = False,+ inlineCSS = False,+ inlineJS = False+}++renderOptions :: [String] -> RenderOptions+renderOptions [] = defaultRenderOptions+renderOptions (key : ls) = case key of+ "--footnote-prefix" -> (renderOptions $ tail ls) {footnotePrefix=head ls}+ "--footnote-index-from" -> (renderOptions $ tail ls) {footnoteIndexFrom=read $ head ls}+ "--footnote-backlinks" -> (renderOptions ls) {footnoteBacklinks=True}+ "--em-dashes" -> (renderOptions ls) {emDashes=True}+ "--inline-css" -> (renderOptions ls) {inlineCSS=True}+ "--inline-js" -> (renderOptions ls) {inlineJS=True}++getRenderOptions :: IO RenderOptions+getRenderOptions = fmap renderOptions getArgs
+ test/Test.hs view
@@ -0,0 +1,327 @@+module Main where++import Data.String.Utils+import Text.Parsec+import System.Exit++import Parsing.Parse+import Parsing.ParseBlock+import Parsing.ParseHtml+import Parsing.ParseInline+import Parsing.State+import Rendering.Render+import Rendering.RenderOptions++printExpectedSuccess :: (ToHtml a) => String -> String -> String -> a -> IO Bool+printExpectedSuccess name input expected parsed = if output == expected+ then do+ putStrLn $ "PASS: " ++ name+ return True+ else do+ putStrLn $ "FAIL: " ++ name+ putStrLn "in:"+ putStrLn input+ putStrLn "out:"+ putStrLn output+ putStrLn "expect:"+ putStrLn expected+ return False+ where+ output = toHtml defaultRenderOptions parsed++expectSuccess :: (ToHtml a) => String -> (Parser a) -> String -> String -> IO Bool+expectSuccess name p input expected = either+ (\err -> do+ putStrLn $ "FAIL: " ++ name+ putStrLn $ show err+ return False)+ (printExpectedSuccess name input expected)+ $ runParser p initialState name input++testItalics = expectSuccess "italics" inline "*abc*" "<i>abc</i>"+testBold = expectSuccess "bold" inline "**abc**" "<b>abc</b>"+testBoldItalics = expectSuccess "bold italics" inline "***abc***" "<b><i>abc</i></b>"+testCode = expectSuccess "code" inline "`abc`" "<code>abc</code>"+testInlineHtml = expectSuccess "inline html" html+ "<abbr title=\"\">SQL</abbr>"+ "<abbr title=\"\">SQL</abbr>"+testMultipleAttrs = expectSuccess "html with multiple attrs" html+ "<abbr title=\"a\" id=\"x\">SQL</abbr>"+ "<abbr title=\"a\" id=\"x\">SQL</abbr>"+testFootnoteRef = expectSuccess "footnote reference" inline+ "^[x]"+ "<sup><a href=\"#-footnote-0\" id=\"a--footnote-0\">[0]</a></sup>"+testLink = expectSuccess "link" inline+ "[Google](https://google.com)"+ "<a href=\"https://google.com\">Google</a>"+testLinkWithContents = expectSuccess "link with styling inside" inline+ "[*Whence* `he` **came**](https://google.com)"+ "<a href=\"https://google.com\"><i>Whence</i> <code>he</code> <b>came</b></a>"+testH1 = expectSuccess "h1" header "# hello" "<h1>hello</h1>"+testH6 = expectSuccess "h6" header "###### hello" "<h6>hello</h6>"+testHardRule = expectSuccess "hard rule" hardRule "---\n" "<hr/>"+testHardRuleLong = expectSuccess "hard rule with extra dashes" hardRule "----\n" "<hr/>"+testParagraph = expectSuccess "paragraph" paragraph+ "This is a paragraph\n\+ \of text.\n"+ "<p>This is a paragraph\n\+ \of text.</p>"+testEscapeCharacters = expectSuccess "paragraph with escaped special characters" paragraph+ "These are special: \\~, \\*, \\[, \\], \\^\n"+ "<p>These are special: ~, *, [, ], ^</p>"+testHashInParagraph = expectSuccess "paragraph containing literal '#'" paragraph+ "This is a paragraph\n\+ \containing '#'\n"+ "<p>This is a paragraph\n\+ \containing '#'</p>"+testOrderedList = expectSuccess "ol" orderedList+ " - point 1\n\+ \ - point 2\n"+ "<ol><li>point 1</li>\n\+ \<li>point 2</li>\n</ol>"+testUnorderedList = expectSuccess "ul" unorderedList+ " * point 1\n\+ \ * point 2\n"+ "<ul><li>point 1</li>\n\+ \<li>point 2</li>\n</ul>"+testBlockQuote = expectSuccess "blockquote" blockQuote+ "> The politician said that\n\+ \> he would fix the economy.\n"+ "<blockquote>The politician said that\n\+ \he would fix the economy.</blockquote>"+testBlockQuotePreFormatted = expectSuccess "blockquote pre-formatted" blockQuote+ "> \"This is it... this is where I belong...\"\n\+ \> I know everyone here... even if I've never met them, never talked to\n\+ \> them, may never hear from them again... I know you all...\n"+ "<blockquote> \"This is it... this is where I belong...\"\n\+ \ I know everyone here... even if I've never met them, never talked to\n\+ \them, may never hear from them again... I know you all...</blockquote>"+testBlockCode = expectSuccess "block code" blockCode+ " var x = 0;\n\+ \ alert(x);\n"+ "<pre><code>var x = 0;\n\+ \alert(x);\n\+ \</code></pre>"+testBlockCodeWhitespace = expectSuccess "block code handles starting whitespace correctly" blockCode+ " def f(x):\n\+ \ return x\n"+ "<pre><code>def f(x):\n\+ \ return x\n\+ \</code></pre>"+testBlockCodeSpecialChars = expectSuccess "any line beginning with four spaces should be a block of code, regardless of the first non-whitespace character" block+ " > print(1)\n\+ \ * 1"+ "<pre><code>> print(1)\n\+ \* 1\n\+ \</code></pre>"+testBlockHtml = expectSuccess "block html" blockHtml+ "<div class=\"class\">\n\+ \ <span></span>\n\+ \</div>"+ "<div class=\"class\">\n\+ \ <span></span>\n\+ \</div>"+testTable = expectSuccess "table" table+ "+---+---+\n\+ \| a | b |\n\+ \| c | d |\n\+ \+---+---+\n"+ "<table><tbody><tr><td> a </td>\n\+ \<td> b </td>\n\+ \</tr>\n\+ \<tr><td> c </td>\n\+ \<td> d </td>\n\+ \</tr>\n\+ \</tbody></table>"+testTableHeader = expectSuccess "table with header" table+ "+---+---+\n\+ \| a | b |\n\+ \+---+---+\n\+ \| c | d |\n\+ \| e | f |\n\+ \+---+---+\n"+ "<table><thead><tr><th> a </th>\n\+ \<th> b </th>\n\+ \</tr>\n\+ \</thead><tbody><tr><td> c </td>\n\+ \<td> d </td>\n\+ \</tr>\n\+ \<tr><td> e </td>\n\+ \<td> f </td>\n\+ \</tr>\n\+ \</tbody></table>"+testTableMinimal = expectSuccess "minimal table" table+ "| a | b |\n\+ \| c | d |\n"+ "<table><tbody><tr><td> a </td>\n\+ \<td> b </td>\n\+ \</tr>\n\+ \<tr><td> c </td>\n\+ \<td> d </td>\n\+ \</tr>\n\+ \</tbody></table>"+testTableHeaderMinimal = expectSuccess "minimal table with header" table+ "| a | b |\n\+ \+\n\+ \| c | d |\n\+ \| e | f |\n"+ "<table><thead><tr><th> a </th>\n\+ \<th> b </th>\n\+ \</tr>\n\+ \</thead><tbody><tr><td> c </td>\n\+ \<td> d </td>\n\+ \</tr>\n\+ \<tr><td> e </td>\n\+ \<td> f </td>\n\+ \</tr>\n\+ \</tbody></table>"+testFootnoteDef = expectSuccess "footnote definition" ast+ "^[x]\n\+ \~[x] This is a single list item\n\+ \of footnote.\n"+ "<p><sup><a href=\"#-footnote-0\" id=\"a--footnote-0\">[0]</a></sup></p>\n\+ \<ol start=\"0\" class=\"footnotes\"><li id=\"-footnote-0\"><p>This is a single list item\n\+ \of footnote.</p></li>\n\+ \</ol>\n"+testFootnoteDefs = expectSuccess "multiple footnote definitions" ast+ "^[0]^[1]^[2]\n\+ \~[0] a\n\+ \~[1] b\n\+ \\n\+ \~[2] c\n"+ "<p><sup><a href=\"#-footnote-0\" id=\"a--footnote-0\">[0]</a></sup><sup><a href=\"#-footnote-1\" id=\"a--footnote-1\">[1]</a></sup><sup><a href=\"#-footnote-2\" id=\"a--footnote-2\">[2]</a></sup></p>\n\+ \<ol start=\"0\" class=\"footnotes\"><li id=\"-footnote-0\"><p>a</p></li>\n\+ \<li id=\"-footnote-1\"><p>b</p></li>\n\+ \<li id=\"-footnote-2\"><p>c</p></li>\n\+ \</ol>\n"+testFootnoteOrdering = expectSuccess "footnotes should be sorted by first reference" ast+ "^[0]^[1]\n\+ \~[1] b\n\+ \~[0] a\n"+ "<p><sup><a href=\"#-footnote-0\" id=\"a--footnote-0\">[0]</a></sup><sup><a href=\"#-footnote-1\" id=\"a--footnote-1\">[1]</a></sup></p>\n\+ \<ol start=\"0\" class=\"footnotes\"><li id=\"-footnote-0\"><p>a</p></li>\n\+ \<li id=\"-footnote-1\"><p>b</p></li>\n\+ \</ol>\n"+testContinuation = expectSuccess "continuation character" paragraph+ "a\\\nb"+ "<p>ab</p>"++expectFailure :: String -> (Parser a) -> String -> String -> IO Bool+expectFailure name p input expectedErr = either+ (\err -> if endswith expectedErr $ show err+ then do+ putStrLn $ "PASS: " ++ name+ return True+ else do+ putStrLn $ "FAIL: " ++ name+ putStrLn "in:"+ putStrLn input+ putStrLn "out:"+ putStrLn $ show err+ putStrLn "expect:"+ putStrLn expectedErr+ return False)+ (\_ -> do+ putStrLn $ "FAIL: " ++ name+ return False)+ $ runParser p initialState name input++testNestedBold = expectFailure "bold tags cannot be nested" inline+ "****abc****"+ "cannot have empty or nested bold nodes"+testMismatchedBoldItalics = expectFailure "bold opening tag closed with italics tag" inline+ "**a*"+ "expecting content in italics node or extra \"*\" to close bold node"+testSwappedItalicsBold = expectFailure "italics and bold closing tags swapped" paragraph+ "*a**b*c**"+ "unexpected \"c\"\n\+ \expecting closing \"**\" (bold)"+testNestedLink = expectFailure "links cannot be nested" inline+ "[[a](https://a.com)](https://b.com)"+ "unexpected \"[\"\n\+ \expecting \"**\" (bold), \"*\" (italics), \"`\" (code), \"^[\" (footnote reference) or \"<\" (html tag)\n\+ \links cannot be nested"+testUnclosedOpeningTag = expectFailure "unclosed opening tag should fail to parse" html+ "<div"+ "unexpected end of input\n\+ \expecting rest of tag name, space or closing \">\" (html tag)"+testUnclosedTag = expectFailure "unclosed tag should fail to parse" html+ "<p>hello"+ "unexpected end of input\n\+ \expecting \"<\" (html tag) or \"</\" (closing html tag)"+testMismatchedTags = expectFailure "mismatched tags should fail to parse" html+ "<a>hello</b>"+ "mismatched tags: 'a' and 'b'"++goldenTest :: FilePath -> FilePath -> String -> IO Bool+goldenTest inFilePath goldenFilePath renderArgs = do+ let r = renderOptions $ words renderArgs+ input <- readFile inFilePath+ golden <- readFile goldenFilePath+ either+ (\_ -> do+ putStrLn $ "FAIL: " ++ goldenFilePath+ return False)+ (\parsed -> do+ let rendered = toHtml r parsed+ if rendered == golden+ then do+ putStrLn $ "PASS: " ++ goldenFilePath+ return True+ else do+ let failPath = goldenFilePath ++ ".fail"+ writeFile failPath rendered+ putStrLn $ "FAIL: " ++ goldenFilePath+ return False)+ $ runParser ast initialState inFilePath input++main :: IO ()+main = do+ results <- sequence [+ testItalics,+ testBold,+ testBoldItalics,+ testCode,+ testInlineHtml,+ testMultipleAttrs,+ testFootnoteRef,+ testLink,+ testLinkWithContents,+ testH1,+ testH6,+ testHardRule,+ testHardRuleLong,+ testParagraph,+ testEscapeCharacters,+ testHashInParagraph,+ testOrderedList,+ testUnorderedList,+ testBlockQuote,+ testBlockQuotePreFormatted,+ testBlockCode,+ testBlockCodeWhitespace,+ testBlockCodeSpecialChars,+ testBlockHtml,+ testTable,+ testTableHeader,+ testTableMinimal,+ testTableHeaderMinimal,+ testFootnoteDef,+ testFootnoteDefs,+ testFootnoteOrdering,+ testContinuation,+ testNestedBold,+ testMismatchedBoldItalics,+ testSwappedItalicsBold,+ testNestedLink,+ testUnclosedOpeningTag,+ testUnclosedTag,+ testMismatchedTags,+ goldenTest "Readme.md" "Readme.html" "--em-dashes --inline-css --inline-js",+ goldenTest "test/goldens/golden1.md" "test/goldens/golden1.html" "--em-dashes",+ goldenTest "test/goldens/golden1.md" "test/goldens/golden1-backlinks.html" "--em-dashes --footnote-backlinks",+ goldenTest "test/goldens/golden2.md" "test/goldens/golden2.html" "--em-dashes",+ goldenTest "test/goldens/golden2.md" "test/goldens/golden2-backlinks.html" "--em-dashes --footnote-backlinks"]+ if and results+ then exitSuccess+ else exitFailure