packages feed

patat (empty) → 0.1.0.0

raw patch · 14 files changed

+1519/−0 lines, 14 filesdep +ansi-terminaldep +ansi-wl-pprintdep +basesetup-changed

Dependencies added: ansi-terminal, ansi-wl-pprint, base, containers, directory, filepath, mtl, optparse-applicative, pandoc, terminal-size, time

Files

+ CHANGELOG.md view
@@ -0,0 +1,4 @@+# Changelog++- 0.1.0.0 (2016-10-02)+    * Upload first version from hotel wifi in Kalaw.
+ LICENSE view
@@ -0,0 +1,339 @@+             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.++    <one line to give the program's name and a brief idea of what it does.>+    Copyright (C) <year>  <name of author>++    This program is free software; you can redistribute it and/or modify+    it under the terms of the GNU 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.++  <signature of Ty Coon>, 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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ patat.cabal view
@@ -0,0 +1,48 @@+Name:                patat+Version:             0.1.0.0+Synopsis:            Terminal-based presentations using Pandoc+Description:         Terminal-based presentations using Pandoc+License:             GPL-2+License-file:        LICENSE+Author:              Jasper Van der Jeugt <m@jaspervdj.be>+Maintainer:          Jasper Van der Jeugt <m@jaspervdj.be>+Homepage:            http://github.com/jaspervdj/patat+Copyright:           2016 Jasper Van der Jeugt+Category:            Text+Build-type:          Simple+Extra-source-files:  CHANGELOG.md+Cabal-version:       >=1.10++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:+    ansi-terminal        >= 0.6  && < 0.7,+    ansi-wl-pprint       >= 0.6  && < 0.7,+    base                 >= 4.6  && < 4.10,+    containers           >= 0.5  && < 0.6,+    directory            >= 1.2  && < 1.3,+    filepath             >= 1.4  && < 1.5,+    mtl                  >= 2.2  && < 2.3,+    optparse-applicative >= 0.12 && < 0.14,+    pandoc               >= 1.17 && < 1.18,+    terminal-size        >= 0.3  && < 0.4,+    time                 >= 1.4  && < 1.7++  Other-modules:+    Data.Data.Extended+    Patat.Presentation+    Patat.Presentation.Display+    Patat.Presentation.Display.Table+    Patat.Presentation.Interactive+    Patat.Presentation.Internal+    Patat.Presentation.Read+    Patat.PrettyPrint+    Text.Pandoc.Extended
+ src/Data/Data/Extended.hs view
@@ -0,0 +1,23 @@+module 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
+ src/Main.hs view
@@ -0,0 +1,137 @@+--------------------------------------------------------------------------------+{-# 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.Monad                (forever, unless, when)+import           Data.Monoid                  ((<>))+import           Data.Version                 (showVersion)+import qualified Options.Applicative          as OA+import           Patat.Presentation+import qualified Paths_patat+import qualified System.Console.ANSI          as Ansi+import           System.Directory             (getModificationTime)+import           System.Exit                  (exitFailure)+import qualified System.IO                    as IO+import qualified Text.PrettyPrint.ANSI.Leijen as PP+import           Prelude+++--------------------------------------------------------------------------------+data Options = Options+    { oFilePath :: !FilePath+    , oForce    :: !Bool+    , oDump     :: !Bool+    , oWatch    :: !Bool+    } deriving (Show)+++--------------------------------------------------------------------------------+parseOptions :: OA.Parser Options+parseOptions = Options+    <$> (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")+++--------------------------------------------------------------------------------+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"+        , "- Previous slide:         backspace, h, left"+        , "- Go forward 10 slides:   j, down"+        , "- Go backward 10 slides:  k, up"+        , "- First slide:            0"+        , "- Last slide:             G"+        , "- Reload file:            r"+        , "- Quit:                   q"+        ]+++--------------------------------------------------------------------------------+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.execParser parserInfo+    errOrPres <- readPresentation (oFilePath options)+    pres      <- either (errorAndExit . return) return errOrPres++    unless (oForce options) assertAnsiFeatures++    if oDump options+        then dumpPresentation pres+        else interactiveLoop options pres++  where+    interactiveLoop options pres0 = do+        IO.hSetBuffering IO.stdin IO.NoBuffering+        commandChan <- Chan.newChan++        _ <- forkIO $ forever $+            readPresentationCommand >>= Chan.writeChan commandChan++        mtime0 <- getModificationTime (pFilePath pres0)+        let watcher mtime = do+                mtime' <- getModificationTime (pFilePath pres0)+                when (mtime' > mtime) $ Chan.writeChan commandChan Reload+                threadDelay (200 * 1000)+                watcher mtime'++        when (oWatch options) $ do+            _ <- forkIO $ watcher mtime0+            return ()++        let loop pres = do+                displayPresentation pres+                c      <- Chan.readChan commandChan+                update <- updatePresentation c pres+                case update of+                    ExitedPresentation        -> return ()+                    UpdatedPresentation pres' -> loop pres'+                    ErroredPresentation err   -> errorAndExit [err]++        loop pres0
+ src/Patat/Presentation.hs view
@@ -0,0 +1,16 @@+module Patat.Presentation+    ( Presentation (..)+    , readPresentation+    , displayPresentation+    , dumpPresentation++    , PresentationCommand (..)+    , readPresentationCommand+    , UpdatedPresentation (..)+    , updatePresentation+    ) where++import           Patat.Presentation.Display+import           Patat.Presentation.Interactive+import           Patat.Presentation.Internal+import           Patat.Presentation.Read
+ src/Patat/Presentation/Display.hs view
@@ -0,0 +1,243 @@+--------------------------------------------------------------------------------+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE RecordWildCards            #-}+module Patat.Presentation.Display+    ( displayPresentation+    , dumpPresentation+    ) where+++--------------------------------------------------------------------------------+import           Data.Data.Extended               (grecQ)+import           Data.List                        (intersperse)+import           Data.Monoid                      (mconcat, mempty, (<>))+import           Patat.Presentation.Display.Table+import           Patat.Presentation.Internal+import           Patat.PrettyPrint                ((<$$>), (<+>))+import qualified Patat.PrettyPrint                as PP+import qualified System.Console.ANSI              as Ansi+import qualified System.Console.Terminal.Size     as Terminal+import qualified Text.Pandoc.Extended             as Pandoc+import           Prelude+++--------------------------------------------------------------------------------+displayPresentation :: Presentation -> IO ()+displayPresentation Presentation {..} = do+    Ansi.clearScreen+    Ansi.setCursorPosition 0 0++    -- Get terminal width/title+    mbWindow <- Terminal.size+    let termWidth   = maybe 72 Terminal.width  mbWindow+        termHeight  = maybe 24 Terminal.height mbWindow+        title       = PP.toString (prettyInlines pTitle)+        titleWidth  = length title+        titleOffset = (termWidth - titleWidth) `div` 2++    Ansi.setCursorColumn titleOffset+    PP.putDoc $ PP.dullyellow $ PP.string title+    putStrLn ""+    putStrLn ""++    let slide = case drop pActiveSlide pSlides of+            []      -> mempty+            (s : _) -> s++    PP.putDoc $ prettySlide slide+    putStrLn ""++    let active      = show (pActiveSlide + 1) ++ " / " ++ show (length pSlides)+        activeWidth = length active++    Ansi.setCursorPosition (termHeight - 2) 0+    PP.putDoc $ " " <> PP.dullyellow (prettyInlines pAuthor)+    Ansi.setCursorColumn (termWidth - activeWidth - 1)+    PP.putDoc $ PP.dullyellow $ PP.string active+    putStrLn ""+++--------------------------------------------------------------------------------+dumpPresentation :: Presentation -> IO ()+dumpPresentation =+    PP.putDoc . PP.vcat . intersperse "----------" . map prettySlide . pSlides+++--------------------------------------------------------------------------------+prettySlide :: Slide -> PP.Doc+prettySlide slide@(Slide blocks) =+    prettyBlocks blocks <>+    case prettyReferences slide of+        []   -> mempty+        refs -> PP.newline <> PP.vcat refs+++--------------------------------------------------------------------------------+prettyBlock :: Pandoc.Block -> PP.Doc++prettyBlock (Pandoc.Plain inlines) = prettyInlines inlines++prettyBlock (Pandoc.Para inlines) = prettyInlines inlines <> PP.newline++prettyBlock (Pandoc.Header i _ inlines) =+    PP.dullblue (PP.string (replicate i '#') <+> prettyInlines inlines) <>+    PP.newline++prettyBlock (Pandoc.CodeBlock _ txt) = PP.vcat+    [ let ind = PP.NotTrimmable "   " in+      PP.indent ind ind $ PP.ondullblack $ PP.dullwhite $ PP.string line+    | line <- blockified txt+    ] <> PP.newline+  where+    blockified str =+        let ls       = lines str+            longest  = foldr max 0 (map length ls)+            extend l = " " ++ l ++ replicate (longest - length l) ' ' ++ " " in+        map extend $ [""] ++ ls ++ [""]++prettyBlock (Pandoc.BulletList bss) = PP.vcat+    [ PP.indent+        (PP.NotTrimmable $ PP.dullmagenta "  - ")+        (PP.Trimmable "    ")+        (prettyBlocks bs)+    | bs <- bss+    ] <> PP.newline++prettyBlock (Pandoc.OrderedList _ bss) = PP.vcat+    [ PP.indent+        (PP.NotTrimmable $ PP.dullmagenta $ PP.string prefix)+        (PP.Trimmable "    ")+        (prettyBlocks bs)+    | (prefix, bs) <- zip padded bss+    ] <> PP.newline+  where+    padded  = [n ++ replicate (4 - length n) ' ' | n <- numbers]+    numbers =+        [ show i ++ "."+        | i <- [1 .. length bss]+        ]++prettyBlock (Pandoc.RawBlock _ t) = PP.string t <> PP.newline++prettyBlock Pandoc.HorizontalRule = "---"++prettyBlock (Pandoc.BlockQuote bs) =+    let quote = PP.NotTrimmable (PP.dullgreen "> ") in+    PP.indent quote quote (prettyBlocks bs)++prettyBlock (Pandoc.Table caption aligns _ headers rows) = prettyTable Table+    { tCaption = prettyInlines caption+    , tAligns  = map align aligns+    , tHeaders = map prettyBlocks headers+    , tRows    = map (map prettyBlocks) rows+    }+  where+    align Pandoc.AlignLeft    = PP.AlignLeft+    align Pandoc.AlignCenter  = PP.AlignCenter+    align Pandoc.AlignDefault = PP.AlignLeft+    align Pandoc.AlignRight   = PP.AlignRight++prettyBlock (Pandoc.Div _attrs blocks) = prettyBlocks blocks++prettyBlock (Pandoc.DefinitionList terms) =+    PP.vcat $ map prettyDefinition terms+  where+    prettyDefinition (term, definitions) =+        PP.dullblue (prettyInlines term) <$$> PP.newline <> PP.vcat+        [ PP.indent+            (PP.NotTrimmable (PP.dullmagenta ":   "))+            (PP.Trimmable "    ") $+            prettyBlocks (Pandoc.plainToPara definition)+        | definition <- definitions+        ]++prettyBlock Pandoc.Null = mempty+++--------------------------------------------------------------------------------+prettyBlocks :: [Pandoc.Block] -> PP.Doc+prettyBlocks = PP.vcat . map prettyBlock+++--------------------------------------------------------------------------------+prettyInline :: Pandoc.Inline -> PP.Doc++prettyInline Pandoc.Space = PP.space++prettyInline (Pandoc.Str str) = PP.string str++prettyInline (Pandoc.Emph inlines) =+    PP.dullgreen $ prettyInlines inlines++prettyInline (Pandoc.Strong inlines) =+    PP.dullred $ PP.bold $ prettyInlines inlines++prettyInline (Pandoc.Code _ txt) =+    PP.ondullblack $ PP.dullwhite $ " " <> PP.string txt <> " "++prettyInline link@(Pandoc.Link _attrs text (target, _title))+    | isReferenceLink link =+        "[" <> PP.dullcyan (prettyInlines text) <> "]"+    | otherwise =+        "<" <> PP.dullcyan (PP.underline $ PP.string target) <> ">"++prettyInline Pandoc.SoftBreak = PP.newline++prettyInline Pandoc.LineBreak = PP.newline++prettyInline (Pandoc.Strikeout t) =+    "~~" <> PP.ondullred (prettyInlines t) <> "~~"++prettyInline (Pandoc.Quoted Pandoc.SingleQuote t) =+    "'" <> PP.dullgreen (prettyInlines t) <> "'"+prettyInline (Pandoc.Quoted Pandoc.DoubleQuote t) =+    "'" <> PP.dullgreen (prettyInlines t) <> "'"++prettyInline (Pandoc.Math _ t) = PP.dullgreen (PP.string t)++prettyInline (Pandoc.Image _ _ (tit, src)) =+    "![" <> PP.dullgreen (PP.string tit) <> "](" <>+    PP.dullcyan (PP.underline (PP.string src)) <> ")"++-- These elements aren't really supported.+prettyInline (Pandoc.Cite      _ t) = prettyInlines t+prettyInline (Pandoc.Span      _ t) = prettyInlines t+prettyInline (Pandoc.RawInline _ t) = PP.string t+prettyInline (Pandoc.Note        t) = prettyBlocks t+prettyInline (Pandoc.Superscript t) = prettyInlines t+prettyInline (Pandoc.Subscript   t) = prettyInlines t+prettyInline (Pandoc.SmallCaps   t) = prettyInlines t+-- prettyInline unsupported = PP.ondullred $ PP.string $ show unsupported+++--------------------------------------------------------------------------------+prettyInlines :: [Pandoc.Inline] -> PP.Doc+prettyInlines = mconcat . map prettyInline+++--------------------------------------------------------------------------------+prettyReferences :: Slide -> [PP.Doc]+prettyReferences =+    map prettyReference . getReferences . unSlide+  where+    getReferences :: [Pandoc.Block] -> [Pandoc.Inline]+    getReferences = filter isReferenceLink . grecQ++    prettyReference :: Pandoc.Inline -> PP.Doc+    prettyReference (Pandoc.Link _attrs text (target, title)) =+        "[" <> PP.dullgreen (prettyInlines $ Pandoc.newlineToSpace text) <>+        "](" <>+        PP.dullcyan (PP.underline (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
+ src/Patat/Presentation/Display/Table.hs view
@@ -0,0 +1,93 @@+--------------------------------------------------------------------------------+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}+module Patat.Presentation.Display.Table+    ( Table (..)+    , prettyTable+    ) where+++--------------------------------------------------------------------------------+import           Data.List         (intersperse, transpose)+import           Data.Monoid       (mconcat, mempty, (<>))+import           Patat.PrettyPrint ((<$$>))+import qualified Patat.PrettyPrint as PP+import           Prelude+++--------------------------------------------------------------------------------+data Table = Table+    { tCaption :: PP.Doc+    , tAligns  :: [PP.Alignment]+    , tHeaders :: [PP.Doc]+    , tRows    :: [[PP.Doc]]+    }+++--------------------------------------------------------------------------------+prettyTable+    :: Table -> PP.Doc+prettyTable Table {..} = PP.indent (PP.Trimmable "  ") (PP.Trimmable "  ") $+    lineIf (not isHeaderLess) (hcat2 headerHeight+        [ PP.dullblue (PP.align w a (vpad headerHeight header))+        | (w, a, header) <- zip3 columnWidths tAligns tHeaders+        ]) <>+    dashedHeaderSeparator 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 columnWidths) <>+    lineIf+        (not $ PP.null tCaption) (PP.newline <> "Table: " <> tCaption)+  where+    lineIf cond line = if cond then line <> PP.newline 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.newline)++    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.newline $+        replicate rowHeight (PP.string "  ")+++--------------------------------------------------------------------------------+isSimpleCell :: PP.Doc -> Bool+isSimpleCell = (<= 1) . fst . PP.dimensions+++--------------------------------------------------------------------------------+dashedHeaderSeparator :: [Int] -> PP.Doc+dashedHeaderSeparator columnWidths = mconcat $ intersperse (PP.string "  ")+    [ PP.dullmagenta (PP.string (replicate w '-'))+    | w <- columnWidths+    ]
+ src/Patat/Presentation/Interactive.hs view
@@ -0,0 +1,100 @@+--------------------------------------------------------------------------------+-- | 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+++--------------------------------------------------------------------------------+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+        "\ESC[C" -> return Forward+        "\ESC[D" -> return Backward+        "\ESC[B" -> return SkipForward+        "\ESC[A" -> return SkipBackward+        "0"      -> return First+        "G"      -> return Last+        "r"      -> return Reload+        _        -> readPresentationCommand+  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 (\x -> x + 1)+    Backward     -> return $ goToSlide (\x -> x - 1)+    SkipForward  -> return $ goToSlide (\x -> x + 10)+    SkipBackward -> return $ goToSlide (\x -> x - 10)+    First        -> return $ goToSlide (\_ -> 0)+    Last         -> return $ goToSlide (\_ -> numSlides - 1)+    Reload       -> reloadPresentation+  where+    numSlides = length (pSlides presentation)+    clip idx  = min (max 0 idx) (numSlides - 1)++    goToSlide f = UpdatedPresentation $+        presentation {pActiveSlide = clip (f $ pActiveSlide presentation)}++    reloadPresentation = do+        errOrPres <- readPresentation (pFilePath presentation)+        return $ case errOrPres of+            Left  err  -> ErroredPresentation err+            Right pres -> UpdatedPresentation $+                pres {pActiveSlide = clip (pActiveSlide presentation)}
+ src/Patat/Presentation/Internal.hs view
@@ -0,0 +1,27 @@+--------------------------------------------------------------------------------+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Patat.Presentation.Internal+    ( Presentation (..)+    , Slide (..)+    ) where+++--------------------------------------------------------------------------------+import           Data.Monoid (Monoid)+import qualified Text.Pandoc as Pandoc+import           Prelude+++--------------------------------------------------------------------------------+data Presentation = Presentation+    { pFilePath    :: !FilePath+    , pTitle       :: ![Pandoc.Inline]+    , pAuthor      :: ![Pandoc.Inline]+    , pSlides      :: [Slide]+    , pActiveSlide :: !Int+    } deriving (Show)+++--------------------------------------------------------------------------------+newtype Slide = Slide {unSlide :: [Pandoc.Block]}+    deriving (Monoid, Show)
+ src/Patat/Presentation/Read.hs view
@@ -0,0 +1,80 @@+-- | Read a presentation from disk.+{-# LANGUAGE RecordWildCards #-}+module Patat.Presentation.Read+    ( readPresentation+    ) where+++--------------------------------------------------------------------------------+import qualified Data.Set                    as Set+import           Patat.Presentation.Internal+import           System.FilePath             (takeExtension)+import qualified Text.Pandoc                 as Pandoc+import qualified Text.Pandoc.Error           as Pandoc+++--------------------------------------------------------------------------------+readPresentation :: FilePath -> IO (Either String Presentation)+readPresentation filePath = do+    src <- readFile filePath+    return $ do+        reader <- case readExtension ext of+            Nothing -> Left $ "Unknown extension: " ++ ext+            Just r  -> Right r++        doc <- case reader src of+            Left err -> Left $ "Pandoc parsing error: " ++ show err+            Right x  -> Right x++        pandocToPresentation filePath 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+    _      -> Nothing++  where+    lhsOpts = Pandoc.def+        { Pandoc.readerExtensions = Set.insert Pandoc.Ext_literate_haskell+            (Pandoc.readerExtensions Pandoc.def)+        }+++--------------------------------------------------------------------------------+pandocToPresentation+    :: FilePath -> Pandoc.Pandoc -> Either String Presentation+pandocToPresentation pFilePath pandoc@(Pandoc.Pandoc meta _) = do+    let pTitle       = Pandoc.docTitle meta+        pSlides      = pandocToSlides pandoc+        pActiveSlide = 0+        pAuthor      = concat (Pandoc.docAuthors meta)+    return Presentation {..}+++--------------------------------------------------------------------------------+-- | 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 h1 headers.+pandocToSlides :: Pandoc.Pandoc -> [Slide]+pandocToSlides (Pandoc.Pandoc _meta blocks0)+    | any (== Pandoc.HorizontalRule) blocks0 = splitAtRules blocks0+    | otherwise                              = splitAtH1s   blocks0+  where+    splitAtRules blocks = case break (== Pandoc.HorizontalRule) blocks of+        (xs, [])           -> [Slide xs]+        (xs, (_rule : ys)) -> Slide xs : splitAtRules ys++    splitAtH1s []       = []+    splitAtH1s (b : bs) = case break isH1 bs of+        (xs, [])       -> [Slide (b : xs)]+        (xs, (y : ys)) -> Slide (b : xs) : splitAtH1s (y : ys)++    isH1 (Pandoc.Header i _ _) = i == 1+    isH1 _                     = False
+ src/Patat/PrettyPrint.hs view
@@ -0,0 +1,386 @@+--------------------------------------------------------------------------------+-- | 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+    , space+    , newline++    , Trimmable (..)+    , indent++    , (<+>)+    , (<$$>)+    , vcat++    , bold+    , underline++    , dullblack+    , dullred+    , dullgreen+    , dullyellow+    , dullblue+    , dullmagenta+    , dullcyan+    , dullwhite++    , ondullblack+    , ondullred++    -- * Exotic combinators+    , Alignment (..)+    , align+    , paste+    ) where+++--------------------------------------------------------------------------------+import           Control.Monad.Reader (asks, local)+import           Control.Monad.RWS    (RWS, runRWS)+import           Control.Monad.State  (get, 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           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+    | Space+    | Newline+    | Ansi+        { ansiCode :: [Ansi.SGR] -> [Ansi.SGR]  -- ^ Modifies current codes.+        , ansiDoc  :: Doc+        }+    | Indent+        { indentFirstLine  :: LineBuffer+        , indentOtherLines :: LineBuffer+        , indentDoc        :: Doc+        }+++--------------------------------------------------------------------------------+chunkToDocE :: Chunk -> DocE+chunkToDocE NewlineChunk            = Newline+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+    }+++--------------------------------------------------------------------------------+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 [] []+        ((), 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 (Space : docs) = do+        chunk <- makeChunk " "+        modify (NotTrimmable chunk :)+        go docs++    go (Newline : docs) = do+        buffer <- get+        tell $ bufferToChunks buffer <> [NewlineChunk]+        indentation <- asks deIndent+        modify $ \_ -> if L.null docs then [] else indentation+        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+++--------------------------------------------------------------------------------+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+++--------------------------------------------------------------------------------+space :: Doc+space = mkDoc Space+++--------------------------------------------------------------------------------+newline :: Doc+newline = mkDoc Newline+++--------------------------------------------------------------------------------+indent :: Trimmable Doc -> Trimmable Doc -> Doc -> Doc+indent firstLineDoc otherLinesDoc doc = mkDoc $ Indent+    { indentFirstLine  = traverse docToChunks firstLineDoc+    , indentOtherLines = traverse docToChunks otherLinesDoc+    , indentDoc        = doc+    }+++--------------------------------------------------------------------------------+(<+>) :: Doc -> Doc -> Doc+x <+> y = x <> space <> y+infixr 6 <+>+++--------------------------------------------------------------------------------+(<$$>) :: Doc -> Doc -> Doc+x <$$> y = x <> newline <> y+infixr 5 <$$>+++--------------------------------------------------------------------------------+vcat :: [Doc] -> Doc+vcat = mconcat . L.intersperse newline+++--------------------------------------------------------------------------------+bold :: Doc -> Doc+bold = mkDoc . Ansi+    (\codes -> Ansi.SetConsoleIntensity Ansi.BoldIntensity : codes)+++--------------------------------------------------------------------------------+underline :: Doc -> Doc+underline = mkDoc . Ansi+    (\codes -> Ansi.SetUnderlining Ansi.SingleUnderline : codes)+++--------------------------------------------------------------------------------+dullcolor :: Ansi.Color -> Doc -> Doc+dullcolor c = mkDoc . Ansi+    (\codes -> Ansi.SetColor Ansi.Foreground Ansi.Dull c : codes)+++--------------------------------------------------------------------------------+dullblack, dullred, dullgreen, dullyellow, dullblue, dullmagenta, dullcyan,+    dullwhite :: Doc -> Doc+dullblack   = dullcolor Ansi.Black+dullred     = dullcolor Ansi.Red+dullgreen   = dullcolor Ansi.Green+dullyellow  = dullcolor Ansi.Yellow+dullblue    = dullcolor Ansi.Blue+dullmagenta = dullcolor Ansi.Magenta+dullcyan    = dullcolor Ansi.Cyan+dullwhite   = dullcolor Ansi.White+++--------------------------------------------------------------------------------+ondullcolor :: Ansi.Color -> Doc -> Doc+ondullcolor c = mkDoc . Ansi+    (\codes -> Ansi.SetColor Ansi.Background Ansi.Dull c : codes)+++--------------------------------------------------------------------------------+ondullblack :: Doc -> Doc+ondullblack = ondullcolor Ansi.Black+++--------------------------------------------------------------------------------+ondullred :: Doc -> Doc+ondullred = ondullcolor Ansi.Red+++--------------------------------------------------------------------------------+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
+ src/Text/Pandoc/Extended.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE LambdaCase #-}+module Text.Pandoc.Extended+    ( module Text.Pandoc++    , plainToPara+    , newlineToSpace+    ) where++import Text.Pandoc+import Data.Data.Extended (grecT)++plainToPara :: [Block] -> [Block]+plainToPara = map $ \case+    Plain inlines -> Para inlines+    block         -> block++newlineToSpace :: [Inline] -> [Inline]+newlineToSpace = grecT $ \case+    SoftBreak -> Space+    LineBreak -> Space+    inline    -> inline