pax_global_header00006660000000000000000000000064131202125510014502gustar00rootroot0000000000000052 comment=7271f36c510d567f077cb53137aabf127060ef28 patat-0.5.2.2/000077500000000000000000000000001312021255100127575ustar00rootroot00000000000000patat-0.5.2.2/.gitignore000066400000000000000000000000741312021255100147500ustar00rootroot00000000000000*.o *.hi extra/make-man extra/patat.1 .stack-work dist tags patat-0.5.2.2/.travis.yml000066400000000000000000000002301312021255100150630ustar00rootroot00000000000000language: haskell ghc: '7.8' sudo: false cache: directories: - '$HOME/.cabal' - '$HOME/.ghc' install: - cabal install script: - make test patat-0.5.2.2/CHANGELOG.md000066400000000000000000000055531312021255100146000ustar00rootroot00000000000000# Changelog - 0.5.2.2 (2017-06-14) * Add `network-uri` dependency to fix travis build - 0.5.2.1 (2017-06-14) * Bump `optparse-applicative-0.14` dependency - 0.5.2.0 (2017-05-16) * Add navigation using `PageUp` and `PageDown`. * Use `skylighting` instead of deprecated `highlighting-kate` for syntax highlighting. - 0.5.1.2 (2017-04-26) * Make build reproducible even if timezone changes (patch by Félix Sipma) - 0.5.1.1 (2017-04-23) * Include `README` in `Extra-source-files` so it gets displayed on Hackage - 0.5.1.0 (2017-04-23) * Bump `aeson-1.2` dependency * Fix vertical alignment of title slides * Fix wrapping issue with inline code at end of line * Add bash-completion script generation to Makefile - 0.5.0.0 (2017-02-06) * Add a `slideLevel` option & autodetect it. This changes the way `patat` splits slides. For more information, see the `README` or the `man` page. If you just want to get the old behavior back, just add: --- patat: slideLevel: 1 ... To the top of your presentation. * Clear the screen when finished with the presentation. - 0.4.7.1 (2017-01-22) * Bump `directory-1.3` dependency * Bump `time-1.7` dependency - 0.4.7.0 (2017-01-20) * Bump `aeson-1.1` dependency * Parse YAML for settings using `yaml` instead of pandoc * Clarify watch & autoAdvance combination in documentation. - 0.4.6.0 (2016-12-28) * Redraw the screen on unknown commands to prevent accidental typing from showing up. * Make the cursor invisible during the presentation. * Move the footer down one more line to gain some screen real estate. - 0.4.5.0 (2016-12-05) * Render the date in a locale-independent manner (patch by Daniel Shahaf). - 0.4.4.0 (2016-12-03) * Force the use of UTF-8 when generating the man page. - 0.4.3.0 (2016-12-02) * Use `SOURCE_DATE_EPOCH` if it is present instead of getting the date from `git log`. - 0.4.2.0 (2016-12-01) * Fix issues with man page generation on Travis. - 0.4.1.0 (2016-12-01) * Fix compatibility with `pandoc-1.18` and `pandoc-1.19`. * Add a man page. - 0.4.0.0 (2016-11-15) * Add configurable auto advancing. * Support fragmented slides. - 0.3.3.0 (2016-10-31) * Add a `--version` flag. * Add support for `pandoc-1.18` which includes a new `LineBlock` element. - 0.3.2.0 (2016-10-20) * Keep running even if errors are encountered during reload. - 0.3.1.0 (2016-10-18) * Fix compilation with `lts-6.22`. - 0.3.0.0 (2016-10-17) * Add syntax highlighting support. * Fixed slide clipping after reload. - 0.2.0.0 (2016-10-13) * Add theming support. * Fix links display. * Add support for wrapping. * Allow org mode as input format. - 0.1.0.0 (2016-10-02) * Upload first version from hotel wifi in Kalaw. patat-0.5.2.2/LICENSE000066400000000000000000000431731312021255100137740ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. 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 this service 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. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey 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 General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. patat-0.5.2.2/Makefile000066400000000000000000000021461312021255100144220ustar00rootroot00000000000000# The minor version is passed to the build. This is used to do some CPP to # solve incompatibilities. PANDOC_MINOR_VERSION=$(shell ghc-pkg latest pandoc | sed 's/.*-//' | cut -d. -f2) # We use `?=` to set SOURCE_DATE_EPOCH only if it is not present. Unfortunately # we can't use `git --date=unix` since only very recent git versions support # that, so we need to make a round trip through `date`. SOURCE_DATE_EPOCH?=$(shell date '+%s' \ --date="$(shell git log -1 --format=%cd --date=rfc)") # Prettify the date. SOURCE_DATE=$(shell env LC_ALL=C date --utc '+%B %d, %Y' -d "@${SOURCE_DATE_EPOCH}") extra/patat.1: README.md extra/make-man SOURCE_DATE="$(SOURCE_DATE)" ./extra/make-man >$@ extra/make-man: extra/make-man.hs ghc -DPANDOC_MINOR_VERSION=${PANDOC_MINOR_VERSION} -Wall -o $@ $< extra/patat.bash-completion: patat --bash-completion-script patat >$@ completion: extra/patat.bash-completion man: extra/patat.1 # Also check if we can generate the manual. test: man bash test.sh clean: rm -f extra/patat.1 rm -f extra/make-man rm -f extra/patat.bash-completion .PHONY: man completion test clean patat-0.5.2.2/README.md000066400000000000000000000260131312021255100142400ustar00rootroot00000000000000patat ===== [![Build Status](https://img.shields.io/travis/jaspervdj/patat.svg)](https://travis-ci.org/jaspervdj/patat) [![Hackage](https://img.shields.io/hackage/v/patat.svg)](https://hackage.haskell.org/package/patat) [![GitHub tag](https://img.shields.io/github/tag/jaspervdj/patat.svg)]() `patat` (**P**resentations **A**top **T**he **A**NSI **T**erminal) is a small tool that allows you to show presentations using only an ANSI terminal. It does not require `ncurses`. Features: - Leverages the great [Pandoc] library to support many input formats including [Literate Haskell]. - Supports [smart slide splitting](#input-format). - Slides can be split up into [multiple fragments](#fragmented-slides) - There is a [live reload](#running) mode. - [Theming](#theming) support. - [Auto advancing](#auto-advancing) with configurable delay. - Optionally [re-wrapping](#line-wrapping) text to terminal width with proper indentation. - Syntax highlighting for nearly one hundred languages generated from [Kate] syntax files. - Written in [Haskell]. ![screenshot](extra/screenshot.png?raw=true) [Kate]: https://kate-editor.org/ [Haskell]: http://haskell.org/ [Pandoc]: http://pandoc.org/ Table of Contents ----------------- - [Table of Contents](#table-of-contents) - [Installation](#installation) - [Pre-built-packages](#pre-built-packages) - [From source](#from-source) - [Running](#running) - [Options](#options) - [Controls](#controls) - [Input format](#input-format) - [Configuration](#configuration) - [Line wrapping](#line-wrapping) - [Auto advancing](#auto-advancing) - [Advanced slide splitting](#advanced-slide-splitting) - [Fragmented slides](#fragmented-slides) - [Theming](#theming) - [Syntax Highlighting](#syntax-highlighting) - [Trivia](#trivia) Installation ------------ ### Pre-built-packages There is a pre-built package available for Debian: - ### From source Installation from source is very easy. You can build from source using `stack install` or `cabal install`. `patat` is also available from [Hackage]. [Hackage]: https://hackage.haskell.org/package/patat For people unfamiliar with the Haskell ecosystem, this means you can do either of the following: #### Using stack 1. Install [stack] for your platform. 2. Clone this repository. 3. Run `stack setup` (if you're running stack for the first time) and `stack install`. 4. Make sure `$HOME/.local/bin` is in your `$PATH`. [stack]: https://docs.haskellstack.org/en/stable/README/ #### Using cabal 1. Install [cabal] for your platform. 2. Run `cabal install patat`. 3. Make sure `$HOME/.cabal/bin` is in your `$PATH`. [cabal]: https://www.haskell.org/cabal/ Running ------- `patat [*options*] file` Options ------- `-w`, `--watch` : If you provide the `--watch` flag, `patat` will watch the presentation file for changes and reload automatically. This is very useful when you are writing the presentation. `-f`, `--force` : Run the presentation even if the terminal claims it does not support ANSI features. `-d`, `--dump` : Just dump all the slides to stdout. This is useful for debugging. `--version` : Display version information. Controls -------- - **Next slide**: `space`, `enter`, `l`, `→`, `PageDown` - **Previous slide**: `backspace`, `h`, `←`, `PageUp` - **Go forward 10 slides**: `j`, `↓` - **Go backward 10 slides**: `k`, `↑` - **First slide**: `0` - **Last slide**: `G` - **Reload file**: `r` - **Quit**: `q` The `r` key is very useful since it allows you to preview your slides while you are writing them. You can also use this to fix artifacts when the terminal is resized. Input format ------------ The input format can be anything that Pandoc supports. Plain markdown is usually the most simple solution: --- title: This is my presentation author: Jane Doe ... # This is a slide Slide contents. Yay. --- # Important title Things I like: - Markdown - Haskell - Pandoc Horizontal rulers (`---`) are used to split slides. However, if you prefer not use these since they are a bit intrusive in the markdown, you can also start every slide with a header. In that case, the file should not contain a single horizontal ruler. `patat` will pick the most deeply nested header (e.g. `h2`) as the marker for a new slide. Headers _above_ the most deeply nested header (e.g. `h1`) will turn into title slides, which are displayed as as a slide containing only the centered title. This means the following document is equivalent to the one we saw before: --- title: This is my presentation author: Jane Doe ... # This is a slide Slide contents. Yay. # Important title Things I like: - Markdown - Haskell - Pandoc And that following document contains three slides: a title slide, followed by two content slides. --- title: This is my presentation author: Jane Doe ... # Chapter 1 ## This is a slide Slide contents. Yay. ## Another slide Things I like: - Markdown - Haskell - Pandoc For more information, see [Advanced slide splitting](#advanced-slide-splitting). Configuration ------------- `patat` is fairly configurable. The configuration is done using [YAML]. There are two places where you can put your configuration: 1. In the presentation file itself, using the [Pandoc metadata header]. 2. In `$HOME/.patat.yaml` [YAML]: http://yaml.org/ [Pandoc metadata header]: http://pandoc.org/MANUAL.html#extension-yaml_metadata_block For example, we set an option `key` to `val` by using the following file: --- title: Presentation with options author: John Doe patat: key: val ... Hello world. Or we can use a normal presentation and have the following `$HOME/.patat.yaml`: key: val ### Line wrapping Line wrapping can be enabled by setting `wrap: true` in the configuration. This will re-wrap all lines to fit the terminal width better. ### Auto advancing By setting `autoAdvanceDelay` to a number of seconds, `patat` will automatically advance to the next slide. --- title: Auto-advance, yes please author: John Doe patat: autoAdvanceDelay: 2 ... Hello World! --- This slide will be shown two seconds after the presentation starts. Note that changes to `autoAdvanceDelay` are not picked up automatically if you are running `patat --watch`. This requires restarting `patat`. ### Advanced slide splitting You can control the way slide splitting works by setting the `slideLevel` variable. This variable defaults to the least header that occurs before a non-header, but it can also be explicitly defined. For example, in the following document, the `slideLevel` defaults to **2**: # This is a slide ## This is a nested header This is some content With `slideLevel` 2, the `h1` will turn into a "title slide", and the `h2` will be displayed at the top of the second slide. We can customize this by setting `slideLevel` manually: --- patat: slideLevel: 1 ... # This is a slide ## This is a nested header This is some content Now, we will only see one slide, which contains a nested header. ### Fragmented slides By default, slides are always displayed "all at once". If you want to display them fragment by fragment, there are two ways to do that. The most common case is that lists should be displayed incrementally. This can be configured by settings `incrementalLists` to `true` in the metadata block: --- title: Presentation with incremental lists author: John Doe patat: incrementalLists: true ... - This list - is displayed - item by item Setting `incrementalLists` works on _all_ lists in the presentation. To flip the setting for a specific list, wrap it in a block quote. This will make the list incremental if `incrementalLists` is not set, and it will display the list all at once if `incrementalLists` is set to `true`. This example contains a sublist which is also displayed incrementally, and then a sublist which is displayed all at once (by merit of the block quote). --- title: Presentation with incremental lists author: John Doe patat: incrementalLists: true ... - This list - is displayed * item * by item - Or sometimes > * all at > * once Another way to break up slides is to use a pagraph only containing three dots separated by spaces. For example, this slide has two pauses: Legen . . . wait for it . . . Dary! ### Theming Colors and other properties can also be changed using this configuration. For example, we can have: --- author: 'Jasper Van der Jeugt' title: 'This is a test' patat: wrap: true theme: emph: [vividBlue, onVividBlack, bold] imageTarget: [onDullWhite, vividRed] ... # This is a presentation This is _emph_ text. ![Hello](foo.png) The properties that can be given a list of styles are: `blockQuote`, `borders`, `bulletList`, `codeBlock`, `code`, `definitionList`, `definitionTerm`, `emph`, `header`, `imageTarget`, `imageText`, `linkTarget`, `linkText`, `math`, `orderedList`, `quoted`, `strikeout`, `strong`, `tableHeader`, `tableSeparator` The accepted styles are: `bold`, `dullBlack`, `dullBlue`, `dullCyan`, `dullGreen`, `dullMagenta`, `dullRed`, `dullWhite`, `dullYellow`, `onDullBlack`, `onDullBlue`, `onDullCyan`, `onDullGreen`, `onDullMagenta`, `onDullRed`, `onDullWhite`, `onDullYellow`, `onVividBlack`, `onVividBlue`, `onVividCyan`, `onVividGreen`, `onVividMagenta`, `onVividRed`, `onVividWhite`, `onVividYellow`, `underline`, `vividBlack`, `vividBlue`, `vividCyan`, `vividGreen`, `vividMagenta`, `vividRed`, `vividWhite`, `vividYellow` ### Syntax Highlighting As part of theming, syntax highlighting is also configurable. This can be configured like this: --- patat: theme: syntaxHighlighting: decVal: [bold, onDullRed] ... ... `decVal` refers to "decimal values". This is known as a "token type". For a full list of token types, see [this list] -- the names are derived from there in an obvious way. [this list]: https://hackage.haskell.org/package/highlighting-kate-0.6.3/docs/Text-Highlighting-Kate-Types.html#t:TokenType Trivia ------ _"Patat"_ is the Flemish word for a simple potato. Dutch people also use it to refer to French Fries but I don't really do that -- in Belgium we just call fries _"Frieten"_. The idea of `patat` is largely based upon [MDP] which is in turn based upon [VTMC]. I wanted to write a clone using Pandoc because I ran into a markdown parsing bug in MDP which I could not work around. A second reason to do a Pandoc-based tool was that I would be able to use [Literate Haskell] as well. Lastly, I also prefer not to install Node.js on my machine if I can avoid it. [MDP]: https://github.com/visit1985/mdp [VTMC]: https://github.com/jclulow/vtmc [Literate Haskell]: https://wiki.haskell.org/Literate_programming patat-0.5.2.2/Setup.hs000066400000000000000000000000561312021255100144140ustar00rootroot00000000000000import Distribution.Simple main = defaultMain patat-0.5.2.2/extra/000077500000000000000000000000001312021255100141025ustar00rootroot00000000000000patat-0.5.2.2/extra/make-man.hs000066400000000000000000000074571312021255100161410ustar00rootroot00000000000000-- | This script generates a man page for patat. {-# LANGUAGE CPP #-} import Control.Applicative ((<$>)) import Control.Monad (guard) import Data.Char (isSpace, toLower) import Data.List (isPrefixOf) import Data.Maybe (isJust) import qualified GHC.IO.Encoding as Encoding import System.Environment (getEnv) import qualified System.IO as IO import qualified Text.Pandoc as Pandoc import qualified Text.Pandoc.Walk as Pandoc import Prelude getVersion :: IO String getVersion = dropWhile isSpace . drop 1 . dropWhile (/= ':') . head . filter (\l -> "version:" `isPrefixOf` map toLower l) . map (dropWhile isSpace) . lines <$> readFile "patat.cabal" removeLinks :: Pandoc.Pandoc -> Pandoc.Pandoc removeLinks = Pandoc.walk $ \inline -> case inline of Pandoc.Link _ inlines _ -> Pandoc.Emph inlines _ -> inline type Sections = [(Int, String, [Pandoc.Block])] toSections :: Int -> [Pandoc.Block] -> Sections toSections level = go where go [] = [] go (h : xs) = case toSectionHeader h of Nothing -> go xs Just (l, title) -> let (section, cont) = break (isJust . toSectionHeader) xs in (l, title, section) : go cont toSectionHeader :: Pandoc.Block -> Maybe (Int, String) toSectionHeader (Pandoc.Header l _ inlines) = do guard (l <= level) let doc = Pandoc.Pandoc Pandoc.nullMeta [Pandoc.Plain inlines] return (l, Pandoc.writeMarkdown Pandoc.def doc) toSectionHeader _ = Nothing fromSections :: Sections -> [Pandoc.Block] fromSections = concatMap $ \(level, title, blocks) -> Pandoc.Header level ("", [], []) [Pandoc.Str title] : blocks reorganizeSections :: Pandoc.Pandoc -> Pandoc.Pandoc reorganizeSections (Pandoc.Pandoc meta0 blocks0) = let sections0 = toSections 2 blocks0 in Pandoc.Pandoc meta0 $ fromSections $ [ (1, "NAME", nameSection) ] ++ [ (1, "SYNOPSIS", s) | (_, _, s) <- lookupSection "Running" sections0 ] ++ [ (1, "DESCRIPTION", []) ] ++ [ (2, n, s) | (_, n, s) <- lookupSection "Controls" sections0 ] ++ [ (2, n, s) | (_, n, s) <- lookupSection "Input format" sections0 ] ++ [ (2, n, s) | (_, n, s) <- lookupSection "Configuration" sections0 ] ++ [ (1, "OPTIONS", s) | (_, _, s) <- lookupSection "Options" sections0 ] ++ [ (1, "SEE ALSO", seeAlsoSection) ] where nameSection = mkPara "patat - Presentations Atop The ANSI Terminal" seeAlsoSection = mkPara "pandoc(1)" mkPara str = [Pandoc.Para [Pandoc.Str str]] lookupSection name sections = [section | section@(_, n, _) <- sections, name == n] main :: IO () main = do Encoding.setLocaleEncoding Encoding.utf8 Right pandoc0 <- Pandoc.readMarkdown Pandoc.def <$> readFile "README.md" Right template <- Pandoc.getDefaultTemplate Nothing "man" version <- getVersion date <- getEnv "SOURCE_DATE" let writerOptions = Pandoc.def { #if PANDOC_MINOR_VERSION >= 19 Pandoc.writerTemplate = Just template #else Pandoc.writerStandalone = True , Pandoc.writerTemplate = template #endif , Pandoc.writerVariables = [ ("author", "Jasper Van der Jeugt") , ("title", "patat manual") , ("date", date) , ("footer", "patat v" ++ version) , ("section", "1") ] } let pandoc1 = reorganizeSections $ removeLinks pandoc0 putStr $ Pandoc.writeMan writerOptions pandoc1 IO.hPutStrLn IO.stderr "Wrote man page." patat-0.5.2.2/extra/screenshot.png000066400000000000000000001455541312021255100170030ustar00rootroot00000000000000PNG  IHDRbsBITO IDATx^|Ugf{IqBn( <{(((@)& $B=&wٖFvf6Dٹs9sE# @5ñ@  V#Le+z9jw54k<~AN>}@<!@U̚x5JhpK]]wd'[D`$5 v>?%FͻO]9ʡ:vt@2w1*YMr^H2VfAkX6σe 4,@k7Wz=&!*cG{XNOGdQvAŃc?!и? }Y뎄nץ(G*1f31>@3ݒ#w8b4EvS-~WC_2V8wϑM?J_X+ĥNRvWtFݨjʄ[iWWG{#KCg Ɗ03kuy݈y5g7(a&T8{wCh22b`V>Gmn61uS*sP`l/g_G׸y-VLX|Q)8}ˍ5(0kB +Mӧṱxºyq}| nׯ^YJC%gzy>2QzEbעҖGUƻj9JVϙ>@U,iJq>p47wfΒ,5IbNcrMA72ASQd:z'<5%:esd!?Tpm:ҳ\3j8xV̋UU%5RE%oՇEYG[Eݭ{ȒDӎTdsZDﻟ*wuV"]Wr W>GcE*YNR'blᖌj#ªxɂUzgԲȭWubHUvs*|,'k ~sV* v2b*Bq5s{BUžVbC<|Έ9|a^3%g4}_CX7 HYYTjl@&G7yy]ݯ0˝Tjtۑ/dH!1j<|~TM#q(Q83zױ"y+x; _,a^kK֑dQmܐ*3 Ws0\%o5𰜓YGq t;B+PEJ-v+cgtYNO0=Ъ Ċ{ hصQߏy)okZT'6@&601֯.0Xv};۱z9i"hdK@g"Z~vvJ3N@rr:ZU=1 yc"@A6VӰkAR#S&~-wZFj̱fS@M?Lx̊TĦ:P$ Q ?_(5ll[Ԁ8:*G֩CHnr V X=)RDnt񒷶[::uvu+0ѭaKQαjFsD|zeyF܇v:7M QMx-w3+"{X!@>@ 5Tu"^NC`n^ֽ4ǯ9^u 8q:\H,OO<4Y*ϖhR %UOIEu +နrM5-!n dV^z`\a&EZ"hU-Ohv6lx ]sk:wK^᤬(lޕsL q2LZWWx۱ @3@ CN < | !Dg @y@@ :C P 9( @t2@ rP@<   ( :C d@<x@ P@tIuv@mP@ z(( :q @ S 08y|ˡlmjOݕI┱^/m,{*A7 d|C%:JcV'R)gm#@ - N).ñP3,,Htu$&\QN_pAvˁ@sPrPqTsX*U}@qCrUݦF! OĬT0yς@30n_[@?݇Tnm*4m`p6U^v'\ :Qy[<@/n_%VXs񋯗5fO-ݛ$1zrДHSU5ag{H ښ5WvULaQsV81jְװo"@KxSVҵNRcSז**Q5%%{EP"n(6^݋LN=фq;-x\]X/0/XҐ",ר̷d^sd=+?Pu=Y%ȹ&J5#U_^u"P]R+P19:`| G0 If:65:PT7դg6 >3M5 S*ɁjKn5LKw>&"6)W'oT# t1 M*Q-ELtQ\L5j =z;MJGXvjVqM~ҙ&B[n p( G e5(9w@CKr qkH6i&ܮTqħc[6۱J @0(C : 庠l1dj۱v~%$"k*4} pt5#Zey,J-[у(u8>/.Ӈ晼U*tN^ Pl'^ozBQ3(RMg @03Æ\vX[7ݓ%{@qދV}Bilо살)!$L"vB*v,9С|O'c":u܍JA0C)ގU^ ݇;V9|SxGkդ)_ͩϭtT9xi/ܰb{vhv #J,FZCE!uGn5d7UY&4gS]PpUl({zOÆr0ʼRr`ͲD+(TYx3G "S Luɡ -UߺF{=SK' ÙųfCvCZ\C :aچJSKTu -ctuU5_̽`@N^!K;hޔu{c{DLė(uRx]Vbà'w.kv-M[SKrp/i~hۘyIQ]^Y%֕+8ae1Q7g0]#NXG4ma@ @OYb tYU/ D}G|Olh{BVraՃԞiz.:,6pS[ڐX2S~0#A|HΆK08_4?/G)^[: d@؇KQ@p}g?˼Aq47"?u~@fAw e& |;> Qzzzuw- = ?8 @gS=;x @G#=>8@ @@ z*( zjρ@  (  |0 @ @@ z*( zjρ@  (  |0 @ @@ z*( zjρ@  (  |0 @ @@ z*( zjρ@  (  |0 @ @`>@` -@m}䘲Qf ۝@)-(Gf:&s'px BoCtt%iڽ;c4N_/.ۥbNyEݻCGyYPXywSv ol2vtF; !zR7YREI h=Y{EJ#h8؝50A^驢; w/KߓkzN%tP'\.q|1IU qk ل.7JȾ.&}laPO\gl3cn/>QYu*,ht:^'t1@U(O ؖe\ȈٍYdp1鏾c=l#/_ve]*?iG6ö}|L{L-9.[朢'x[?G?OmѲG%N)Pf8eV|q7}/h"6kϪ+Q^3 *roٱP#]u<}#tc#_!x5Yu1'=[K8xd-'ƇČW_kx_?Uw 5e Jʶ(;F>w,ؼbnGٿII?_X|he{k`%*6xpX^xkۆgW'GPg8<$,ͼZu3M//t%.5]afœUe55::k[eLCɊ0S =gl"[qC fYuqӷɛ֝ZKB˚/S) }u"Q]޶O(U/X8.1,'uj1 Y8Gyf^+2Y@苯-[_Lٰ|L03IW *mXe7|vƄ ꅷ '}M~+f +ђvڜ`KQtH""rڒ]7,ߕVQY&|8l["?g٪t`6)dI"w'+Jl8<~\|D+ìqO o~Ϯa.Zb8L,7h/rͤNxvew?W5uxg-Aڧ*&͘pn9%-^*9;i~~+MKhoVP~|"2qWz[k=֌@h6O:Ҁ74k v DK ;~Ѐң6+ ]oH=?%sin|Ehqap%(^Vzw6qu SbU\]!peht*ǼQ\ݒSQfq@#[\uQXNGxWnnGxڪBbe+ %Wإǣ~e:""¸nY]XX\dS =ķehf'fŻ=&6l:}SX>F{ǵ7JET,Eu~rrrl^ xQ#}YSv-kmYyGҶ|uk<'iG`+׶zz_Ϩl+ģW9Yj[y~֕v( ۾~__\OK|U_$ v' Cþʤ""~ ʰX}𾃹%MүWwTi+PȒE%^Iv Ҵsу4 lO]۹D3`yrS";.ܠӿ-5/5vh0Ik wF[Rd~mɎ=WOJfգ1On._.,|ȔȤ37# xSnFv1 ..rr?1~̂1s u(; z4y)k,.hD:u E</%b׃Vm*ݴ^[h98GԀ([v(62YvD (v4 |iՃ8FNܹ>"AHMCx;K~J%FGt\QwR8PV]-2\ppSm[1 nEQqo(#BC%0H,RQqZnAZnaF!v&)i 'P3 _Znm7U{UNꮯwԼ/?ؿl/sӊB T3,fX=dz$h8SZOOg:EGmjXNJ#NuQfV1^[dsKH%5%yFO4UyU:'/7ҠHT"7Bfez2"7=՞zA3C[RR!1V:: )Nu~5ӂ8.0O]V5]&qxD1sr1Ko_UISBQ9jմ*%rdp@y*=ܻ)=dSU> HI^QmVcj-pR-U s;CFX,ڛMZDTAT˱+ MwCA؄$3a\C^E2'uWO=ҒV^єB*; mqRX3Uܠ_ eV6jS<|燆9s|V[…ݕMh쾯h)}kSw93qi%Ƽ?noo 1{Fɋ# IbwOоQxP8_5{A#4)?2_؃Uk:WP$=G @o'TiW-sצO. 21eM5-qS:uFqgrbCW}K_)l3bYU]9RO;! O ,Gܟ4ɬ$"xDLSMrMMrDq*W/9W up۲+hs+Ҳgsk?PUr9əB͘^QIHr1'"\W ȼ:C@qGijdzCWyǭ\yG>uGW_Q]HUzOmܮ:-#O?|%cgE|jvҜ<靿 V{< SNnB#Ey?+ unrK%ڿ+%uDԆs甞p{V#xKEȒщ)sdžHQhٽ_~?ʸ1c8쿔o\BrB#9EY5Ƽ)˙ tflAL$]dXC\:z̀SG#zLvI=66X?zyDA W 9+!٤Hrɽ"o%_̥s]>neq^a)'=[1a˅E /G]P"X>!1trI ,.<؋fHhK(r9#^}ݹ~.48+AZ=iV-d7]޾nd֋$7%P˒%lh[!^것[TA m1),E{EJ޿xщ_wS/?*8sqi})°G絻O[2A^KZN[k)4DWw#X,ב[E9~,n]Hx(JPɅWc~n{_cOcϨp\A] PǨA> qs{ئl0{vrp]ki{ O`f@ @>w!\Po"0QX2o wD("=D` & hx0h@{<#@ =P@G @@XM @ @ `5( F@ @  P@@ Vjd @@9@XM @ @ `5( F@ @  P@@ Vjd @03@؝7e > >kVo/vwm`1^y&:f=HEu:QbjpE{) mȭO7x ӛ?8ݡrmOkvv.x#yyn_S r[U M1 gwιi* tu9}/|⣅ wsS R]C@'ϒ "k<@ pBbwo:#ѝMކPZцE#Y{V-8XA8MGsĨ L;_"$E/0\GLtdd &t|m!=j?$fDX×;f^Js[&iԬ>^n؋}qѹ?=Ud:II#v1'=[K,'@qJb1q8U*I>Ar/RK\{iRLҖ^eΖm[,gkB uUݽ/v$/9Ia6e"b&vjV3_UYdPc,}[fafiegK^:;rlPs `]OM bc$8ޙnϏvHҳk}YIMzL$s7lky 6 $5Wg)eY9Vu@ fYuqӷɛ֝ZfYEVE I*^B9҃I{~㍝Pr&1M=)W?=h-רZ"I4,H-bSdN*.kBLΨ0Q'$O*똡#y;\L-gIr*ȏT6f0H8TDS3 8'=Ps+ϺR&Ȇso'ŵ1K|8dfb/DٻW/RkB9B.2|-T͙ xDr%C[(dɝJohv-wڕmؤ.Ev.q>G]W8S)zZ٤QN$e8Bomٛ s?V=<MݑiJ4X&#sD-q^L=}?5[ΚdqPUH**)hHC64rQ[3^}liZsGG3HKt"b0Lsʘ~W vmXZT,Dd&:]Ŕ;S}n,Z~[$mY)+jTvݖ,[-5yeJnPE1'^Xb~ WJ5!##=]?B:iaqNvWX]ub+o4̞w)xDMVh~a)mWT49~}F YJg5w$1gIku۪mM(;riIZ>"?b9xU֔^a.~aE9~t¬tVk|lw~ y] IxzL`/?b /,U z,qDhNk7rYx~n^ ?˗{worB\s)Nm8wN .{m=K>"?ӲKEȒщ)sdžHD6N԰Q̼NVbѓ{n=/Hl9$8r1O-;GXݖa$[{\%˙C.m gp ˟tP-"vrD eRsUǓs,!k9s6ƃy3,^<4Ri<{k?"oZF]k|_+0\Zx௃=e+UvWiW&2=|{I5XiwM2}c` /OCt{=KA]:قjFT cL^M|I.|g겙w&MOZ*( yM(r9#^}ݹ~}7|Ƶ4OM<ÇzsAʒkW矽%=zFmۡFR&dQd|~gƿ`Hmэ2^~K0Ee?B*ЪG+|2J>#߈꿁f9U$-zqg9ײnGz^]; ˙CΔir }q/u-~mYyt2" g%^nḬVֱEa1}Ьxɯu>5\~9s=* 􉧎ΛU6?/T8t|"+By?j[{MʼnԨi祱N_p7 -+7X㱅ͻOҼ,[(uҼUpC2XݛHtgabΊ3Dž6|X(]`B/y7EPj+~RT\):=; vs/M @DoTQS;ps `חIJ558Ѡ>^n؋}M0aWݕO<|a}IÏp/wܽS-Q_X:!G[yz)ijgmޘ$wm?ߡ 2YC1konݾ;)`<UZ#(Ӫhsg>UU\߲cߡҖp #^:22̅P=h'k?Ee5g$Wo,h"Oڬ=0@#Ʈxb̨@GkBg_sBOΟ47nxdo5x=1q/?973qQn\V۟kڒOz U7/VMⓗ _1`0ikkI~¨;K S =O(U/X8.;f~̵KG]x̊Or1 {u5d%?$7qﯘ=EGX`}nmݹ"F|dǮ+=Òr$iuel\.9ۆe]zgj+5Wg)eY9V.^`P?xx?"Yc=鹏YyRSn`>󜀪S/Hܼ;̙nt|Ь]˺J]2vHubr@h;ڸ>HD78 +0, (=:oShyoSǩ#-kL!hKX;i[i7[{NprOK}iXOgʃ8嫳Xc >ΐC|+O/PAo6y~bVcatN xc$F&pT Tߎ7]ZdÀƄ%%i}3;:<޽j"$ֻVHd.q&i✬ @έu-nP$|ʨ-2۳ldžGE ixm/T$;R%Nxj_ǮbӁJEAֈ*0˗}+޺eyR{wFܣ@Rw 3ioO[Ww|= ؁N ":9MVCg>8uқ(KlQcOb4&j&c,1j,h{^kG*p-SYnV(^M}-o 7R >I 1\E}]P!w"AA4) _,fyAL<ۨPMmdG{B9p^JS2;8 #SS~E_tmC5'Y^rzpk.`c ) K{ cƍ:Nz)T@LW^_#tRŪS@uYò/4sް@@đ&v G.-̎J.;GA G'ɠu?$?PR8tWڅnC$ KKZaFFMeΉN)YH͌KvgiY]/*?:! ݗ뭝߻ϳgPY,IKK0 V51ݬ=MGYQY>[#,@\TǥEh3WX>qq9q}G7D-h+1"16eӽ&;+T^DѵiQkI K.8#;!/pA΅g1}$+3)KG{Wڮ0/a?@nȼg؜~~IOt&N耰xZϓ};O380a{! %.7qBTC>2}=oŹ@1;k3E"#f̨}<^g~ pqq㸎fR7Gܩ};Mt-lyG~%oKz΢YoYvO8bHo`82Ky-t@(h""a,}%уaJ.}Nyo?i'틮7= PB uޭZSE8o7Ga`}cF^x-5UCȰL>nW, !/A!^tl3 {]9~JXP@3htKPqZ LV`$?:(,kgL.=-i1Oϭ-` K2Njzz}1^s;w|N#a*lNu;oɎ3}IN8v.߫ =7!S'И~o_M0©T]fKTIS^\PWK2܎/ߙM~d"z,8R+F.ݣ 3?9ɓd[?zP?C:4k<;#ީKUT_-/K*Pϳ$Kĭl>/e=}ǘʯQC d2pd~d~Û܈Ry4.RJĵ2c>6l~ROUt@7=o{L ϊk߿Wt}?}6K,ع䗵nޱۗrW49+O ?+ߊKfzخ=Xwԭ ւhR@$Jd@o]0&:4wLK8D^Gl7~66x/'rˬýՋf6O#K+F+H^Rldu#ޯ1Rr';N}wA7Fࣗ)M H{7]~NxjFIyrzw )1̥wI>Hօ. IDAT9[YL/{.Y|Wa02:Ğo8ve#*J u>̊ց@ 1#Lk]qsZ#Tuko( ̢`x[pI8k?ae_^zYݙrC%'4v, K; 0 8xr܍>t5cƝɒȰTf7!a"VmtmcI 4cdOUTtݔk/R9\x)WY}eVRPIe~jELQR_9y%>Y'?!WrJwђU+ e ksgu) rJ0}=ޮLYR29:z::dvllS3R(oTPS׭LWf EO&b̠RCO--|c1 {=6c޹#=Ϯj )y'^5K}TQ= YYr_w{a Նܔr⒊,eW__-eDaQ6ԭLv`¬ʎ:Lٹq08v= Lj'AN Ifc/goPx’B˕[]a[>V|t޷rr\KD$NFx>*%E|BzLtbrYX)_P JRcVATH3H:K̹S 83'1jix}L,S$QMCMq?0fjw';C,S{[yVDգtn $^ӓ[W SQwUjTc^5~Fb՜´$RVUle?n0w:s|/ B#_\+d{f8u-0'#sofH}L:Ou_Tǟ((*t 1,EiF7 ~=oS~](2ܲODL?wLԽOKgeNx3kn#v|P\'me G׀FՎQ|a=@C,9wk%nhDFxcO ]N_wAڛ^%Ԝ%&4Z.r RL}b>f1O$x|Ht~V@X39Ϧ6Nţ=H8/O 5;8 #SS~Et5)$㒫&Wg[e8zZ˧-z@(\Z@$d,9|΢^lj=uBO_q3ګj/NI Jk:SݥAɁz,aٗT=Za+M T2i#?aR!oA[gX,8ip0o[S)s pGE%a$|@Z@ejaBdԳb(xKVuWl z(t# ,%:40J`=F36S{ĝzdnCEU\{dyıw ~Qy=ҷpp6m if߮;]I Ψ< EI Mt?դ琏L_[q.Pŝ K<<&56Ԇϯ6N"?_U\Z)+޷%"cs%=Mq8qV'=_wfqn#F-a²$0($Z3wP±D:SP5H\Itw߮ ]t:1f0 xrg|?X8d踸q\Y3}]p,ZU V}2b{7]@ӵ>~IJ%cZ'{m١(}n ^f/8Z@Ō{-/.+z1YG-"|K6J@u 0Y(U,%|Œ\(5 ̅ɗU5 ?lu%ݲժG:k 72!xuAC*Wi;/}2x^_*N|)wUO(R)mEWa}%YN~ʊw}ɴc{3fҴӷ8NӆY^]^rhW}wR Ȭ'V\8 ¸g/]i3Ic4 yd[~ΔGwmƄa/M+oN=}[~=zZ/^r+OԝQg/cozJY~a,t_ f]}URS5rҧ >MRgRX#kQ!f5͑.fXxtn7^/h5U$/2,~= H-P$ʆMTzMб=C04wW_o`N=fi>YR@ hDLH H~tPX:׶Ϙ>]zZb[yMXFΊ,M{.u`oh4 aoStm=շ5-ůw?l|V6̶ Xf\41Ɂ@S!<@@@dz-`  P v P v P v P v P v P v P v P vj,{ //.JB A@B*  B3*hZݼP9ЌqTA@@@ ʁf Ќ+   Z-V7/T@@4#f\!Uj yr    4 V @ @WH@@Zn^hF͸B   @huB@@@@3@hRB*  B3*hZݼP9Ќ@ pn6 @ !>_wX+ج>\5@S@V 7֘yjAS cl%Fg^ܞz@wYhOZB ˗:)2k7dָ2h̖@@_E0ݔJm;a dm/7zAѿO'=V[@@@KU%7x5NY.('LW/V%`\z2[gG+fO39mP{㒤G_c/sF^BCw-Vfk'çv`'<\ȸ5|8yI.]Xy1:(KFRQ~e7E֏8V2+p$phٱow={0D 9nֲX6"{l9uܽZ[VhfF=wg-ݶY(?np8'u'߸s_A9dLfgvQ~ݳ16_9WhrX⳹[v*> pQcQG_}0~ֱ~}JidΈkiҥOO[|YjkOc/JzbpI7z]8]V옴b{92c c=Wn~I*+c_׫g lFU~aͰ&9 {Of{uWJq@@@AeG32^ D#/8&, Oy2%€ޢ}gu7)=kt32sCEQ ^V+@FxS_zNbWm3F_?/7߈$0b }LU3ҳ2uC8)⚗l׎>-/DSOȬȊń'1H5.%e0Eά ;ݘVFQ'8i\8}~ŰdTa fh >1tlLD&p cH ˜We‚b[qi8'ǗIaJx,JM)KnюS1ّ*}FgуY9btZcuWˬxwܤ5ZvR'SQ;{sUj$MXr@Er0ê6XYo qӇ4U{Cqa-T? htU7P/V~QńB-d #˃DON|uUȎ1tXTPKLX?E,N]Wo jt$îwj ~_کUeV\o䲱R>RI^L+v&^6*X}δ1?5BN@@@>zƚN?os? Dπh`4;)8ɑ~y12 UHB^AOvyKg.n/8,yv|S v^ST!Ee֠ƎS&n^ƻ$UI U  M#Ь5l5D4D!&dS!KJ1KٯKfVuOeRlF%])@2_⫛t{aޣ uw1K>{˩+J<Ȓ԰)?p~{q eV\N_\.W!QN:0#^ V=m>C"_7]Lyւ@Kh9ԙښ|gqƉ(c"16eݾ ;JYS Dұx]$S1sK3ݲhv Cy:Nӥ{8]Z*Jk1`wt ~zԫtRXSJd)cI1 +WqDNX"d'2ҖK~nٽy\^7AiɛK~ zP@Z_)8kҔ]vՈFw4d F[goN HUi.dGRhxGc.X~'R2Llct[=Xv.ކDQZB`W|'5m@ K,yaӇ40413}p,‡ןyˡ6G6?W[r~g;_y~؀^wEߞƇ?0n7Sxo.OY3kP6hH~wkWKX$1-mh)lʤ(X  -O%"xџn ł{nkΏݰQ?+yCˢ:owi~5 g֓O8}̹ c|#e"r!V_Vy"!gƬY:DkԨS|wVVLxm6x1_^&T ӫ#K h-e`4Y)6F:߿u^cMVLH}1 IDATb) ^Zsk= m(7C;!hLjރH,j@b -\@rGL Cb$eվhCŎg3}r 1NP=*+}%(nݫZ@󻺹sn ^:^ȎOKzʌ_8ذI\<_mp~FdYvUh.[5V . w\-~W ;Y]ū{t4Na}%\[|o<ȿA3xqj˚Os/k#3ܿf܀~9ua\tڶ%f$X~aį~m:m\ȡ,=c!O7)0j /n٘G)6}mxxg tYô<]:c2Nեann_q2MdvNzL N`9Wv( ;)/ ;hU5\f2DXmn=e98XLWIN7ʣҼL,5;^ʼn~y6{<;uӣ$wkցܼl9RnF?y׿OD'<X[e& n{9wf_w㍑;vV&XfdfPZ_I%iz rٱ***gztFg8D2<Ǫz].:LܻoQq2c' 2-p]rEIH?ILlt˻R #Տg]0YŒ:$Wh;*\=&Xy6ޚRH~=lZCz<ޑHȳ'B/KTµkQr`9JK/-MRZgaL,T.{[T_V elDwQ,lUȕ%*YOJ$# 8(͖FvDaQ6UJJz~dcz\6V\RPo[Mi(/&"KZf~aVieg=cP]\qnm'~RحUi'9ېL^Jny#s5LMϯy9ArêxzQtQ"R-<%KҳSOl^?t\QM9YPp-U $(ɣ7ueVm!OuϚʊ|.~wi_X  Z@R"a:#Dұh{;8q}z7\2׸T-XQ6PI5 L=՛׭N/w+oME\6kڔc" xEz#1:7h / XI_?a 7d gTmx-5GBQWV _$}i84pu)8&9>U;zi'xqYCrہ>@4*n0y:g>xB疥}dnA1^2\x6(%+eWp{3-S?}./{qwF~,ٯ_fуk@"q]|Ded+cof b}d^d ".טFT:wgdv]ޤ @ UrHĞ:珯&&)81_!b26/95^b5tWW{}A^trNbF^VfAb,rݭ|z9]ƒNk v0=zqrnzFjl %\}߮ wyXǗIJjeܐ[QIG0;rn+@PT'6c4GU-G3WX>s؊jNFqSU4T_ 7l_|tCĩ߂ΚQ"` cSX6+&m]p㞻->n}8Q~W@/2.Zfe6Ut 'L 0QH5 T O3݆G_}S>fq.fQOTO J$b0Zc#$ T08f7ggD7l8ŗ& :$x9԰X2/~ pqq㸎fXtU!h@4@qZ dU0Y4W?թ};Mt-lyGJ>:EƳ?vq;mD'#b[QL! ʿ׀`i.{yӁ˶3=嗚F`ǹX<{$n}Ӭh+PUU{ԶЋwdeOgKu: rh@A;Wnn Uc;N,DIvɪ)9}[~=zZ/^r+OTwҫ_,+V/^"䅀y? vO3i(|(j1VI7|=l QŐnTgՠnX}PN}~~ʡ^|ۢ:\Uc" ^wR}i4zHr`7zTQIe)6G@LwUaBjeFV&n 7S4q@@F j4֚8n2߯5z4aeFa HCSmm@d;Wذ1Zp|e ^*G&hή@7}෉W~o#gk^)*9EB@ y++'y!A#}ձ˄0~ʢ"kڀby޾+'wBcM ;_zûWVWZni2tۺORFC2+x!YVu*& Т h!yiw <{|[WN$?MxvEܿg5FfZ7Ք-8HP6mP+ s,vRW뱣Q~ lX G0ZO[.)2032sdޏW{[]6z9/[ ab@Ըmyh8Mg]5^Xs?,tmN !3nm- (! Dr6tܠG2z߿\$avg\P@ =50]ߝ,muOwNn_,yۃqww 소eY~1w _0krhFnFz,  Ϩa H!i{čuC61tE#F"YJdTVGK_eۢ1ˤl La=r#<@h) ~ ='kƭ9}WOmkᦁj 8~I~e?R/nO=r+ơ#MyŠ͹1Gi[fnmk|-ٲMŭ'YT&lNLE15,<5KZ%s^r ?'jHfY@M#mt(oqI4' D#J(_pzR8{ y,.O*17D/?͋qǽ Q0o0QxAJz*"҃n>Տfi^ӗ/ؚS aC?,PO@Lm[3; \풕?Y0gIZobL?v6?߯O嗲8iƝS bfgvQ~ݳ16_9WI7z]8]V옴b{!I7C3ǝ=;V'2khˏ{-n=cH:@ TZBHV?Z;6EZ"z\&Z .H:$oJy FR":I{c4m18`Om$E8İdCnPb 0IW$} Mg s'}$SCX=+^VYF{o{rL * Cdջ<{~ 6OK|d`ٱ**ʪ|Y@`meBk߽E;L9ń'1HLܿuԕ.kxA$ 2 22#d=R8.ÐA:m$+ PUtҋ(C8'FFpLT4h5&JwT4B\<$.#ZoI U]&R%_RH3Eb9*UCH!Bܥ8"ϊ #q=)ڃuH;sZNybZE+(m_ib8L(8=%GZq1AjrxzQObd'al&HQzx29(_"MD˙Yc^IOe‚b̀0tCd'l IR&M=/X.D:_⌲BصjÊ-#n@14L<Ĉ,ˋhA6,TK5{Sҏ"=#sj/ CjU Ž2pznYDe?o#wa~Zb/@T˞t0+~IYr8ƫLd>0k #?4XV0QX    Hdԩ#N.ymX~-_ B\F F􂲜ϻbPQZm,.F a #*|aQ&WF*F?YTo oRR} KxU_Z%}DPV,zUԶq'?dqjąKwn2Eao97^dnA1^a '\U^)JcMbHd]H%I`T'FWiwjhIvTQBbI 3>(q_)NH5y5O% TA^Mq.']\pDlB™-TSגQ=Q.IA환')nSwoEKPŒ0q'1 FU@*(ypjlDlR\׋Xv@R!ɟL2w +Vm#l T n7ؠtywU(uTk_8e@Fj0Vu7Cҏ)hWw,#D&v֜ZAnD/K"b.ã<_u-FP"7ٝD{R,FfFd'gGKg gp㞻->n}U?T6x! xGw/FΓkՈFw4d F[goN HnP#?`oI\x%QFjⴀ ÿlL=Gx^L$BD3|4"EFR aO#FHyע'8+Y\ALA!qɍ =F HAO-P&;Ѣ$ߪ}+?5BD"?~NK"ȧ4/PVY b8{!<9D'c[Q հeaBrnyat=+ @5 p@qI'gGb?|[@@e @Ѳڣ!ᒂ=3$piH`_%7780!GEĄLQw#t+N]~2)$z$']E/R;1}'4 qWzE=,_Aȝ\>5 UցhQ֜Nͮ0\x9FK[YbY`]T%W} $=%(O9Fy8˷,wA#m_Syv=lC<ۑX,NCK%ozE"K,1FLő+NO V\LNf>   P_<ҍ{ŌJDTjo .)dBTBvѰAFɷ,eQa4!y̞%R%BޓiD/Ex[#ba8"Ij%2]5؅,Pi@  {@l^备/I} UNDMy_$C$V!0amH!^SqFUEEeiF\HUl  JB$K cԹW;cHA^2fILVhŘȅSmA@@y!O!e$?h/Tom@/Hh4}Na¨9d0ƯYEuwK,:<)]~r@:Hv|>eX`WCXd2jn)0,8۰*ѠpH'vMm1ywYAIFVN㞻->n}e<,PQz dBcۦZ'~]N尌SG)[9!7 'uLY%.ER&;]*ha923irJ IDATňy+A"̋Ґ횽$tknzÎbA?p*َC 7 4J  03+fAg2ODŽ_s; LVhEb!LiEE )";)6; {ӿ=$)`$f¨ BƤ\tt@Y@@@@@ @@4C[`C@@@@5 (HLZLZS3BYA@@i.EQFJπM{ @n дpFzCn -z |xMU:r[NR/qV|u?/$Oe   -Rn3~ƣ{J5Z|A@@Bx<>0z$>$jZPn~P`iaH:-vYjz/Uz4j_Q2[x"snr3fiwO4N:/lKِ`䐷s&8tqé@$}-.YopݩTFeo?nudZvVٔ_:Ma(o1;_JX" ~>;bOE5*+l̢-0@\\߫[.E|&uB>W|9LcZL~1.{뗯t%t%;#BW;Ee|4GQA 7 7%%Q'%Ŗ,Evlc'ݺ̦ILwmfg:kͶv6ٸ&YǒX%[HI&A$AA7pw %p$}?~w<"Kih@Q3 o\yl_f8ܐiK޻+07Yyy19_[ TMrmީ} oO-E$?[=}곦YJ3l[Z@yU>d??n;Jܫ|[wA9dO|K.|RP( Kxp۹da ӫ"OoԽ?=Qul 5k_j(B8n@   #>gXe/x0̂ge_`_8zI^Yx$sx#+'Z G(`)(<`P] Њp$si'%_<Ђ$6(T"glգBxGg^FN;1Nzͽ2Si<_oAܯj| Ģ{vN9 g4޲"H;<6 l[sϴ%xEP(&Nqzura7#W=l͍deM6tb952(K qq@@8 +͊Z옾$wX哯7qXaãN5>3Bro2NA?0xޅEP$,U.}րE?ڦUhl#XbFwM;%5`Flf)2F4w=d~qo}ZsrƵܵ W*x@ؽo SKa-ސR˟$z0l2vj cm 20AtY0Ls/H#Wxy|O~WԬaoNS(glYZޘ-L>o(Y?)j $p}ӗЂqX<a樻hˋ lDY]FFF`"cbc^S玘K׮JqmVrʭ_o]/QˠhhhExY+߽Sqqޗ̊%͏Jgd_8Β6{ٿEF 'Q"LA05@ϟǛ;KVnAۇ~0s<Px5e~(au#?`kJ{\0G~XC04@<4w)#y^=sj}Q,(|cDmq.VFFF`#wv444444#a-暜. F*Gŗ|t>G@,5ƁG͖F߁@=Wk.;'ѾGŷ44o6!Mxtd.4UE 9{ ?urxR{z/P,>k% S{/aS_1O 0Q bS)YXE74N<;pTh}'KH^%ꪯ ˍ#uvt)S'zihlI˜%n=EޙpL )K wbg/;ㇰ]WaL7H C_ԇb>F z渉 <҅m:CN4vxiQif4}|f3u%q*1h$e vgb4βvJKu5olSwF:>:lޮ3&ۥL2R@~DĔ5}\.i&l!d(ŋ t` &>6_w<oHɕYa1lS2(sz#2v(+U! J{^Rŧcݾ 'Q{v_yIRz|v@:q*1e;3JVGƻc*]0ʛ Ӓ8RlJ9* 5]0ŐRn(#7Z-AۜY lcwiy4.jdGn-x&e,k.Y@YwЇ(/)Lʱ#֚jc^;7ɠ.pV$3#1-gj| (0Rh?4PsoNƄG޺xV1mP(v!ՠ VQq b0@Q Da)xh$ Y+-yj AxR;Uj:W}łJVwMw# 9ҏ?TVΟglp~tJ MtCwœGZ>{ 󍞌vpm0L\1:M=SoŦTm:%B'_ #^9}DÕ QT5LQ;"d%>샫Q~ MQq(qyۖB_'tZܛ@e< ±CK 2bD8Iތ' Jqج';FDrnF[.X,KX%+?c57B2bU2paaX;!yz BYKݽ]O \60'N/q*5ݤ(LwK ١i ]o'huyMHqJn&8=rjl=<Ϲ ) :^{X-jjYФG*of  FׯLk^-e5'l~%6ׂ+6`.l!Frp%Givi-mBt'SZ:hV/2lj6+?Vަ;fCdx¶ӍՉLmT?QjI[?bgeNՖ/r+~=3rcwOI܎0{$}\fFkv-r#D&&@LU`F$D ~)BA` n*XyS,2z&IKڛ0&1V4 G0cX@ZV!>U@..t<(PƮ3QW^نFK1*g<PHxaG(7qd팇UV*"$q*@G_/>qd Q[oщN#.=4pHʋ7&1s#Q'z:DH) qD2GEEYsM>"_2^?p|?/"U|tKV+Z `ؤ 0vC\fӣniV9ܤ)XW'*@5/8-Cd"CiKZTŵwt{tB ~h^O E{'fMnED<0TOmw'2P-jy^o)ɒF}DԛˏU`.6|{PMst4.T`DpHBX]Hqx$3^}Nۄ@u 8 ڧy܊e"[BI@E'C>`_ǩу,X^;@FX\7LR'IzwY,&zqσŰb;>- IDATB>xM^f>T߄MGNφ?@2t +…o Σa\*'RhYG dP>/B X>v?7oah2=Q%N%g9HwcbZp[W1PN`F -3ڼASM] al (`ͩ"l&ݰ2LwGυ۷{Y tUNmLb?/(!6Яku1Xr#nG56N ׾ds?B7A0mx1jF(W@$W6ߣ~T?=!ʺ8Xe2yb.slO撻q&/Io/ߍvtҵv]1H}KXD?lvs(0|r>`pp@]g.%yOxs̰i+#? i{ΟmK+!I͡ɗ)i)/]SSioQe?_qyKK* A!- oN CūNuU2+0 R@1Ӧo֟hHye}^y5H,ˌ9=a8*-K'/ޞCѳƧnz +C:أCNuEĩJn(K:afZgߍT0`D s5s~*KOuS@lR囜 WOEɰ rة݆hTb9IʒeR7Lbz3PzX.2"8[.KeaޭT!Xl6MܒRˉe I%* A!]4 W]6x¶yՍ)Xcĩ[[<>V P`+X䬖7*F%&IH_lء唏~Q+y>gtؒBXANT8oI&pW-\Gp K.h QШz?(*B,9o>,sʬMbxHk_;۱gwYuFk۰F6@*̒Os8 DVjM.,I;" rn,Pf|])4B*T;ņWwTܶOo!vS%6Niٺq*deI2b.$-7#Vj?ZD9{;W3Oy|R%ߘK֣gL؊4E ZNgn קe& qLlS/juq8 /(ʢrxF1WߕރSB^Bp`L\RfgP 3I8 -sF&8J|4&]o8 Cc5،e#*Ek,l|iGvBsp@+:z8৐[=]YFalG18i֏EĂXH3xrk3*WD5[`wV  sASd>1.Z?&.<A\xYݺuU眽; ٺo WŖt'Q¸EUD7˨l]yV4qZ dҫtrcgxٓ'jF<\vq;"%9' I4!P7+]%-RۉZ*^_ ډk}Am(bհNc/IO2%ɓLT'1>ӬVjUmT͈k mjx79@sV~7~5~ǹG5؊Wf45"p(YkQ/Di) <wPGg2!@[` l/Ə;a9OAWZھ"Fa^#9l@yw>:vKt' Maintainer: Jasper Van der Jeugt Homepage: http://github.com/jaspervdj/patat Copyright: 2016 Jasper Van der Jeugt Category: Text Build-type: Simple Cabal-version: >=1.10 Extra-source-files: CHANGELOG.md README.md Source-repository head Type: git Location: git://github.com/jaspervdj/patat.git Executable patat Main-is: Main.hs Ghc-options: -Wall -threaded -rtsopts "-with-rtsopts=-N" Hs-source-dirs: src Default-language: Haskell2010 Build-depends: aeson >= 0.9 && < 1.3, ansi-terminal >= 0.6 && < 0.7, ansi-wl-pprint >= 0.6 && < 0.7, base >= 4.6 && < 4.10, bytestring >= 0.10 && < 0.11, containers >= 0.5 && < 0.6, directory >= 1.2 && < 1.4, filepath >= 1.4 && < 1.5, mtl >= 2.2 && < 2.3, optparse-applicative >= 0.12 && < 0.15, pandoc >= 1.16 && < 1.20, skylighting >= 0.1 && < 0.4, terminal-size >= 0.3 && < 0.4, text >= 1.2 && < 1.3, time >= 1.4 && < 1.8, unordered-containers >= 0.2 && < 0.3, yaml >= 0.7 && < 0.9, -- We don't even depend on these packages but they can break cabal install -- because of the conflicting 'Network.URI' module. network-uri >= 2.6, network >= 2.6 Other-modules: Data.Aeson.Extended Data.Aeson.TH.Extended Data.Data.Extended Patat.AutoAdvance Patat.Presentation Patat.Presentation.Display Patat.Presentation.Display.CodeBlock Patat.Presentation.Display.Table Patat.Presentation.Fragment Patat.Presentation.Interactive Patat.Presentation.Internal Patat.Presentation.Read Patat.PrettyPrint Patat.Theme Text.Pandoc.Extended patat-0.5.2.2/src/000077500000000000000000000000001312021255100135465ustar00rootroot00000000000000patat-0.5.2.2/src/Data/000077500000000000000000000000001312021255100144175ustar00rootroot00000000000000patat-0.5.2.2/src/Data/Aeson/000077500000000000000000000000001312021255100154645ustar00rootroot00000000000000patat-0.5.2.2/src/Data/Aeson/Extended.hs000066400000000000000000000014121312021255100175560ustar00rootroot00000000000000{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Data.Aeson.Extended ( module Data.Aeson , FlexibleNum (..) ) where import Control.Applicative ((<$>)) import Data.Aeson import qualified Data.Text as T import Text.Read (readMaybe) import Prelude -- | This can be parsed from a JSON string in addition to a JSON number. newtype FlexibleNum a = FlexibleNum {unFlexibleNum :: a} deriving (Show, ToJSON) instance (FromJSON a, Read a) => FromJSON (FlexibleNum a) where parseJSON (String str) = case readMaybe (T.unpack str) of Nothing -> fail $ "Could not parse " ++ T.unpack str ++ " as a number" Just x -> return (FlexibleNum x) parseJSON val = FlexibleNum <$> parseJSON val patat-0.5.2.2/src/Data/Aeson/TH/000077500000000000000000000000001312021255100157775ustar00rootroot00000000000000patat-0.5.2.2/src/Data/Aeson/TH/Extended.hs000066400000000000000000000012151312021255100200720ustar00rootroot00000000000000-------------------------------------------------------------------------------- module Data.Aeson.TH.Extended ( module Data.Aeson.TH , dropPrefixOptions ) where -------------------------------------------------------------------------------- import Data.Aeson.TH import Data.Char (isUpper, toLower) -------------------------------------------------------------------------------- dropPrefixOptions :: Options dropPrefixOptions = defaultOptions { fieldLabelModifier = dropPrefix } where dropPrefix str = case break isUpper str of (_, (y : ys)) -> toLower y : ys _ -> str patat-0.5.2.2/src/Data/Data/000077500000000000000000000000001312021255100152705ustar00rootroot00000000000000patat-0.5.2.2/src/Data/Data/Extended.hs000066400000000000000000000011201312021255100173560ustar00rootroot00000000000000module Data.Data.Extended ( module Data.Data , grecQ , grecT ) where import Data.Data -- | Recursively find all values of a certain type. grecQ :: (Data a, Data b) => a -> [b] grecQ = concat . gmapQ (\x -> maybe id (:) (cast x) $ grecQ x) -- | Recursively apply an update to a certain type. grecT :: (Data a, Data b) => (a -> a) -> b -> b grecT f x = gmapT (grecT f) (castMap f x) castMap :: (Typeable a, Typeable b) => (a -> a) -> b -> b castMap f x = case cast x of Nothing -> x Just y -> case cast (f y) of Nothing -> x Just z -> z patat-0.5.2.2/src/Main.hs000066400000000000000000000155021312021255100147710ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Main where -------------------------------------------------------------------------------- import Control.Applicative ((<$>), (<*>)) import Control.Concurrent (forkIO, threadDelay) import qualified Control.Concurrent.Chan as Chan import Control.Exception (finally) import Control.Monad (forever, unless, when) import qualified Data.Aeson.Extended as A import Data.Monoid (mempty, (<>)) import Data.Time (UTCTime) import Data.Version (showVersion) import qualified Options.Applicative as OA import Patat.AutoAdvance import Patat.Presentation import qualified Paths_patat import Prelude import qualified System.Console.ANSI as Ansi import System.Directory (doesFileExist, getModificationTime) import System.Exit (exitFailure, exitSuccess) import qualified System.IO as IO import qualified Text.PrettyPrint.ANSI.Leijen as PP -------------------------------------------------------------------------------- data Options = Options { oFilePath :: !(Maybe FilePath) , oForce :: !Bool , oDump :: !Bool , oWatch :: !Bool , oVersion :: !Bool } deriving (Show) -------------------------------------------------------------------------------- parseOptions :: OA.Parser Options parseOptions = Options <$> (OA.optional $ OA.strArgument $ OA.metavar "FILENAME" <> OA.help "Input file") <*> (OA.switch $ OA.long "force" <> OA.short 'f' <> OA.help "Force ANSI terminal" <> OA.hidden) <*> (OA.switch $ OA.long "dump" <> OA.short 'd' <> OA.help "Just dump all slides and exit" <> OA.hidden) <*> (OA.switch $ OA.long "watch" <> OA.short 'w' <> OA.help "Watch file for changes") <*> (OA.switch $ OA.long "version" <> OA.help "Display version info and exit" <> OA.hidden) -------------------------------------------------------------------------------- parserInfo :: OA.ParserInfo Options parserInfo = OA.info (OA.helper <*> parseOptions) $ OA.fullDesc <> OA.header ("patat v" <> showVersion Paths_patat.version) <> OA.progDescDoc (Just desc) where desc = PP.vcat [ "Terminal-based presentations using Pandoc" , "" , "Controls:" , "- Next slide: space, enter, l, right, pagedown" , "- Previous slide: backspace, h, left, pageup" , "- Go forward 10 slides: j, down" , "- Go backward 10 slides: k, up" , "- First slide: 0" , "- Last slide: G" , "- Reload file: r" , "- Quit: q" ] -------------------------------------------------------------------------------- parserPrefs :: OA.ParserPrefs parserPrefs = OA.prefs OA.showHelpOnError -------------------------------------------------------------------------------- errorAndExit :: [String] -> IO a errorAndExit msg = do mapM_ (IO.hPutStrLn IO.stderr) msg exitFailure -------------------------------------------------------------------------------- assertAnsiFeatures :: IO () assertAnsiFeatures = do supports <- Ansi.hSupportsANSI IO.stdout unless supports $ errorAndExit [ "It looks like your terminal does not support ANSI codes." , "If you still want to run the presentation, use `--force`." ] -------------------------------------------------------------------------------- main :: IO () main = do options <- OA.customExecParser parserPrefs parserInfo when (oVersion options) $ do putStrLn (showVersion Paths_patat.version) exitSuccess filePath <- case oFilePath options of Just fp -> return fp Nothing -> OA.handleParseResult $ OA.Failure $ OA.parserFailure parserPrefs parserInfo OA.ShowHelpText mempty errOrPres <- readPresentation filePath pres <- either (errorAndExit . return) return errOrPres unless (oForce options) assertAnsiFeatures if oDump options then dumpPresentation pres else interactiveLoop options pres where interactiveLoop :: Options -> Presentation -> IO () interactiveLoop options pres0 = (`finally` cleanup) $ do IO.hSetBuffering IO.stdin IO.NoBuffering Ansi.hideCursor -- Spawn the initial channel that gives us commands based on user input. commandChan0 <- Chan.newChan _ <- forkIO $ forever $ readPresentationCommand >>= Chan.writeChan commandChan0 -- If an auto delay is set, use 'autoAdvance' to create a new one. commandChan <- case psAutoAdvanceDelay (pSettings pres0) of Nothing -> return commandChan0 Just (A.FlexibleNum delay) -> autoAdvance delay commandChan0 -- Spawn a thread that adds 'Reload' commands based on the file time. mtime0 <- getModificationTime (pFilePath pres0) when (oWatch options) $ do _ <- forkIO $ watcher commandChan (pFilePath pres0) mtime0 return () let loop :: Presentation -> Maybe String -> IO () loop pres mbError = do case mbError of Nothing -> displayPresentation pres Just err -> displayPresentationError pres err c <- Chan.readChan commandChan update <- updatePresentation c pres case update of ExitedPresentation -> return () UpdatedPresentation pres' -> loop pres' Nothing ErroredPresentation err -> loop pres (Just err) loop pres0 Nothing cleanup :: IO () cleanup = do Ansi.showCursor Ansi.clearScreen Ansi.setCursorPosition 0 0 -------------------------------------------------------------------------------- watcher :: Chan.Chan PresentationCommand -> FilePath -> UTCTime -> IO a watcher chan filePath mtime0 = do -- The extra exists check helps because some editors temporarily make the -- file dissapear while writing. exists <- doesFileExist filePath mtime1 <- if exists then getModificationTime filePath else return mtime0 when (mtime1 > mtime0) $ Chan.writeChan chan Reload threadDelay (200 * 1000) watcher chan filePath mtime1 patat-0.5.2.2/src/Patat/000077500000000000000000000000001312021255100146175ustar00rootroot00000000000000patat-0.5.2.2/src/Patat/AutoAdvance.hs000066400000000000000000000041461312021255100173520ustar00rootroot00000000000000-------------------------------------------------------------------------------- module Patat.AutoAdvance ( autoAdvance ) where -------------------------------------------------------------------------------- import Control.Concurrent (forkIO, threadDelay) import qualified Control.Concurrent.Chan as Chan import Control.Monad (forever) import qualified Data.IORef as IORef import Data.Time (diffUTCTime, getCurrentTime) import Patat.Presentation (PresentationCommand (..)) -------------------------------------------------------------------------------- -- | This function takes an existing channel for presentation commands -- (presumably coming from human input) and creates a new one that /also/ sends -- a 'Forward' command if nothing happens for N seconds. autoAdvance :: Int -> Chan.Chan PresentationCommand -> IO (Chan.Chan PresentationCommand) autoAdvance delaySeconds existingChan = do let delay = delaySeconds * 1000 -- We are working with ms in this function newChan <- Chan.newChan latestCommandAt <- IORef.newIORef =<< getCurrentTime -- This is a thread that copies 'existingChan' to 'newChan', and writes -- whenever the latest command was to 'latestCommandAt'. _ <- forkIO $ forever $ do cmd <- Chan.readChan existingChan getCurrentTime >>= IORef.writeIORef latestCommandAt Chan.writeChan newChan cmd -- This is a thread that waits around 'delay' seconds and then checks if -- there's been a more recent command. If not, we write a 'Forward'. _ <- forkIO $ forever $ do current <- getCurrentTime latest <- IORef.readIORef latestCommandAt let elapsed = floor $ 1000 * (current `diffUTCTime` latest) :: Int if elapsed >= delay then do Chan.writeChan newChan Forward IORef.writeIORef latestCommandAt current threadDelay (delay * 1000) else do let wait = delay - elapsed threadDelay (wait * 1000) return newChan patat-0.5.2.2/src/Patat/Presentation.hs000066400000000000000000000010211312021255100176200ustar00rootroot00000000000000module Patat.Presentation ( PresentationSettings (..) , defaultPresentationSettings , Presentation (..) , readPresentation , displayPresentation , displayPresentationError , dumpPresentation , PresentationCommand (..) , readPresentationCommand , UpdatedPresentation (..) , updatePresentation ) where import Patat.Presentation.Display import Patat.Presentation.Interactive import Patat.Presentation.Internal import Patat.Presentation.Read patat-0.5.2.2/src/Patat/Presentation/000077500000000000000000000000001312021255100172725ustar00rootroot00000000000000patat-0.5.2.2/src/Patat/Presentation/Display.hs000066400000000000000000000313541312021255100212410ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Display ( displayPresentation , displayPresentationError , dumpPresentation ) where -------------------------------------------------------------------------------- import Control.Applicative ((<$>)) import Control.Monad (mplus, unless) import qualified Data.Aeson.Extended as A import Data.Data.Extended (grecQ) import Data.List (intersperse) import Data.Maybe (fromMaybe) import Data.Monoid (mconcat, mempty, (<>)) import qualified Data.Text as T import Patat.Presentation.Display.CodeBlock import Patat.Presentation.Display.Table import Patat.Presentation.Internal import Patat.PrettyPrint ((<$$>), (<+>)) import qualified Patat.PrettyPrint as PP import Patat.Theme (Theme (..)) import qualified Patat.Theme as Theme import Prelude import qualified System.Console.ANSI as Ansi import qualified System.Console.Terminal.Size as Terminal import qualified System.IO as IO import qualified Text.Pandoc.Extended as Pandoc -------------------------------------------------------------------------------- data CanvasSize = CanvasSize {csRows :: Int, csCols :: Int} deriving (Show) -------------------------------------------------------------------------------- -- | Display something within the presentation borders that draw the title and -- the active slide number and so on. displayWithBorders :: Presentation -> (CanvasSize -> Theme -> PP.Doc) -> IO () displayWithBorders Presentation {..} f = do Ansi.clearScreen Ansi.setCursorPosition 0 0 -- Get terminal width/title mbWindow <- Terminal.size let columns = fromMaybe 72 $ (A.unFlexibleNum <$> psColumns pSettings) `mplus` (Terminal.width <$> mbWindow) rows = fromMaybe 24 $ (A.unFlexibleNum <$> psRows pSettings) `mplus` (Terminal.height <$> mbWindow) let settings = pSettings {psColumns = Just $ A.FlexibleNum columns} theme = fromMaybe Theme.defaultTheme (psTheme settings) title = PP.toString (prettyInlines theme pTitle) titleWidth = length title titleOffset = (columns - titleWidth) `div` 2 borders = themed (themeBorders theme) unless (null title) $ do Ansi.setCursorColumn titleOffset PP.putDoc $ borders $ PP.string title putStrLn "" putStrLn "" let canvasSize = CanvasSize (rows - 2) columns PP.putDoc $ withWrapSettings settings $ f canvasSize theme putStrLn "" let (sidx, _) = pActiveFragment active = show (sidx + 1) ++ " / " ++ show (length pSlides) activeWidth = length active Ansi.setCursorPosition (rows - 1) 0 PP.putDoc $ " " <> borders (prettyInlines theme pAuthor) Ansi.setCursorColumn (columns - activeWidth - 1) PP.putDoc $ borders $ PP.string active IO.hFlush IO.stdout -------------------------------------------------------------------------------- displayPresentation :: Presentation -> IO () displayPresentation pres@Presentation {..} = displayWithBorders pres $ \canvasSize theme -> case getActiveFragment pres of Nothing -> mempty Just (ActiveContent fragment) -> prettyFragment theme fragment Just (ActiveTitle block) -> let pblock = prettyBlock theme block (prows, pcols) = PP.dimensions pblock offsetRow = (csRows canvasSize `div` 2) - (prows `div` 2) offsetCol = (csCols canvasSize `div` 2) - (pcols `div` 2) spaces = mconcat (replicate offsetCol PP.space) in mconcat (replicate (offsetRow - 3) PP.hardline) <$$> PP.indent (PP.NotTrimmable spaces) (PP.NotTrimmable spaces) pblock -------------------------------------------------------------------------------- -- | Displays an error in the place of the presentation. This is useful if we -- want to display an error but keep the presentation running. displayPresentationError :: Presentation -> String -> IO () displayPresentationError pres err = displayWithBorders pres $ \_ Theme {..} -> themed themeStrong "Error occurred in the presentation:" <$$> "" <$$> (PP.string err) -------------------------------------------------------------------------------- dumpPresentation :: Presentation -> IO () dumpPresentation pres = let theme = fromMaybe Theme.defaultTheme (psTheme $ pSettings pres) in PP.putDoc $ withWrapSettings (pSettings pres) $ PP.vcat $ intersperse "----------" $ do slide <- pSlides pres return $ case slide of TitleSlide block -> "~~~title" <$$> prettyBlock theme block ContentSlide fragments -> PP.vcat $ intersperse "~~~frag" $ do fragment <- fragments return $ prettyFragment theme fragment -------------------------------------------------------------------------------- withWrapSettings :: PresentationSettings -> PP.Doc -> PP.Doc withWrapSettings ps = case (psWrap ps, psColumns ps) of (Just True, Just (A.FlexibleNum col)) -> PP.wrapAt (Just col) _ -> id -------------------------------------------------------------------------------- prettyFragment :: Theme -> Fragment -> PP.Doc prettyFragment theme fragment@(Fragment blocks) = prettyBlocks theme blocks <> case prettyReferences theme fragment of [] -> mempty refs -> PP.hardline <> PP.vcat refs -------------------------------------------------------------------------------- prettyBlock :: Theme -> Pandoc.Block -> PP.Doc prettyBlock theme (Pandoc.Plain inlines) = prettyInlines theme inlines prettyBlock theme (Pandoc.Para inlines) = prettyInlines theme inlines <> PP.hardline prettyBlock theme@Theme {..} (Pandoc.Header i _ inlines) = themed themeHeader (PP.string (replicate i '#') <+> prettyInlines theme inlines) <> PP.hardline prettyBlock theme (Pandoc.CodeBlock (_, classes, _) txt) = prettyCodeBlock theme classes txt prettyBlock theme (Pandoc.BulletList bss) = PP.vcat [ PP.indent (PP.NotTrimmable $ themed (themeBulletList theme) prefix) (PP.Trimmable " ") (prettyBlocks theme' bs) | bs <- bss ] <> PP.hardline where prefix = " " <> PP.string [marker] <> " " marker = case T.unpack <$> themeBulletListMarkers theme of Just (x : _) -> x _ -> '-' -- Cycle the markers. theme' = theme { themeBulletListMarkers = (\ls -> T.drop 1 ls <> T.take 1 ls) <$> themeBulletListMarkers theme } prettyBlock theme@Theme {..} (Pandoc.OrderedList _ bss) = PP.vcat [ PP.indent (PP.NotTrimmable $ themed themeOrderedList $ PP.string prefix) (PP.Trimmable " ") (prettyBlocks theme bs) | (prefix, bs) <- zip padded bss ] <> PP.hardline where padded = [n ++ replicate (4 - length n) ' ' | n <- numbers] numbers = [ show i ++ "." | i <- [1 .. length bss] ] prettyBlock _theme (Pandoc.RawBlock _ t) = PP.string t <> PP.hardline prettyBlock _theme Pandoc.HorizontalRule = "---" prettyBlock theme@Theme {..} (Pandoc.BlockQuote bs) = let quote = PP.NotTrimmable (themed themeBlockQuote "> ") in PP.indent quote quote (prettyBlocks theme bs) prettyBlock theme@Theme {..} (Pandoc.DefinitionList terms) = PP.vcat $ map prettyDefinition terms where prettyDefinition (term, definitions) = themed themeDefinitionTerm (prettyInlines theme term) <$$> PP.hardline <> PP.vcat [ PP.indent (PP.NotTrimmable (themed themeDefinitionList ": ")) (PP.Trimmable " ") $ prettyBlocks theme (Pandoc.plainToPara definition) | definition <- definitions ] prettyBlock theme (Pandoc.Table caption aligns _ headers rows) = PP.wrapAt Nothing $ prettyTable theme Table { tCaption = prettyInlines theme caption , tAligns = map align aligns , tHeaders = map (prettyBlocks theme) headers , tRows = map (map (prettyBlocks theme)) rows } where align Pandoc.AlignLeft = PP.AlignLeft align Pandoc.AlignCenter = PP.AlignCenter align Pandoc.AlignDefault = PP.AlignLeft align Pandoc.AlignRight = PP.AlignRight prettyBlock theme (Pandoc.Div _attrs blocks) = prettyBlocks theme blocks prettyBlock _theme Pandoc.Null = mempty #if MIN_VERSION_pandoc(1,18,0) -- 'LineBlock' elements are new in pandoc-1.18 prettyBlock theme@Theme {..} (Pandoc.LineBlock inliness) = let ind = PP.NotTrimmable (themed themeLineBlock "| ") in PP.wrapAt Nothing $ PP.indent ind ind $ PP.vcat $ map (prettyInlines theme) inliness #endif -------------------------------------------------------------------------------- prettyBlocks :: Theme -> [Pandoc.Block] -> PP.Doc prettyBlocks theme = PP.vcat . map (prettyBlock theme) -------------------------------------------------------------------------------- prettyInline :: Theme -> Pandoc.Inline -> PP.Doc prettyInline _theme Pandoc.Space = PP.space prettyInline _theme (Pandoc.Str str) = PP.string str prettyInline theme@Theme {..} (Pandoc.Emph inlines) = themed themeEmph $ prettyInlines theme inlines prettyInline theme@Theme {..} (Pandoc.Strong inlines) = themed themeStrong $ prettyInlines theme inlines prettyInline Theme {..} (Pandoc.Code _ txt) = themed themeCode $ PP.string (" " <> txt <> " ") prettyInline theme@Theme {..} link@(Pandoc.Link _attrs text (target, _title)) | isReferenceLink link = "[" <> themed themeLinkText (prettyInlines theme text) <> "]" | otherwise = "<" <> themed themeLinkTarget (PP.string target) <> ">" prettyInline _theme Pandoc.SoftBreak = PP.softline prettyInline _theme Pandoc.LineBreak = PP.hardline prettyInline theme@Theme {..} (Pandoc.Strikeout t) = "~~" <> themed themeStrikeout (prettyInlines theme t) <> "~~" prettyInline theme@Theme {..} (Pandoc.Quoted Pandoc.SingleQuote t) = "'" <> themed themeQuoted (prettyInlines theme t) <> "'" prettyInline theme@Theme {..} (Pandoc.Quoted Pandoc.DoubleQuote t) = "'" <> themed themeQuoted (prettyInlines theme t) <> "'" prettyInline Theme {..} (Pandoc.Math _ t) = themed themeMath (PP.string t) prettyInline theme@Theme {..} (Pandoc.Image _attrs text (target, _title)) = "![" <> themed themeImageText (prettyInlines theme text) <> "](" <> themed themeImageTarget (PP.string target) <> ")" -- These elements aren't really supported. prettyInline theme (Pandoc.Cite _ t) = prettyInlines theme t prettyInline theme (Pandoc.Span _ t) = prettyInlines theme t prettyInline _theme (Pandoc.RawInline _ t) = PP.string t prettyInline theme (Pandoc.Note t) = prettyBlocks theme t prettyInline theme (Pandoc.Superscript t) = prettyInlines theme t prettyInline theme (Pandoc.Subscript t) = prettyInlines theme t prettyInline theme (Pandoc.SmallCaps t) = prettyInlines theme t -- prettyInline unsupported = PP.ondullred $ PP.string $ show unsupported -------------------------------------------------------------------------------- prettyInlines :: Theme -> [Pandoc.Inline] -> PP.Doc prettyInlines theme = mconcat . map (prettyInline theme) -------------------------------------------------------------------------------- prettyReferences :: Theme -> Fragment -> [PP.Doc] prettyReferences theme@Theme {..} = map prettyReference . getReferences . unFragment where getReferences :: [Pandoc.Block] -> [Pandoc.Inline] getReferences = filter isReferenceLink . grecQ prettyReference :: Pandoc.Inline -> PP.Doc prettyReference (Pandoc.Link _attrs text (target, title)) = "[" <> themed themeLinkText (prettyInlines theme $ Pandoc.newlineToSpace text) <> "](" <> themed themeLinkTarget (PP.string target) <> (if null title then mempty else PP.space <> "\"" <> PP.string title <> "\"") <> ")" prettyReference _ = mempty -------------------------------------------------------------------------------- isReferenceLink :: Pandoc.Inline -> Bool isReferenceLink (Pandoc.Link _attrs text (target, _)) = [Pandoc.Str target] /= text isReferenceLink _ = False patat-0.5.2.2/src/Patat/Presentation/Display/000077500000000000000000000000001312021255100206775ustar00rootroot00000000000000patat-0.5.2.2/src/Patat/Presentation/Display/CodeBlock.hs000066400000000000000000000061731312021255100230670ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- | Displaying code blocks, optionally with syntax highlighting. {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Display.CodeBlock ( prettyCodeBlock ) where -------------------------------------------------------------------------------- import Data.Maybe (mapMaybe) import Data.Monoid (mconcat, (<>)) import qualified Data.Text as T import Patat.Presentation.Display.Table (themed) import qualified Patat.PrettyPrint as PP import Patat.Theme import Prelude import qualified Skylighting as Skylighting -------------------------------------------------------------------------------- highlight :: [String] -> String -> [Skylighting.SourceLine] highlight classes rawCodeBlock = case mapMaybe getSyntax classes of [] -> zeroHighlight rawCodeBlock (syn : _) -> case Skylighting.tokenize config syn (T.pack rawCodeBlock) of Left _ -> zeroHighlight rawCodeBlock Right sl -> sl where getSyntax :: String -> Maybe Skylighting.Syntax getSyntax c = Skylighting.lookupSyntax (T.pack c) syntaxMap config :: Skylighting.TokenizerConfig config = Skylighting.TokenizerConfig { Skylighting.syntaxMap = syntaxMap , Skylighting.traceOutput = False } syntaxMap :: Skylighting.SyntaxMap syntaxMap = Skylighting.defaultSyntaxMap -------------------------------------------------------------------------------- -- | This does fake highlighting, everything becomes a normal token. That makes -- things a bit easier, since we only need to deal with one cases in the -- renderer. zeroHighlight :: String -> [Skylighting.SourceLine] zeroHighlight str = [[(Skylighting.NormalTok, T.pack line)] | line <- lines str] -------------------------------------------------------------------------------- prettyCodeBlock :: Theme -> [String] -> String -> PP.Doc prettyCodeBlock theme@Theme {..} classes rawCodeBlock = PP.vcat (map blockified sourceLines) <> PP.hardline where sourceLines :: [Skylighting.SourceLine] sourceLines = [[]] ++ highlight classes rawCodeBlock ++ [[]] prettySourceLine :: Skylighting.SourceLine -> PP.Doc prettySourceLine = mconcat . map prettyToken prettyToken :: Skylighting.Token -> PP.Doc prettyToken (tokenType, str) = themed (syntaxHighlight theme tokenType) (PP.string $ T.unpack str) sourceLineLength :: Skylighting.SourceLine -> Int sourceLineLength line = sum [T.length str | (_, str) <- line] blockWidth :: Int blockWidth = foldr max 0 (map sourceLineLength sourceLines) blockified :: Skylighting.SourceLine -> PP.Doc blockified line = let len = sourceLineLength line indent = PP.NotTrimmable " " in PP.indent indent indent $ themed themeCodeBlock $ " " <> prettySourceLine line <> PP.string (replicate (blockWidth - len) ' ') <> " " patat-0.5.2.2/src/Patat/Presentation/Display/Table.hs000066400000000000000000000071661312021255100222740ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Display.Table ( Table (..) , prettyTable , themed ) where -------------------------------------------------------------------------------- import Data.List (intersperse, transpose) import Data.Monoid (mconcat, mempty, (<>)) import Patat.PrettyPrint ((<$$>)) import qualified Patat.PrettyPrint as PP import Patat.Theme (Theme (..)) import qualified Patat.Theme as Theme import Prelude -------------------------------------------------------------------------------- data Table = Table { tCaption :: PP.Doc , tAligns :: [PP.Alignment] , tHeaders :: [PP.Doc] , tRows :: [[PP.Doc]] } -------------------------------------------------------------------------------- prettyTable :: Theme -> Table -> PP.Doc prettyTable theme@Theme {..} Table {..} = PP.indent (PP.Trimmable " ") (PP.Trimmable " ") $ lineIf (not isHeaderLess) (hcat2 headerHeight [ themed themeTableHeader (PP.align w a (vpad headerHeight header)) | (w, a, header) <- zip3 columnWidths tAligns tHeaders ]) <> dashedHeaderSeparator theme columnWidths <$$> joinRows [ hcat2 rowHeight [ PP.align w a (vpad rowHeight cell) | (w, a, cell) <- zip3 columnWidths tAligns row ] | (rowHeight, row) <- zip rowHeights tRows ] <$$> lineIf isHeaderLess (dashedHeaderSeparator theme columnWidths) <> lineIf (not $ PP.null tCaption) (PP.hardline <> "Table: " <> tCaption) where lineIf cond line = if cond then line <> PP.hardline else mempty joinRows | all (all isSimpleCell) tRows = PP.vcat | otherwise = PP.vcat . intersperse "" isHeaderLess = all PP.null tHeaders headerDimensions = map PP.dimensions tHeaders :: [(Int, Int)] rowDimensions = map (map PP.dimensions) tRows :: [[(Int, Int)]] columnWidths :: [Int] columnWidths = [ safeMax (map snd col) | col <- transpose (headerDimensions : rowDimensions) ] rowHeights = map (safeMax . map fst) rowDimensions :: [Int] headerHeight = safeMax (map fst headerDimensions) :: Int vpad :: Int -> PP.Doc -> PP.Doc vpad height doc = let (actual, _) = PP.dimensions doc in doc <> mconcat (replicate (height - actual) PP.hardline) safeMax = foldr max 0 hcat2 :: Int -> [PP.Doc] -> PP.Doc hcat2 rowHeight = PP.paste . intersperse (spaces2 rowHeight) spaces2 :: Int -> PP.Doc spaces2 rowHeight = mconcat $ intersperse PP.hardline $ replicate rowHeight (PP.string " ") -------------------------------------------------------------------------------- isSimpleCell :: PP.Doc -> Bool isSimpleCell = (<= 1) . fst . PP.dimensions -------------------------------------------------------------------------------- dashedHeaderSeparator :: Theme -> [Int] -> PP.Doc dashedHeaderSeparator Theme {..} columnWidths = mconcat $ intersperse (PP.string " ") [ themed themeTableSeparator (PP.string (replicate w '-')) | w <- columnWidths ] -------------------------------------------------------------------------------- -- | This does not really belong in the module. themed :: Maybe Theme.Style -> PP.Doc -> PP.Doc themed Nothing = id themed (Just (Theme.Style [])) = id themed (Just (Theme.Style codes)) = PP.ansi codes patat-0.5.2.2/src/Patat/Presentation/Fragment.hs000066400000000000000000000122301312021255100213670ustar00rootroot00000000000000-- | For background info on the spec, see the "Incremental lists" section of the -- the pandoc manual. {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} module Patat.Presentation.Fragment ( FragmentSettings (..) , fragmentBlocks , fragmentBlock ) where import Data.Foldable (Foldable) import Data.List (foldl', intersperse) import Data.Maybe (fromMaybe) import Data.Traversable (Traversable) import Prelude import qualified Text.Pandoc as Pandoc data FragmentSettings = FragmentSettings { fsIncrementalLists :: !Bool } deriving (Show) -- fragmentBlocks :: [Pandoc.Block] -> [[Pandoc.Block]] -- fragmentBlocks = NonEmpty.toList . joinFragmentedBlocks . map fragmentBlock fragmentBlocks :: FragmentSettings -> [Pandoc.Block] -> [[Pandoc.Block]] fragmentBlocks fs blocks0 = case joinFragmentedBlocks (map (fragmentBlock fs) blocks0) of Unfragmented bs -> [bs] Fragmented xs bs -> map (fromMaybe []) xs ++ [fromMaybe [] bs] -- | This is all the ways we can "present" a block, after splitting in -- fragments. -- -- In the simplest (and most common case) a block can only be presented in a -- single way ('Unfragmented'). -- -- Alternatively, we might want to show different (partial) versions of the -- block first before showing the final complete one. These partial or complete -- versions can be empty, hence the 'Maybe'. -- -- For example, imagine that we display the following bullet list incrementally: -- -- > [1, 2, 3] -- -- Then we would get something like: -- -- > Fragmented [Nothing, Just [1], Just [1, 2]] (Just [1, 2, 3]) data Fragmented a = Unfragmented a | Fragmented [Maybe a] (Maybe a) deriving (Functor, Foldable, Show, Traversable) fragmentBlock :: FragmentSettings -> Pandoc.Block -> Fragmented Pandoc.Block fragmentBlock _fs block@(Pandoc.Para inlines) | inlines == threeDots = Fragmented [Nothing] Nothing | otherwise = Unfragmented block where threeDots = intersperse Pandoc.Space $ replicate 3 (Pandoc.Str ".") fragmentBlock fs (Pandoc.BulletList bs0) = fragmentList fs (fsIncrementalLists fs) Pandoc.BulletList bs0 fragmentBlock fs (Pandoc.OrderedList attr bs0) = fragmentList fs (fsIncrementalLists fs) (Pandoc.OrderedList attr) bs0 fragmentBlock fs (Pandoc.BlockQuote [Pandoc.BulletList bs0]) = fragmentList fs (not $ fsIncrementalLists fs) Pandoc.BulletList bs0 fragmentBlock fs (Pandoc.BlockQuote [Pandoc.OrderedList attr bs0]) = fragmentList fs (not $ fsIncrementalLists fs) (Pandoc.OrderedList attr) bs0 fragmentBlock _ block@(Pandoc.BlockQuote _) = Unfragmented block fragmentBlock _ block@(Pandoc.Header _ _ _) = Unfragmented block fragmentBlock _ block@(Pandoc.Plain _) = Unfragmented block fragmentBlock _ block@(Pandoc.CodeBlock _ _) = Unfragmented block fragmentBlock _ block@(Pandoc.RawBlock _ _) = Unfragmented block fragmentBlock _ block@(Pandoc.DefinitionList _) = Unfragmented block fragmentBlock _ block@(Pandoc.Table _ _ _ _ _) = Unfragmented block fragmentBlock _ block@(Pandoc.Div _ _) = Unfragmented block fragmentBlock _ block@Pandoc.HorizontalRule = Unfragmented block fragmentBlock _ block@Pandoc.Null = Unfragmented block #if MIN_VERSION_pandoc(1,18,0) fragmentBlock _ block@(Pandoc.LineBlock _) = Unfragmented block #endif joinFragmentedBlocks :: [Fragmented block] -> Fragmented [block] joinFragmentedBlocks = foldl' append (Unfragmented []) where append (Unfragmented xs) (Unfragmented y) = Unfragmented (xs ++ [y]) append (Fragmented xs x) (Unfragmented y) = Fragmented xs (appendMaybe x (Just y)) append (Unfragmented x) (Fragmented ys y) = Fragmented [appendMaybe (Just x) y' | y' <- ys] (appendMaybe (Just x) y) append (Fragmented xs x) (Fragmented ys y) = Fragmented (xs ++ [appendMaybe x y' | y' <- ys]) (appendMaybe x y) appendMaybe :: Maybe [a] -> Maybe a -> Maybe [a] appendMaybe Nothing Nothing = Nothing appendMaybe Nothing (Just x) = Just [x] appendMaybe (Just xs) Nothing = Just xs appendMaybe (Just xs) (Just x) = Just (xs ++ [x]) fragmentList :: FragmentSettings -- ^ Global settings -> Bool -- ^ Fragment THIS list? -> ([[Pandoc.Block]] -> Pandoc.Block) -- ^ List constructor -> [[Pandoc.Block]] -- ^ List items -> Fragmented Pandoc.Block -- ^ Resulting list fragmentList fs fragmentThisList constructor blocks0 = fmap constructor fragmented where -- The fragmented list per list item. items :: [Fragmented [Pandoc.Block]] items = map (joinFragmentedBlocks . map (fragmentBlock fs)) blocks0 fragmented :: Fragmented [[Pandoc.Block]] fragmented = joinFragmentedBlocks $ map (if fragmentThisList then insertPause else id) items insertPause :: Fragmented a -> Fragmented a insertPause (Unfragmented x) = Fragmented [Nothing] (Just x) insertPause (Fragmented xs x) = Fragmented (Nothing : xs) x patat-0.5.2.2/src/Patat/Presentation/Interactive.hs000066400000000000000000000105211312021255100221020ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- | Module that allows the user to interact with the presentation {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Interactive ( PresentationCommand (..) , readPresentationCommand , UpdatedPresentation (..) , updatePresentation ) where -------------------------------------------------------------------------------- import Patat.Presentation.Internal import Patat.Presentation.Read -------------------------------------------------------------------------------- data PresentationCommand = Exit | Forward | Backward | SkipForward | SkipBackward | First | Last | Reload | UnknownCommand String -------------------------------------------------------------------------------- readPresentationCommand :: IO PresentationCommand readPresentationCommand = do k <- readKey case k of "q" -> return Exit "\n" -> return Forward "\DEL" -> return Backward "h" -> return Backward "j" -> return SkipForward "k" -> return SkipBackward "l" -> return Forward -- Arrow keys "\ESC[C" -> return Forward "\ESC[D" -> return Backward "\ESC[B" -> return SkipForward "\ESC[A" -> return SkipBackward -- PageUp and PageDown "\ESC[6" -> return Forward "\ESC[5" -> return Backward "0" -> return First "G" -> return Last "r" -> return Reload _ -> return (UnknownCommand k) where readKey :: IO String readKey = do c0 <- getChar case c0 of '\ESC' -> do c1 <- getChar case c1 of '[' -> do c2 <- getChar return [c0, c1, c2] _ -> return [c0, c1] _ -> return [c0] -------------------------------------------------------------------------------- data UpdatedPresentation = UpdatedPresentation !Presentation | ExitedPresentation | ErroredPresentation String deriving (Show) -------------------------------------------------------------------------------- updatePresentation :: PresentationCommand -> Presentation -> IO UpdatedPresentation updatePresentation cmd presentation = case cmd of Exit -> return ExitedPresentation Forward -> return $ goToSlide $ \(s, f) -> (s, f + 1) Backward -> return $ goToSlide $ \(s, f) -> (s, f - 1) SkipForward -> return $ goToSlide $ \(s, _) -> (s + 10, 0) SkipBackward -> return $ goToSlide $ \(s, _) -> (s - 10, 0) First -> return $ goToSlide $ \_ -> (0, 0) Last -> return $ goToSlide $ \_ -> (numSlides presentation, 0) Reload -> reloadPresentation UnknownCommand _ -> return (UpdatedPresentation presentation) where numSlides :: Presentation -> Int numSlides pres = length (pSlides pres) clip :: Index -> Presentation -> Index clip (slide, fragment) pres | slide >= numSlides pres = (numSlides pres - 1, lastFragments - 1) | slide < 0 = (0, 0) | fragment >= numFragments' slide = if slide + 1 >= numSlides pres then (slide, lastFragments - 1) else (slide + 1, 0) | fragment < 0 = if slide - 1 >= 0 then (slide - 1, numFragments' (slide - 1) - 1) else (slide, 0) | otherwise = (slide, fragment) where numFragments' s = maybe 1 numFragments (getSlide s pres) lastFragments = numFragments' (numSlides pres - 1) goToSlide :: (Index -> Index) -> UpdatedPresentation goToSlide f = UpdatedPresentation $ presentation { pActiveFragment = clip (f $ pActiveFragment presentation) presentation } reloadPresentation = do errOrPres <- readPresentation (pFilePath presentation) return $ case errOrPres of Left err -> ErroredPresentation err Right pres -> UpdatedPresentation $ pres { pActiveFragment = clip (pActiveFragment presentation) pres } patat-0.5.2.2/src/Patat/Presentation/Internal.hs000066400000000000000000000113501312021255100214020ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} module Patat.Presentation.Internal ( Presentation (..) , PresentationSettings (..) , defaultPresentationSettings , Slide (..) , Fragment (..) , Index , getSlide , numFragments , ActiveFragment (..) , getActiveFragment ) where -------------------------------------------------------------------------------- import Control.Monad (mplus) import qualified Data.Aeson.Extended as A import qualified Data.Aeson.TH.Extended as A import Data.Maybe (listToMaybe) import Data.Monoid (Monoid (..), (<>)) import qualified Patat.Theme as Theme import qualified Text.Pandoc as Pandoc import Prelude -------------------------------------------------------------------------------- data Presentation = Presentation { pFilePath :: !FilePath , pTitle :: ![Pandoc.Inline] , pAuthor :: ![Pandoc.Inline] , pSettings :: !PresentationSettings , pSlides :: [Slide] , pActiveFragment :: !Index } deriving (Show) -------------------------------------------------------------------------------- -- | These are patat-specific settings. That is where they differ from more -- general metadata (author, title...) data PresentationSettings = PresentationSettings { psRows :: !(Maybe (A.FlexibleNum Int)) , psColumns :: !(Maybe (A.FlexibleNum Int)) , psWrap :: !(Maybe Bool) , psTheme :: !(Maybe Theme.Theme) , psIncrementalLists :: !(Maybe Bool) , psAutoAdvanceDelay :: !(Maybe (A.FlexibleNum Int)) , psSlideLevel :: !(Maybe Int) } deriving (Show) -------------------------------------------------------------------------------- instance Monoid PresentationSettings where mempty = PresentationSettings Nothing Nothing Nothing Nothing Nothing Nothing Nothing mappend l r = PresentationSettings { psRows = psRows l `mplus` psRows r , psColumns = psColumns l `mplus` psColumns r , psWrap = psWrap l `mplus` psWrap r , psTheme = psTheme l <> psTheme r , psIncrementalLists = psIncrementalLists l `mplus` psIncrementalLists r , psAutoAdvanceDelay = psAutoAdvanceDelay l `mplus` psAutoAdvanceDelay r , psSlideLevel = psSlideLevel l `mplus` psSlideLevel r } -------------------------------------------------------------------------------- defaultPresentationSettings :: PresentationSettings defaultPresentationSettings = PresentationSettings { psRows = Nothing , psColumns = Nothing , psWrap = Nothing , psTheme = Just Theme.defaultTheme , psIncrementalLists = Nothing , psAutoAdvanceDelay = Nothing , psSlideLevel = Nothing } -------------------------------------------------------------------------------- data Slide = ContentSlide [Fragment] | TitleSlide Pandoc.Block deriving (Show) -------------------------------------------------------------------------------- newtype Fragment = Fragment {unFragment :: [Pandoc.Block]} deriving (Monoid, Show) -------------------------------------------------------------------------------- -- | Active slide, active fragment. type Index = (Int, Int) -------------------------------------------------------------------------------- getSlide :: Int -> Presentation -> Maybe Slide getSlide sidx = listToMaybe . drop sidx . pSlides -------------------------------------------------------------------------------- numFragments :: Slide -> Int numFragments (ContentSlide fragments) = length fragments numFragments (TitleSlide _) = 1 -------------------------------------------------------------------------------- data ActiveFragment = ActiveContent Fragment | ActiveTitle Pandoc.Block deriving (Show) -------------------------------------------------------------------------------- getActiveFragment :: Presentation -> Maybe ActiveFragment getActiveFragment presentation = do let (sidx, fidx) = pActiveFragment presentation slide <- getSlide sidx presentation case slide of TitleSlide block -> return (ActiveTitle block) ContentSlide fragments -> fmap ActiveContent . listToMaybe $ drop fidx fragments -------------------------------------------------------------------------------- $(A.deriveJSON A.dropPrefixOptions ''PresentationSettings) patat-0.5.2.2/src/Patat/Presentation/Read.hs000066400000000000000000000173441312021255100205120ustar00rootroot00000000000000-- | Read a presentation from disk. {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Read ( readPresentation ) where -------------------------------------------------------------------------------- import Control.Monad.Except (ExceptT (..), runExceptT, throwError) import Control.Monad.Trans (liftIO) import qualified Data.Aeson as A import qualified Data.ByteString as B import qualified Data.HashMap.Strict as HMS import Data.Maybe (fromMaybe) import Data.Monoid (mempty, (<>)) import qualified Data.Set as Set import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Yaml as Yaml import Patat.Presentation.Fragment import Patat.Presentation.Internal import Prelude import System.Directory (doesFileExist, getHomeDirectory) import System.FilePath (takeExtension, ()) import qualified Text.Pandoc.Error as Pandoc import qualified Text.Pandoc.Extended as Pandoc -------------------------------------------------------------------------------- readPresentation :: FilePath -> IO (Either String Presentation) readPresentation filePath = runExceptT $ do src <- liftIO $ readFile filePath reader <- case readExtension ext of Nothing -> throwError $ "Unknown file extension: " ++ show ext Just x -> return x doc <- case reader src of Left e -> throwError $ "Could not parse document: " ++ show e Right x -> return x homeSettings <- ExceptT readHomeSettings metaSettings <- ExceptT $ return $ readMetaSettings src let settings = metaSettings <> homeSettings <> defaultPresentationSettings ExceptT $ return $ pandocToPresentation filePath settings doc where ext = takeExtension filePath -------------------------------------------------------------------------------- readExtension :: String -> Maybe (String -> Either Pandoc.PandocError Pandoc.Pandoc) readExtension fileExt = case fileExt of ".md" -> Just $ Pandoc.readMarkdown Pandoc.def ".lhs" -> Just $ Pandoc.readMarkdown lhsOpts "" -> Just $ Pandoc.readMarkdown Pandoc.def ".org" -> Just $ Pandoc.readOrg Pandoc.def _ -> Nothing where lhsOpts = Pandoc.def { Pandoc.readerExtensions = Set.insert Pandoc.Ext_literate_haskell (Pandoc.readerExtensions Pandoc.def) } -------------------------------------------------------------------------------- pandocToPresentation :: FilePath -> PresentationSettings -> Pandoc.Pandoc -> Either String Presentation pandocToPresentation pFilePath pSettings pandoc@(Pandoc.Pandoc meta _) = do let !pTitle = Pandoc.docTitle meta !pSlides = pandocToSlides pSettings pandoc !pActiveFragment = (0, 0) !pAuthor = concat (Pandoc.docAuthors meta) return Presentation {..} -------------------------------------------------------------------------------- -- | This re-parses the pandoc metadata block using the YAML library. This -- avoids the problems caused by pandoc involving rendering Markdown. This -- should only be used for settings though, not things like title / authors -- since those /can/ contain markdown. parseMetadataBlock :: String -> Maybe A.Value parseMetadataBlock src = do block <- mbBlock Yaml.decode $! T.encodeUtf8 $! T.pack block where mbBlock = case lines src of ("---" : ls) -> case break (`elem` ["---", "..."]) ls of (_, []) -> Nothing (block, (_ : _)) -> Just (unlines block) _ -> Nothing -------------------------------------------------------------------------------- -- | Read settings from the metadata block in the Pandoc document. readMetaSettings :: String -> Either String PresentationSettings readMetaSettings src = fromMaybe (Right mempty) $ do A.Object obj <- parseMetadataBlock src val <- HMS.lookup "patat" obj return $! resultToEither $! A.fromJSON val where resultToEither :: A.Result a -> Either String a resultToEither (A.Success x) = Right x resultToEither (A.Error e) = Left $! "Error parsing patat settings from metadata: " ++ e -------------------------------------------------------------------------------- -- | Read settings from "$HOME/.patat.yaml". readHomeSettings :: IO (Either String PresentationSettings) readHomeSettings = do home <- getHomeDirectory let path = home ".patat.yaml" exists <- doesFileExist path if not exists then return (Right mempty) else do contents <- B.readFile path return $! Yaml.decodeEither contents -------------------------------------------------------------------------------- pandocToSlides :: PresentationSettings -> Pandoc.Pandoc -> [Slide] pandocToSlides settings pandoc = let slideLevel = fromMaybe (detectSlideLevel pandoc) (psSlideLevel settings) unfragmented = splitSlides slideLevel pandoc fragmented = [ case slide of TitleSlide _ -> slide ContentSlide fragments0 -> let blocks = concatMap unFragment fragments0 blockss = fragmentBlocks fragmentSettings blocks in ContentSlide (map Fragment blockss) | slide <- unfragmented ] in fragmented where fragmentSettings = FragmentSettings { fsIncrementalLists = fromMaybe False (psIncrementalLists settings) } -------------------------------------------------------------------------------- -- | Find level of header that starts slides. This is defined as the least -- header that occurs before a non-header in the blocks. detectSlideLevel :: Pandoc.Pandoc -> Int detectSlideLevel (Pandoc.Pandoc _meta blocks0) = go 6 blocks0 where go level (Pandoc.Header n _ _ : x : xs) | n < level && nonHeader x = go n xs | otherwise = go level (x:xs) go level (_ : xs) = go level xs go level [] = level nonHeader (Pandoc.Header _ _ _) = False nonHeader _ = True -------------------------------------------------------------------------------- -- | Split a pandoc document into slides. If the document contains horizonal -- rules, we use those as slide delimiters. If there are no horizontal rules, -- we split using headers, determined by the slide level (see -- 'detectSlideLevel'). splitSlides :: Int -> Pandoc.Pandoc -> [Slide] splitSlides slideLevel (Pandoc.Pandoc _meta blocks0) | any (== Pandoc.HorizontalRule) blocks0 = splitAtRules blocks0 | otherwise = splitAtHeaders [] blocks0 where mkContentSlide :: [Pandoc.Block] -> [Slide] mkContentSlide [] = [] -- Never create empty slides mkContentSlide bs = [ContentSlide [Fragment bs]] splitAtRules blocks = case break (== Pandoc.HorizontalRule) blocks of (xs, []) -> mkContentSlide xs (xs, (_rule : ys)) -> mkContentSlide xs ++ splitAtRules ys splitAtHeaders acc [] = mkContentSlide (reverse acc) splitAtHeaders acc (b@(Pandoc.Header i _ _) : bs) | i > slideLevel = splitAtHeaders (b : acc) bs | i == slideLevel = mkContentSlide (reverse acc) ++ splitAtHeaders [b] bs | otherwise = mkContentSlide (reverse acc) ++ [TitleSlide b] ++ splitAtHeaders [] bs splitAtHeaders acc (b : bs) = splitAtHeaders (b : acc) bs patat-0.5.2.2/src/Patat/PrettyPrint.hs000066400000000000000000000311151312021255100174600ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- | This is a small pretty-printing library. {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} module Patat.PrettyPrint ( Doc , toString , dimensions , null , hPutDoc , putDoc , string , text , space , softline , hardline , wrapAt , Trimmable (..) , indent , ansi , (<+>) , (<$$>) , vcat -- * Exotic combinators , Alignment (..) , align , paste ) where -------------------------------------------------------------------------------- import Control.Monad.Reader (asks, local) import Control.Monad.RWS (RWS, runRWS) import Control.Monad.State (get, gets, modify) import Control.Monad.Writer (tell) import Data.Foldable (Foldable) import qualified Data.List as L import Data.Monoid (Monoid, mconcat, mempty, (<>)) import Data.String (IsString (..)) import qualified Data.Text as T import Data.Traversable (Traversable, traverse) import qualified System.Console.ANSI as Ansi import qualified System.IO as IO import Prelude hiding (null) -------------------------------------------------------------------------------- -- | A simple chunk of text. All ANSI codes are "reset" after printing. data Chunk = StringChunk [Ansi.SGR] String | NewlineChunk deriving (Eq) -------------------------------------------------------------------------------- type Chunks = [Chunk] -------------------------------------------------------------------------------- hPutChunk :: IO.Handle -> Chunk -> IO () hPutChunk h NewlineChunk = IO.hPutStrLn h "" hPutChunk h (StringChunk codes str) = do Ansi.hSetSGR h (reverse codes) IO.hPutStr h str Ansi.hSetSGR h [Ansi.Reset] -------------------------------------------------------------------------------- chunkToString :: Chunk -> String chunkToString NewlineChunk = "\n" chunkToString (StringChunk _ str) = str -------------------------------------------------------------------------------- -- | If two neighboring chunks have the same set of ANSI codes, we can group -- them together. optimizeChunks :: Chunks -> Chunks optimizeChunks (StringChunk c1 s1 : StringChunk c2 s2 : chunks) | c1 == c2 = optimizeChunks (StringChunk c1 (s1 <> s2) : chunks) | otherwise = StringChunk c1 s1 : optimizeChunks (StringChunk c2 s2 : chunks) optimizeChunks (x : chunks) = x : optimizeChunks chunks optimizeChunks [] = [] -------------------------------------------------------------------------------- chunkLines :: Chunks -> [Chunks] chunkLines chunks = case break (== NewlineChunk) chunks of (xs, _newline : ys) -> xs : chunkLines ys (xs, []) -> [xs] -------------------------------------------------------------------------------- data DocE = String String | Softspace | Hardspace | Softline | Hardline | WrapAt { wrapAtCol :: Maybe Int , wrapDoc :: Doc } | Ansi { ansiCode :: [Ansi.SGR] -> [Ansi.SGR] -- ^ Modifies current codes. , ansiDoc :: Doc } | Indent { indentFirstLine :: LineBuffer , indentOtherLines :: LineBuffer , indentDoc :: Doc } -------------------------------------------------------------------------------- chunkToDocE :: Chunk -> DocE chunkToDocE NewlineChunk = Hardline chunkToDocE (StringChunk codes str) = Ansi (\_ -> codes) (Doc [String str]) -------------------------------------------------------------------------------- newtype Doc = Doc {unDoc :: [DocE]} deriving (Monoid) -------------------------------------------------------------------------------- instance IsString Doc where fromString = string -------------------------------------------------------------------------------- instance Show Doc where show = toString -------------------------------------------------------------------------------- data DocEnv = DocEnv { deCodes :: [Ansi.SGR] -- ^ Most recent ones first in the list , deIndent :: LineBuffer -- ^ Don't need to store first-line indent , deWrap :: Maybe Int -- ^ Wrap at columns } -------------------------------------------------------------------------------- type DocM = RWS DocEnv Chunks LineBuffer -------------------------------------------------------------------------------- data Trimmable a = NotTrimmable !a | Trimmable !a deriving (Foldable, Functor, Traversable) -------------------------------------------------------------------------------- -- | Note that this is reversed so we have fast append type LineBuffer = [Trimmable Chunk] -------------------------------------------------------------------------------- bufferToChunks :: LineBuffer -> Chunks bufferToChunks = map trimmableToChunk . reverse . dropWhile isTrimmable where isTrimmable (NotTrimmable _) = False isTrimmable (Trimmable _) = True trimmableToChunk (NotTrimmable c) = c trimmableToChunk (Trimmable c) = c -------------------------------------------------------------------------------- docToChunks :: Doc -> Chunks docToChunks doc0 = let env0 = DocEnv [] [] Nothing ((), b, cs) = runRWS (go $ unDoc doc0) env0 mempty in optimizeChunks (cs <> bufferToChunks b) where go :: [DocE] -> DocM () go [] = return () go (String str : docs) = do chunk <- makeChunk str modify (NotTrimmable chunk :) go docs go (Softspace : docs) = do hard <- softConversion Softspace docs go (hard : docs) go (Hardspace : docs) = do chunk <- makeChunk " " modify (NotTrimmable chunk :) go docs go (Softline : docs) = do hard <- softConversion Softline docs go (hard : docs) go (Hardline : docs) = do buffer <- get tell $ bufferToChunks buffer <> [NewlineChunk] indentation <- asks deIndent modify $ \_ -> if L.null docs then [] else indentation go docs go (WrapAt {..} : docs) = do local (\env -> env {deWrap = wrapAtCol}) $ go (unDoc wrapDoc) go docs go (Ansi {..} : docs) = do local (\env -> env {deCodes = ansiCode (deCodes env)}) $ go (unDoc ansiDoc) go docs go (Indent {..} : docs) = do local (\env -> env {deIndent = indentOtherLines ++ deIndent env}) $ do modify (indentFirstLine ++) go (unDoc indentDoc) go docs makeChunk :: String -> DocM Chunk makeChunk str = do codes <- asks deCodes return $ StringChunk codes str -- Convert 'Softspace' or 'Softline' to 'Hardspace' or 'Hardline' softConversion :: DocE -> [DocE] -> DocM DocE softConversion soft docs = do mbWrapCol <- asks deWrap case mbWrapCol of Nothing -> return hard Just maxCol -> do -- Slow. currentLine <- gets (concatMap chunkToString . bufferToChunks) let currentCol = length currentLine case nextWordLength docs of Nothing -> return hard Just l | currentCol + 1 + l <= maxCol -> return Hardspace | otherwise -> return Hardline where hard = case soft of Softspace -> Hardspace Softline -> Hardline _ -> soft nextWordLength :: [DocE] -> Maybe Int nextWordLength [] = Nothing nextWordLength (String x : xs) | L.null x = nextWordLength xs | otherwise = Just (length x) nextWordLength (Softspace : xs) = nextWordLength xs nextWordLength (Hardspace : xs) = nextWordLength xs nextWordLength (Softline : xs) = nextWordLength xs nextWordLength (Hardline : _) = Nothing nextWordLength (WrapAt {..} : xs) = nextWordLength (unDoc wrapDoc ++ xs) nextWordLength (Ansi {..} : xs) = nextWordLength (unDoc ansiDoc ++ xs) nextWordLength (Indent {..} : xs) = nextWordLength (unDoc indentDoc ++ xs) -------------------------------------------------------------------------------- toString :: Doc -> String toString = concat . map chunkToString . docToChunks -------------------------------------------------------------------------------- -- | Returns the rows and columns necessary to render this document dimensions :: Doc -> (Int, Int) dimensions doc = let ls = lines (toString doc) in (length ls, foldr max 0 (map length ls)) -------------------------------------------------------------------------------- null :: Doc -> Bool null doc = case unDoc doc of [] -> True; _ -> False -------------------------------------------------------------------------------- hPutDoc :: IO.Handle -> Doc -> IO () hPutDoc h = mapM_ (hPutChunk h) . docToChunks -------------------------------------------------------------------------------- putDoc :: Doc -> IO () putDoc = hPutDoc IO.stdout -------------------------------------------------------------------------------- mkDoc :: DocE -> Doc mkDoc e = Doc [e] -------------------------------------------------------------------------------- string :: String -> Doc string = mkDoc . String -- TODO (jaspervdj): Newline conversion -------------------------------------------------------------------------------- text :: T.Text -> Doc text = string . T.unpack -------------------------------------------------------------------------------- space :: Doc space = mkDoc Softspace -------------------------------------------------------------------------------- softline :: Doc softline = mkDoc Softline -------------------------------------------------------------------------------- hardline :: Doc hardline = mkDoc Hardline -------------------------------------------------------------------------------- wrapAt :: Maybe Int -> Doc -> Doc wrapAt wrapAtCol wrapDoc = mkDoc WrapAt {..} -------------------------------------------------------------------------------- indent :: Trimmable Doc -> Trimmable Doc -> Doc -> Doc indent firstLineDoc otherLinesDoc doc = mkDoc $ Indent { indentFirstLine = traverse docToChunks firstLineDoc , indentOtherLines = traverse docToChunks otherLinesDoc , indentDoc = doc } -------------------------------------------------------------------------------- ansi :: [Ansi.SGR] -> Doc -> Doc ansi codes = mkDoc . Ansi (codes ++) -------------------------------------------------------------------------------- (<+>) :: Doc -> Doc -> Doc x <+> y = x <> space <> y infixr 6 <+> -------------------------------------------------------------------------------- (<$$>) :: Doc -> Doc -> Doc x <$$> y = x <> hardline <> y infixr 5 <$$> -------------------------------------------------------------------------------- vcat :: [Doc] -> Doc vcat = mconcat . L.intersperse hardline -------------------------------------------------------------------------------- data Alignment = AlignLeft | AlignCenter | AlignRight deriving (Eq, Ord, Show) -------------------------------------------------------------------------------- align :: Int -> Alignment -> Doc -> Doc align width alignment doc0 = let chunks0 = docToChunks doc0 lines_ = chunkLines chunks0 in vcat [ Doc (map chunkToDocE (alignLine line)) | line <- lines_ ] where lineWidth :: [Chunk] -> Int lineWidth = sum . map (length . chunkToString) alignLine :: [Chunk] -> [Chunk] alignLine line = let actual = lineWidth line spaces n = [StringChunk [] (replicate n ' ')] in case alignment of AlignLeft -> line <> spaces (width - actual) AlignRight -> spaces (width - actual) <> line AlignCenter -> let r = (width - actual) `div` 2 l = (width - actual) - r in spaces l <> line <> spaces r -------------------------------------------------------------------------------- -- | Like the unix program 'paste'. paste :: [Doc] -> Doc paste docs0 = let chunkss = map docToChunks docs0 :: [Chunks] cols = map chunkLines chunkss :: [[Chunks]] rows0 = L.transpose cols :: [[Chunks]] rows1 = map (map (Doc . map chunkToDocE)) rows0 :: [[Doc]] in vcat $ map mconcat rows1 patat-0.5.2.2/src/Patat/Theme.hs000066400000000000000000000263431312021255100162250ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Patat.Theme ( Theme (..) , defaultTheme , Style (..) , SyntaxHighlighting (..) , defaultSyntaxHighlighting , syntaxHighlight ) where -------------------------------------------------------------------------------- import Control.Monad (forM_, mplus) import qualified Data.Aeson as A import qualified Data.Aeson.TH.Extended as A import Data.Char (toLower, toUpper) import Data.List (intercalate, isSuffixOf) import qualified Data.Map as M import Data.Maybe (mapMaybe, maybeToList) import Data.Monoid (Monoid (..), (<>)) import qualified Data.Text as T import Prelude import qualified Skylighting as Skylighting import qualified System.Console.ANSI as Ansi import Text.Read (readMaybe) -------------------------------------------------------------------------------- data Theme = Theme { themeBorders :: !(Maybe Style) , themeHeader :: !(Maybe Style) , themeCodeBlock :: !(Maybe Style) , themeBulletList :: !(Maybe Style) , themeBulletListMarkers :: !(Maybe T.Text) , themeOrderedList :: !(Maybe Style) , themeBlockQuote :: !(Maybe Style) , themeDefinitionTerm :: !(Maybe Style) , themeDefinitionList :: !(Maybe Style) , themeTableHeader :: !(Maybe Style) , themeTableSeparator :: !(Maybe Style) , themeLineBlock :: !(Maybe Style) , themeEmph :: !(Maybe Style) , themeStrong :: !(Maybe Style) , themeCode :: !(Maybe Style) , themeLinkText :: !(Maybe Style) , themeLinkTarget :: !(Maybe Style) , themeStrikeout :: !(Maybe Style) , themeQuoted :: !(Maybe Style) , themeMath :: !(Maybe Style) , themeImageText :: !(Maybe Style) , themeImageTarget :: !(Maybe Style) , themeSyntaxHighlighting :: !(Maybe SyntaxHighlighting) } deriving (Show) -------------------------------------------------------------------------------- instance Monoid Theme where mempty = Theme Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing mappend l r = Theme { themeBorders = mplusOn themeBorders , themeHeader = mplusOn themeHeader , themeCodeBlock = mplusOn themeCodeBlock , themeBulletList = mplusOn themeBulletList , themeBulletListMarkers = mplusOn themeBulletListMarkers , themeOrderedList = mplusOn themeOrderedList , themeBlockQuote = mplusOn themeBlockQuote , themeDefinitionTerm = mplusOn themeDefinitionTerm , themeDefinitionList = mplusOn themeDefinitionList , themeTableHeader = mplusOn themeTableHeader , themeTableSeparator = mplusOn themeTableSeparator , themeLineBlock = mplusOn themeLineBlock , themeEmph = mplusOn themeEmph , themeStrong = mplusOn themeStrong , themeCode = mplusOn themeCode , themeLinkText = mplusOn themeLinkText , themeLinkTarget = mplusOn themeLinkTarget , themeStrikeout = mplusOn themeStrikeout , themeQuoted = mplusOn themeQuoted , themeMath = mplusOn themeMath , themeImageText = mplusOn themeImageText , themeImageTarget = mplusOn themeImageTarget , themeSyntaxHighlighting = mappendOn themeSyntaxHighlighting } where mplusOn f = f l `mplus` f r mappendOn f = f l `mappend` f r -------------------------------------------------------------------------------- defaultTheme :: Theme defaultTheme = Theme { themeBorders = dull Ansi.Yellow , themeHeader = dull Ansi.Blue , themeCodeBlock = dull Ansi.White <> ondull Ansi.Black , themeBulletList = dull Ansi.Magenta , themeBulletListMarkers = Just "-*" , themeOrderedList = dull Ansi.Magenta , themeBlockQuote = dull Ansi.Green , themeDefinitionTerm = dull Ansi.Blue , themeDefinitionList = dull Ansi.Magenta , themeTableHeader = dull Ansi.Blue , themeTableSeparator = dull Ansi.Magenta , themeLineBlock = dull Ansi.Magenta , themeEmph = dull Ansi.Green , themeStrong = dull Ansi.Red <> bold , themeCode = dull Ansi.White <> ondull Ansi.Black , themeLinkText = dull Ansi.Green , themeLinkTarget = dull Ansi.Cyan <> underline , themeStrikeout = ondull Ansi.Red , themeQuoted = dull Ansi.Green , themeMath = dull Ansi.Green , themeImageText = dull Ansi.Green , themeImageTarget = dull Ansi.Cyan <> underline , themeSyntaxHighlighting = Just defaultSyntaxHighlighting } where dull c = Just $ Style [Ansi.SetColor Ansi.Foreground Ansi.Dull c] ondull c = Just $ Style [Ansi.SetColor Ansi.Background Ansi.Dull c] bold = Just $ Style [Ansi.SetConsoleIntensity Ansi.BoldIntensity] underline = Just $ Style [Ansi.SetUnderlining Ansi.SingleUnderline] -------------------------------------------------------------------------------- newtype Style = Style {unStyle :: [Ansi.SGR]} deriving (Monoid, Show) -------------------------------------------------------------------------------- instance A.ToJSON Style where toJSON = A.toJSON . mapMaybe nameForSGR . unStyle -------------------------------------------------------------------------------- instance A.FromJSON Style where parseJSON val = do names <- A.parseJSON val sgrs <- mapM toSgr names return $! Style sgrs where toSgr name = case M.lookup name sgrsByName of Just sgr -> return sgr Nothing -> fail $! "Unknown style: " ++ show name ++ ". Known styles are: " ++ intercalate ", " (map show $ M.keys sgrsByName) -------------------------------------------------------------------------------- nameForSGR :: Ansi.SGR -> Maybe String nameForSGR (Ansi.SetColor layer intensity color) = Just $ (\str -> case layer of Ansi.Foreground -> str Ansi.Background -> "on" ++ capitalize str) $ (case intensity of Ansi.Dull -> "dull" Ansi.Vivid -> "vivid") ++ (case color of Ansi.Black -> "Black" Ansi.Red -> "Red" Ansi.Green -> "Green" Ansi.Yellow -> "Yellow" Ansi.Blue -> "Blue" Ansi.Magenta -> "Magenta" Ansi.Cyan -> "Cyan" Ansi.White -> "White") nameForSGR (Ansi.SetUnderlining Ansi.SingleUnderline) = Just "underline" nameForSGR (Ansi.SetConsoleIntensity Ansi.BoldIntensity) = Just "bold" nameForSGR _ = Nothing -------------------------------------------------------------------------------- sgrsByName :: M.Map String Ansi.SGR sgrsByName = M.fromList [ (name, sgr) | sgr <- knownSgrs , name <- maybeToList (nameForSGR sgr) ] where -- | It doesn't really matter if we generate "too much" SGRs here since -- 'nameForSGR' will only pick the ones we support. knownSgrs = [ Ansi.SetColor l i c | l <- [minBound .. maxBound] , i <- [minBound .. maxBound] , c <- [minBound .. maxBound] ] ++ [Ansi.SetUnderlining u | u <- [minBound .. maxBound]] ++ [Ansi.SetConsoleIntensity c | c <- [minBound .. maxBound]] -------------------------------------------------------------------------------- newtype SyntaxHighlighting = SyntaxHighlighting { unSyntaxHighlighting :: M.Map String Style } deriving (Monoid, Show, A.ToJSON) -------------------------------------------------------------------------------- instance A.FromJSON SyntaxHighlighting where parseJSON val = do styleMap <- A.parseJSON val forM_ (M.keys styleMap) $ \k -> case nameToTokenType k of Just _ -> return () Nothing -> fail $ "Unknown token type: " ++ show k return (SyntaxHighlighting styleMap) -------------------------------------------------------------------------------- defaultSyntaxHighlighting :: SyntaxHighlighting defaultSyntaxHighlighting = mkSyntaxHighlighting [ (Skylighting.KeywordTok, dull Ansi.Yellow) , (Skylighting.ControlFlowTok, dull Ansi.Yellow) , (Skylighting.DataTypeTok, dull Ansi.Green) , (Skylighting.DecValTok, dull Ansi.Red) , (Skylighting.BaseNTok, dull Ansi.Red) , (Skylighting.FloatTok, dull Ansi.Red) , (Skylighting.ConstantTok, dull Ansi.Red) , (Skylighting.CharTok, dull Ansi.Red) , (Skylighting.SpecialCharTok, dull Ansi.Red) , (Skylighting.StringTok, dull Ansi.Red) , (Skylighting.VerbatimStringTok, dull Ansi.Red) , (Skylighting.SpecialStringTok, dull Ansi.Red) , (Skylighting.CommentTok, dull Ansi.Blue) , (Skylighting.DocumentationTok, dull Ansi.Blue) , (Skylighting.AnnotationTok, dull Ansi.Blue) , (Skylighting.CommentVarTok, dull Ansi.Blue) , (Skylighting.ImportTok, dull Ansi.Cyan) , (Skylighting.OperatorTok, dull Ansi.Cyan) , (Skylighting.FunctionTok, dull Ansi.Cyan) , (Skylighting.PreprocessorTok, dull Ansi.Cyan) ] where dull c = Style [Ansi.SetColor Ansi.Foreground Ansi.Dull c] mkSyntaxHighlighting ls = SyntaxHighlighting $ M.fromList [(nameForTokenType tt, s) | (tt, s) <- ls] -------------------------------------------------------------------------------- nameForTokenType :: Skylighting.TokenType -> String nameForTokenType = unCapitalize . dropTok . show where unCapitalize (x : xs) = toLower x : xs unCapitalize xs = xs dropTok :: String -> String dropTok str | "Tok" `isSuffixOf` str = take (length str - 3) str | otherwise = str -------------------------------------------------------------------------------- nameToTokenType :: String -> Maybe Skylighting.TokenType nameToTokenType = readMaybe . capitalize . (++ "Tok") -------------------------------------------------------------------------------- capitalize :: String -> String capitalize "" = "" capitalize (x : xs) = toUpper x : xs -------------------------------------------------------------------------------- syntaxHighlight :: Theme -> Skylighting.TokenType -> Maybe Style syntaxHighlight theme tokenType = do sh <- themeSyntaxHighlighting theme M.lookup (nameForTokenType tokenType) (unSyntaxHighlighting sh) -------------------------------------------------------------------------------- $(A.deriveJSON A.dropPrefixOptions ''Theme) patat-0.5.2.2/src/Text/000077500000000000000000000000001312021255100144725ustar00rootroot00000000000000patat-0.5.2.2/src/Text/Pandoc/000077500000000000000000000000001312021255100156765ustar00rootroot00000000000000patat-0.5.2.2/src/Text/Pandoc/Extended.hs000066400000000000000000000015261312021255100177760ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE BangPatterns #-} {-# LANGUAGE LambdaCase #-} module Text.Pandoc.Extended ( module Text.Pandoc , plainToPara , newlineToSpace ) where -------------------------------------------------------------------------------- import Data.Data.Extended (grecT) import Text.Pandoc import Prelude -------------------------------------------------------------------------------- plainToPara :: [Block] -> [Block] plainToPara = map $ \case Plain inlines -> Para inlines block -> block -------------------------------------------------------------------------------- newlineToSpace :: [Inline] -> [Inline] newlineToSpace = grecT $ \case SoftBreak -> Space LineBreak -> Space inline -> inline patat-0.5.2.2/stack.yaml000066400000000000000000000002371312021255100147520ustar00rootroot00000000000000resolver: nightly-2017-06-14 install-ghc: true packages: - '.' flags: {} extra-package-dbs: [] extra-deps: - 'aeson-1.2.0.0' - 'optparse-applicative-0.14.0.0' patat-0.5.2.2/test.sh000066400000000000000000000013141312021255100142710ustar00rootroot00000000000000#!/bin/bash set -o nounset -o errexit -o pipefail srcs=$(find tests -type f ! -name '*.dump') stuff_went_wrong=false for src in $srcs; do expected="$src.dump" echo -n "Testing $src... " actual=$(mktemp) patat --dump --force "$src" >"$actual" if [[ $@ == "--fix" ]]; then cp "$actual" "$expected" echo 'Fixed' elif [[ ! -f "$expected" ]]; then echo "missing file: $expected" stuff_went_wrong=true elif [[ "$(cat "$expected")" == "$(cat "$actual")" ]]; then echo 'OK' else echo 'files differ' diff "$actual" "$expected" || true stuff_went_wrong=true fi done if [[ "$stuff_went_wrong" = true ]]; then exit 1 fi patat-0.5.2.2/tests/000077500000000000000000000000001312021255100141215ustar00rootroot00000000000000patat-0.5.2.2/tests/01.md000066400000000000000000000002131312021255100146570ustar00rootroot00000000000000--- title: This is my presentation author: Jasper Van der Jeugt ... # This is a test Hello world --- # This is a second slide lololol patat-0.5.2.2/tests/01.md.dump000066400000000000000000000001631312021255100156270ustar00rootroot00000000000000# This is a test Hello world ---------- # This is a second slide lololol patat-0.5.2.2/tests/02.lhs000066400000000000000000000001541312021255100150520ustar00rootroot00000000000000This is how to do _Hello World_ in Haskell: > main :: IO () > main = putStrLn "Hello World!" Cool, right? patat-0.5.2.2/tests/02.lhs.dump000066400000000000000000000006041312021255100160160ustar00rootroot00000000000000This is how to do Hello World in Haskell:      main :: IO ()    main = putStrLn "Hello World!"     Cool, right? patat-0.5.2.2/tests/03.md000066400000000000000000000007141312021255100146670ustar00rootroot00000000000000Inline markups: - ~~striked out~~ - --- > Some quote > Quote with embedded list: > > - Hello > - World --- - List with an embedded quote: > Tu quoque Wow rad stuff. - Second item in that list. --- Code with empty line: puts "wow" puts "amaze" --- Code in ordered list: 1. Do you know the coolest codes? It's this: fire_missiles() cancel() Great 2. Also `fib` is pretty cool yeah patat-0.5.2.2/tests/03.md.dump000066400000000000000000000021411312021255100156270ustar00rootroot00000000000000Inline markups:  - ~~striked out~~  - <http://example.com> ---------- > Some quote > Quote with embedded list: >  >  - Hello >  - World ----------  - List with an embedded quote:  > Tu quoque  Wow rad stuff.  - Second item in that list. ---------- Code with empty line:      puts "wow"       puts "amaze"     ---------- Code in ordered list: 1. Do you know the coolest codes?  It's this:      fire_missiles()    cancel()      Great 2. Also  fib  is pretty cool yeah patat-0.5.2.2/tests/deflist.md000066400000000000000000000003451312021255100160770ustar00rootroot00000000000000Term 1 : Definition 1 Term 2 with *inline markup* : Definition 2 { some code, part of Definition 2 } Third paragraph of definition 2. --- Term 1 ~ Definition 1 Term 2 ~ Definition 2a ~ Definition 2b patat-0.5.2.2/tests/deflist.md.dump000066400000000000000000000010161312021255100170370ustar00rootroot00000000000000Term 1 : Definition 1 Term 2 with inline markup : Definition 2      { some code, part of Definition 2 }      Third paragraph of definition 2. ---------- Term 1 : Definition 1 Term 2 : Definition 2a : Definition 2b patat-0.5.2.2/tests/fragments.md000066400000000000000000000002711312021255100164310ustar00rootroot00000000000000--- patat: incrementalLists: true ... - This list - is displayed * item * by item - Or sometimes > * all at > * once --- Legen . . . wait for it . . . Dary! patat-0.5.2.2/tests/fragments.md.dump000066400000000000000000000014531312021255100174000ustar00rootroot00000000000000 ~~~frag  - This list ~~~frag  - This list  - is displayed ~~~frag  - This list  - is displayed   * item ~~~frag  - This list  - is displayed   * item   * by item ~~~frag  - This list  - is displayed   * item   * by item  - Or sometimes   * all at   * once ---------- Legen ~~~frag Legen wait for it ~~~frag Legen wait for it Dary! patat-0.5.2.2/tests/headers.md000066400000000000000000000002661312021255100160620ustar00rootroot00000000000000# This could be a title ## This is nested Here is some content ## This is also nested Here is more content # Another topic ## What is going on? I think we can display slides? patat-0.5.2.2/tests/headers.md.dump000066400000000000000000000005401312021255100170210ustar00rootroot00000000000000~~~title # This could be a title ---------- ## This is nested Here is some content ---------- ## This is also nested Here is more content ---------- ~~~title # Another topic ---------- ## What is going on? I think we can display slides? patat-0.5.2.2/tests/links.md000066400000000000000000000003401312021255100155600ustar00rootroot00000000000000This is an "automatic link": . This is an [inline link](/url), and here's [one with a title](http://fsf.org "click here for a good time!"). Let's talk about [foo][foosite] [foosite]: http://foo.com/ patat-0.5.2.2/tests/links.md.dump000066400000000000000000000007161312021255100165330ustar00rootroot00000000000000This is an "automatic link": <https://jaspervdj.be>. This is an [inline link], and here's [one with a title]. Let's talk about [foo] [inline link](/url) [one with a title](http://fsf.org "click here for a good time!") [foo](http://foo.com/)patat-0.5.2.2/tests/lists.md000066400000000000000000000004001312021255100155730ustar00rootroot00000000000000- This is a nested list. * The nested items should have different list markers. * I mean, they can be the same, but it doesn't look nice. printf("Nested code block!\n") * Cool right? Definitely super cool - One final item patat-0.5.2.2/tests/lists.md.dump000066400000000000000000000005771312021255100165560ustar00rootroot00000000000000 - This is a nested list.   * The nested items should have different list markers.   * I mean, they can be the same, but it doesn't look nice.  printf("Nested code block!\n")   * Cool right?  Definitely super cool  - One final item patat-0.5.2.2/tests/meta.md000066400000000000000000000001651312021255100153730ustar00rootroot00000000000000--- patat: theme: bulletListMarkers: '<>' ... - Hello - World * How * Are * You * Doing patat-0.5.2.2/tests/meta.md.dump000066400000000000000000000003031312021255100163310ustar00rootroot00000000000000 < Hello  < World   > How   > Are   > You   > Doing patat-0.5.2.2/tests/slidelevel0.md000066400000000000000000000001651312021255100166550ustar00rootroot00000000000000--- patat: slideLevel: 0 --- # We should not split slides Never # At all Because we have `slideLevel` set to 0 patat-0.5.2.2/tests/slidelevel0.md.dump000066400000000000000000000002101312021255100176100ustar00rootroot00000000000000# We should not split slides Never # At all Because we have  slideLevel  set to 0 patat-0.5.2.2/tests/slidelevel1.md000066400000000000000000000004671312021255100166630ustar00rootroot00000000000000--- patat: slideLevel: 1 --- # This starts a new slide ## But this does not Here is some content ## And another header And more content (yep) # This should start a new slide ## With some content ### Very deeply nested #### Is a hidden message ##### A dark secret... jet fuel can't melt steel beams patat-0.5.2.2/tests/slidelevel1.md.dump000066400000000000000000000006061312021255100176220ustar00rootroot00000000000000# This starts a new slide ## But this does not Here is some content ## And another header And more content (yep) ---------- # This should start a new slide ## With some content ### Very deeply nested #### Is a hidden message ##### A dark secret... jet fuel can't melt steel beams patat-0.5.2.2/tests/slidelevel2.md000066400000000000000000000002361312021255100166560ustar00rootroot00000000000000# This is a title ## This is a slide Here is some content ## And another slide And more content (yep) # This is another title ## With some content Yay patat-0.5.2.2/tests/slidelevel2.md.dump000066400000000000000000000005101312021255100176150ustar00rootroot00000000000000~~~title # This is a title ---------- ## This is a slide Here is some content ---------- ## And another slide And more content (yep) ---------- ~~~title # This is another title ---------- ## With some content Yay patat-0.5.2.2/tests/syntax.md000066400000000000000000000002371312021255100157730ustar00rootroot00000000000000--- patat: theme: syntaxHighlighting: decVal: [bold, onDullRed] ... Some simple code: ```c int main(int argc, char **argv) { return 0; } ``` patat-0.5.2.2/tests/syntax.md.dump000066400000000000000000000007051312021255100167370ustar00rootroot00000000000000Some simple code:      int main(int argc, char **argv) {    return 0;    }     patat-0.5.2.2/tests/tables.md000066400000000000000000000030611312021255100157150ustar00rootroot00000000000000# Normal simple table Right Left Center Default ------- ------ ---------- ------- 12 12 12 12 123 123 123 123 1 1 1 1 Table: Demonstration of simple table syntax. # Headerless table ------- ------ ---------- ------- 12 12 12 12 123 123 123 123 1 1 1 1 ------- ------ ---------- ------- # Multiline ------------------------------------------------------------- Centered Default Right Left Header Aligned Aligned Aligned ----------- ------- --------------- ------------------------- First row 12.0 Example of a row that spans multiple lines. Second row 5.0 Here's another one. Note the blank line between rows. ------------------------------------------------------------- Table: Here's the caption. It, too, may span multiple lines. # Headerless multiline ----------- ------- --------------- ------------------------- First row 12.0 Example of a row that spans multiple lines. Second row 5.0 Here's another one. Note the blank line between rows. ----------- ------- --------------- ------------------------- : Here's a multiline table without headers. patat-0.5.2.2/tests/tables.md.dump000066400000000000000000000036061312021255100166660ustar00rootroot00000000000000# Normal simple table  Right Left Center Default  ----- ---- ------ -------  12 12 12 12   123 123 123 123   1 1 1 1   Table: Demonstration of simple table syntax. ---------- # Headerless table  --- --- --- ---  12 12 12 12  123 123 123 123  1 1 1 1   --- --- --- --- ---------- # Multiline  Centered Default Right Left   Header Aligned Aligned Aligned   -------- ------- ------- ------------------------  First row 12.0 Example of a row that   spans multiple lines.     Second row 5.0 Here's another one. Note  the blank line between   rows.   Table: Here's the caption. It, too, may span  multiple lines. ---------- # Headerless multiline  ------ --- ---- ------------------------  First row 12.0 Example of a row that   spans multiple lines.     Second row 5.0 Here's another one. Note  the blank line between   rows.   ------ --- ---- ------------------------  Table: Here's a multiline table without headers. patat-0.5.2.2/tests/themes.md000066400000000000000000000003051312021255100157260ustar00rootroot00000000000000--- patat: theme: bulletListMarkers: '-+' emph: [onVividRed, underline] ... - This is a simple list. * With _nested_ items. * One or two. - The list theming is customized a bit. patat-0.5.2.2/tests/themes.md.dump000066400000000000000000000003261312021255100166750ustar00rootroot00000000000000 - This is a simple list.   + With nested items.   + One or two.  - The list theming is customized a bit. patat-0.5.2.2/tests/wrapping.md000066400000000000000000000011061312021255100162700ustar00rootroot00000000000000--- patat: wrap: true columns: 40 ... This is a long sentence over multiple lines which can be re-wrapped. This is a super long sentence over a single line which should also be re-wrapped. This is a table and tables should not be wrapped ------- ------- ---------- ---------- ---------- 1 2 3 4 5 6 7 8 9 10 - This is a list - This list has a really long sentence in it which should also be wrapped with proper indentation - Another item This line is long, and then ends with `code` patat-0.5.2.2/tests/wrapping.md.dump000066400000000000000000000014151312021255100172370ustar00rootroot00000000000000This is a long sentence over multiple lines which can be re-wrapped. This is a super long sentence over a single line which should also be re-wrapped.  This is a table and tables should not be wrapped  ------- ------- ---------- ---------- ----------  1 2 3 4 5   6 7 8 9 10   - This is a list  - This list has a really long sentence  in it which should also be wrapped  with proper indentation  - Another item This line is long, and then ends with  code