packages feed

publish 0.4.4 → 2.0.1

raw patch · 13 files changed

+464/−209 lines, 13 filesdep +megaparsecdep ~bytestringdep ~chronologiquedep ~deepseq

Dependencies added: megaparsec

Dependency ranges changed: bytestring, chronologique, deepseq, directory, filepath, hinotify, hspec, pandoc, pandoc-types, template-haskell, text, typed-process, unbeliever, unix, unordered-containers

Files

publish.cabal view
@@ -1,113 +1,130 @@ cabal-version: 1.12-name: publish-version: 0.4.4-license: BSD3-license-file: LICENCE-copyright: © 2016-2019 Operational Dynamics Consulting Pty Ltd, and Others-maintainer: Andrew Cowie <andrew@operationaldynamics.com>-author: Andrew Cowie <andrew@operationaldynamics.com>-stability: experimental-tested-with: ghc ==8.6-homepage: https://github.com/oprdyn/publish#readme-bug-reports: https://github.com/oprdyn/publish/issues-synopsis: Publishing tools for papers, books, and presentations-description:-    Tools for rendering markdown-centric documents into PDFs.-    .-    A description of this package, a list of features, and some background-    to its design is contained in the-    <https://github.com/oprdyn/publish/blob/master/README.markdown README>-    on GitHub.-    .-    The main program, @render@, is available via a Docker image at-    <https://cloud.docker.com/u/oprdyn/repository/docker/oprdyn/publish-render/general docker.io/oprdyn/publish-render:latest>-category: Text-build-type: Simple +-- This file has been generated from package.yaml by hpack version 0.31.1.+--+-- see: https://github.com/sol/hpack+--+-- hash: 547ada31362d64dd2c988a4b125cd34de323ad166da373e7cec366b84f9b4352++name:           publish+version:        2.0.1+synopsis:       Publishing tools for papers, books, and presentations+description:    Tools for rendering markdown-centric documents into PDFs.+                .+                A description of this package, a list of features, and some background+                to its design is contained in the+                <https://github.com/oprdyn/publish/blob/master/README.markdown README>+                on GitHub.+                .+                The main program, @render@, is available via a Docker image at+                <https://hub.docker.com/r/oprdyn/publish-render docker.io/oprdyn/publish-render:latest>+category:       Text+stability:      experimental+homepage:       https://github.com/oprdyn/publish#readme+bug-reports:    https://github.com/oprdyn/publish/issues+author:         Andrew Cowie <andrew@operationaldynamics.com>+maintainer:     Andrew Cowie <andrew@operationaldynamics.com>+copyright:      © 2016-2019 Operational Dynamics Consulting Pty Ltd, and Others+license:        BSD3+license-file:   LICENCE+tested-with:    GHC == 8.6+build-type:     Simple+ source-repository head-    type: git-    location: https://github.com/oprdyn/publish+  type: git+  location: https://github.com/oprdyn/publish  executable format-    main-is: FormatMain.hs-    hs-source-dirs: src-    other-modules:-        FormatDocument-        PandocToMarkdown-    default-language: Haskell2010-    ghc-options: -threaded -Wall -Wwarn -fwarn-tabs-    build-depends:-        base >=4.11 && <5,-        bytestring >=0.10.8.2 && <0.11,-        chronologique >=0.3.1.1 && <0.4,-        deepseq >=1.4.4.0 && <1.5,-        directory >=1.3.3.0 && <1.4,-        filepath >=1.4.2.1 && <1.5,-        hinotify ==0.4.*,-        pandoc ==2.5.*,-        pandoc-types >=1.17.5.4 && <1.18,-        template-haskell >=2.14.0.0 && <2.15,-        text >=1.2.3.1 && <1.3,-        typed-process >=0.2.4.0 && <0.3,-        unbeliever >=0.9.3.2 && <0.10,-        unix >=2.7.2.2 && <2.8,-        unordered-containers >=0.2.9.0 && <0.3+  main-is: FormatMain.hs+  other-modules:+      FormatDocument+      PandocToMarkdown+  hs-source-dirs:+      src+  ghc-options: -threaded -Wall -Wwarn -fwarn-tabs+  build-depends:+      base >=4.11 && <5+    , bytestring+    , chronologique+    , deepseq+    , directory+    , filepath+    , hinotify+    , megaparsec+    , pandoc+    , pandoc-types+    , template-haskell+    , text+    , typed-process+    , unbeliever >=0.9.3.2+    , unix+    , unordered-containers+  default-language: Haskell2010  executable render-    main-is: RenderMain.hs-    hs-source-dirs: src-    other-modules:-        Environment-        LatexPreamble-        NotifyChanges-        OutputParser-        PandocToMarkdown-        RenderDocument-        Utilities-    default-language: Haskell2010-    ghc-options: -threaded -Wall -Wwarn -fwarn-tabs-    build-depends:-        base >=4.11 && <5,-        bytestring >=0.10.8.2 && <0.11,-        chronologique >=0.3.1.1 && <0.4,-        deepseq >=1.4.4.0 && <1.5,-        directory >=1.3.3.0 && <1.4,-        filepath >=1.4.2.1 && <1.5,-        hinotify ==0.4.*,-        pandoc ==2.5.*,-        pandoc-types >=1.17.5.4 && <1.18,-        template-haskell >=2.14.0.0 && <2.15,-        text >=1.2.3.1 && <1.3,-        typed-process >=0.2.4.0 && <0.3,-        unbeliever >=0.9.3.2 && <0.10,-        unix >=2.7.2.2 && <2.8,-        unordered-containers >=0.2.9.0 && <0.3+  main-is: RenderMain.hs+  other-modules:+      Environment+      LatexPreamble+      NotifyChanges+      LatexOutputReader+      PandocToMarkdown+      ParseBookfile+      RenderDocument+      Utilities+  hs-source-dirs:+      src+  ghc-options: -threaded -Wall -Wwarn -fwarn-tabs+  build-depends:+      base >=4.11 && <5+    , bytestring+    , chronologique+    , deepseq+    , directory+    , filepath+    , hinotify+    , megaparsec+    , pandoc+    , pandoc-types+    , template-haskell+    , text+    , typed-process+    , unbeliever >=0.9.3.2+    , unix+    , unordered-containers+  default-language: Haskell2010  test-suite check-    type: exitcode-stdio-1.0-    main-is: TestSuite.hs-    hs-source-dirs: src tests-    other-modules:-        CheckTableProperties-        CompareFragments-        FormatDocument-        PandocToMarkdown-    default-language: Haskell2010-    ghc-options: -threaded -Wall -Wwarn -fwarn-tabs -threaded-    build-depends:-        base >=4.11 && <5,-        bytestring >=0.10.8.2 && <0.11,-        chronologique >=0.3.1.1 && <0.4,-        deepseq >=1.4.4.0 && <1.5,-        directory >=1.3.3.0 && <1.4,-        filepath >=1.4.2.1 && <1.5,-        hinotify ==0.4.*,-        hspec >=2.6.1 && <2.7,-        pandoc ==2.5.*,-        pandoc-types >=1.17.5.4 && <1.18,-        template-haskell >=2.14.0.0 && <2.15,-        text >=1.2.3.1 && <1.3,-        typed-process >=0.2.4.0 && <0.3,-        unbeliever >=0.9.3.2 && <0.10,-        unix >=2.7.2.2 && <2.8,-        unordered-containers >=0.2.9.0 && <0.3+  type: exitcode-stdio-1.0+  main-is: TestSuite.hs+  other-modules:+      CheckBookfileParser+      CheckTableProperties+      CompareFragments+      Environment+      FormatDocument+      PandocToMarkdown+      ParseBookfile+  hs-source-dirs:+      src+      tests+  ghc-options: -threaded -Wall -Wwarn -fwarn-tabs -threaded+  build-depends:+      base >=4.11 && <5+    , bytestring+    , chronologique+    , deepseq+    , directory+    , filepath+    , hinotify+    , hspec+    , megaparsec+    , pandoc+    , pandoc-types+    , template-haskell+    , text+    , typed-process+    , unbeliever >=0.9.3.2+    , unix+    , unordered-containers+  default-language: Haskell2010
src/Environment.hs view
@@ -2,6 +2,7 @@ (       Env(..)     , initial+    , Bookfile(..) ) where @@ -20,3 +21,8 @@     cwd <- getWorkingDirectory     return (Env cwd [] "/dev/null" "/dev/null" "/dev/null") +data Bookfile = Bookfile+    { versionFrom :: Int+    , preamblesFrom :: [FilePath]+    , fragmentsFrom :: [FilePath]+    } deriving (Show, Eq)
src/FormatDocument.hs view
@@ -15,6 +15,7 @@ import qualified Data.Text as T (Text) import qualified Data.Text.IO as T import System.IO (withFile, IOMode(..))+import System.Directory (getFileSize, renameFile) import Text.Pandoc (runIOorExplode, readMarkdown, def     , ReaderOptions(readerExtensions), pandocExtensions, disableExtension     , Extension(..), Pandoc, Extensions)@@ -37,6 +38,7 @@ getFragmentName :: Program None FilePath getFragmentName = do     params <- getCommandLine+     let fragment = case lookupArgument "document" params of             Nothing -> error "invalid"             Just file -> file@@ -69,13 +71,30 @@     runIOorExplode $ do         readMarkdown readingOptions contents +data Inplace = Inplace | Console+ writeResult :: FilePath -> Pandoc -> Program None () writeResult file doc =   let-    result = file ++ "-tmp"     contents' = pandocToMarkdown doc-  in-    liftIO $ do-        withFile result WriteMode $ \handle ->-            hWrite handle contents'+    result = file ++ "~tmp"+  in do+    params <- getCommandLine +    let mode = case lookupOptionFlag "inplace" params of+            Just False  -> error "Invalid State"+            Just True   -> Inplace+            Nothing     -> Console++    case mode of+        Inplace -> liftIO $ do+            withFile result WriteMode $ \handle ->+                hWrite handle contents'++            size <- getFileSize result+            if size == 0+                then error "Zero content, not overwriting"+                else renameFile result file++        Console -> liftIO $ do+                hWrite stdout contents'
src/FormatMain.hs view
@@ -15,7 +15,13 @@ main :: IO () main = do     context <- configure version None (simple-        [ Argument "document" [quote|+        [ Option "inplace" (Just 'i') Empty [quote|+            Overwrite the original file with the reformatted version. WARNING+            This tool is experimental. You should ensure you have a safe copy+            of your original (ie, add it to Git's index) before running with+            this option enabled.+          |]+        , Argument "document" [quote|             The file containing the markdown to be reformatted           |]         ])
+ src/LatexOutputReader.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE OverloadedStrings #-}++module LatexOutputReader+    ( parseOutputForError+    )+where++import Core.Text+import qualified Data.ByteString.Lazy.Char8 as L++{-+The build command returned a non-zero exit code, so there is a+reasonable assumption that there is indeed an error to be extracted.+-}+-- Originally written in lazy ByteString as that is output from readProcess+parseOutputForError :: FilePath -> Rope -> Rope+parseOutputForError tmpdir =+  let+    needle = L.pack tmpdir++    stripBeginning [] = []+    stripBeginning (b:bs) = if L.isPrefixOf needle b+        then b : bs+        else stripBeginning bs++    dropEnding [] = []+    dropEnding (b:bs) = if L.isPrefixOf "Output written on" b || "No pages of output." == b+        then []+        else b : dropEnding bs+  in+    intoRope . L.intercalate "\n" . dropEnding . stripBeginning . L.lines . fromRope+++-- Error stream from xelatex looks like this:+{-+/tmp/publish-Km3eN1/Junk.tex:8: Undefined control sequence.+l.8 \broken+           +No pages of output.+Transcript written on /tmp/publish-Km3eN1/Junk.log+-}
src/LatexPreamble.hs view
@@ -3,6 +3,7 @@  module LatexPreamble     ( preamble+    , beginning     , ending     ) where@@ -23,8 +24,6 @@ \setsansfont{TeX Gyre Heros}[Scale=MatchLowercase] \setmonofont{Inconsolata}[Scale=MatchLowercase] -%\usepackage[showframe, pass]{geometry}- % use upquote for straight quotes in verbatim environments \usepackage{upquote} @@ -90,6 +89,11 @@ \setsubsecheadstyle{\normalsize\sffamily\bfseries} \setsubsubsecheadstyle{\normalsize\rmfamily\itshape} +|]++beginning :: Rope+beginning = [quote|+ % % Output from Skylighting.styleToLaTeX %@@ -135,13 +139,16 @@ \newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} \newcommand{\WarningTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\usepackage[normalem]{ulem}+% % avoid problems with \sout in headers with hyperref:-\pdfstringdefDisableCommands{\renewcommand{\sout}{}}+% +\usepackage[normalem]{ulem}+\pdfstringdefDisableCommands{\renewcommand{\sout}{}}  \begin{document} |]+  ending :: Rope ending = [quote|
− src/OutputParser.hs
@@ -1,41 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module OutputParser-    ( parseOutputForError-    )-where--import Core.Text-import qualified Data.ByteString.Lazy.Char8 as L--{--The build command returned a non-zero exit code, so there is a-reasonable assumption that there is indeed an error to be extracted.--}--- Originally written in lazy ByteString as that is output from readProcess-parseOutputForError :: FilePath -> Rope -> Rope-parseOutputForError tmpdir =-  let-    needle = L.pack tmpdir--    stripBeginning [] = []-    stripBeginning (b:bs) = if L.isPrefixOf needle b-        then b : bs-        else stripBeginning bs--    dropEnding [] = []-    dropEnding (b:bs) = if L.isPrefixOf "Output written on" b || "No pages of output." == b-        then []-        else b : dropEnding bs-  in-    intoRope . L.intercalate "\n" . dropEnding . stripBeginning . L.lines . fromRope----- Error stream from xelatex looks like this:-{--/tmp/publish-Km3eN1/Junk.tex:8: Undefined control sequence.-l.8 \broken-           -No pages of output.-Transcript written on /tmp/publish-Km3eN1/Junk.log--}
+ src/ParseBookfile.hs view
@@ -0,0 +1,60 @@+module ParseBookfile where++import Control.Monad+import Data.Void+import Text.Megaparsec+import Text.Megaparsec.Char+import qualified Text.Megaparsec.Char.Lexer as L++import Environment (Bookfile(..))++-- use String, since we need FilePaths which are type aliases over String anyway.+type Parser = Parsec Void String++__VERSION__ :: Int+__VERSION__ = 2++parseMagicLine :: Parser Int+parseMagicLine = do+    void (char '%') <?> "first line to begin with % character"+    void spaceChar <?> "a space character"+    void (string "publish")+    void spaceChar <?> "a space character"+    void (char 'v') <?> "the character v and then a number"+    v <- L.decimal <?> "the bookfile schema version number"+    unless (v == __VERSION__) (fail ("currently recognized bookfile schema version is v" ++ show __VERSION__))+    void newline+    return v++parseBeginLine :: Parser ()+parseBeginLine = try $ label "begin marker" $ do+    void (string "% begin")+    void newline++parseFileLine :: Parser FilePath+parseFileLine = do+    notFollowedBy (char '%')+    file <- takeWhile1P (Just "line containing a filename") (/= '\n')+    return file++parseEndLine :: Parser ()+parseEndLine = try $ label "end marker" $ do+    void (string "% end")+    void newline++parseBlank :: Parser ()+parseBlank = do+    void (hidden (many newline))++parseBookfile :: Parser Bookfile+parseBookfile = do+    version <- parseMagicLine+    preambles <- many (parseBlank *> parseFileLine <* parseBlank)+    parseBeginLine+    fragments <- many (parseBlank *> parseFileLine <* parseBlank)+    parseEndLine+    return Bookfile+        { versionFrom = version+        , preamblesFrom = preambles+        , fragmentsFrom = fragments+        }
src/RenderDocument.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE QuasiQuotes #-}  module RenderDocument     ( program@@ -11,9 +12,8 @@ import Core.System import Core.Text import Data.Char (isSpace)-import qualified Data.List as List (dropWhileEnd)-import Data.Text (Text)-import qualified Data.Text as T+import qualified Data.List as List (dropWhileEnd, null)+import Data.Maybe (isJust) import qualified Data.Text.IO as T import System.Directory (doesFileExist, doesDirectoryExist     , copyFileWithMetadata)@@ -25,14 +25,16 @@ import System.Posix.Directory (changeWorkingDirectory) import System.Posix.Temp (mkdtemp) import System.Posix.User (getEffectiveUserID, getEffectiveGroupID)+import Text.Megaparsec (runParser, errorBundlePretty) import Text.Pandoc (runIOorExplode, readMarkdown, writeLaTeX, def     , readerExtensions, readerColumns, pandocExtensions     , writerTopLevelDivision, TopLevelDivision(TopLevelChapter)) -import Environment (Env(..))+import Environment (Env(..), Bookfile(..)) import NotifyChanges (waitForChange)-import LatexPreamble (preamble, ending)-import OutputParser (parseOutputForError)+import LatexPreamble (preamble, beginning, ending)+import LatexOutputReader (parseOutputForError)+import ParseBookfile (parseBookfile) import Utilities (ensureDirectory, execProcess, ifNewer, isNewer)  data Mode = Once | Cycle@@ -55,16 +57,27 @@   renderDocument :: Mode -> FilePath -> Program Env [FilePath]-renderDocument mode bookfile = do+renderDocument mode file = do     event "Read .book file"-    files <- processBookFile bookfile+    book <- processBookFile file      event "Setup temporary directory"-    setupTargetFile bookfile+    setupTargetFile file+    setupPreambleFile+    validatePreamble book +    let preambles = preamblesFrom book+    let fragments = fragmentsFrom book++    event "Convert preamble and begin fragments to LaTeX"+    mapM_ processFragment preambles+    setupBeginningFile+     event "Convert document fragments to LaTeX"-    mapM_ processFragment files+    mapM_ processFragment fragments +    setupEndingFile+     event "Write intermediate LaTeX file"     produceResult @@ -79,7 +92,8 @@             Cycle -> return ()         ) -    return (bookfile:files)+    -- question: original lists or filtered ones?+    return (file : preambles ++ fragments)   extractMode :: Parameters -> Program Env Mode@@ -117,7 +131,7 @@   setupTargetFile :: FilePath -> Program Env ()-setupTargetFile book = do+setupTargetFile file = do     env <- getApplicationState     let start = startingDirectoryFrom env     let dotfile = start ++ "/.target"@@ -148,44 +162,116 @@     let master = tmpdir ++ "/" ++ base ++ ".tex"         result = tmpdir ++ "/" ++ base ++ ".pdf" -    first <- case lookupOptionFlag "builtin-preamble" params of-        Nothing     -> return []-        Just True   -> do-            let name = "00_Beginning.latex"-            let target = tmpdir ++ "/" ++ name-            liftIO $ withFile target WriteMode $ \handle -> do-                hWrite handle preamble-            return [name]-        Just _      -> invalid-     let env' = env-            { intermediateFilenamesFrom = first+            { intermediateFilenamesFrom = []             , masterFilenameFrom = master             , resultFilenameFrom = result             , tempDirectoryFrom = tmpdir             }     setApplicationState env'   where-    base = takeBaseName book -- "/directory/file.ext" -> "file"+    base = takeBaseName file -- "/directory/file.ext" -> "file"      boom = userError "Temp dir no longer present"      trim :: String -> String     trim = List.dropWhileEnd isSpace +setupPreambleFile :: Program Env ()+setupPreambleFile = do+    env <- getApplicationState+    let tmpdir = tempDirectoryFrom env -processBookFile :: FilePath -> Program Env [FilePath]+    params <- getCommandLine+    first <- case lookupOptionFlag "builtin-preamble" params of+        Nothing     -> return []+        Just True   -> do+            let name = "00_Preamble.latex"+            let target = tmpdir ++ "/" ++ name+            liftIO $ withFile target WriteMode $ \handle -> do+                hWrite handle preamble+            return [name]+        Just _      -> invalid++    let env' = env { intermediateFilenamesFrom = first }+    setApplicationState env'++{-+This could do a lot more; checking to see if \documentclass is present, for+example. At present this covers the (likely common) failure mode of+specifying neither -p nor a preamble in the bookfile.+-}+validatePreamble :: Bookfile -> Program Env ()+validatePreamble book = do+    params <- getCommandLine+    let preambles = preamblesFrom book+    let builtin = isJust (lookupOptionFlag "builtin-preamble" params)++    if List.null preambles && not builtin+        then do+            write "error: no preamble\n"+            let msg :: Rope = [quote|+You need to either a) put the name of the file including the LaTeX+preamble for your document in the .book file between the "% publish"+and "% begin" lines, or b) specify the --builtin-preamble option on+the command-line when running this program.+|]+            writeR msg+            terminate 2+        else+            return ()++setupBeginningFile :: Program Env ()+setupBeginningFile = do+    env <- getApplicationState+    let tmpdir = tempDirectoryFrom env+        files = intermediateFilenamesFrom env++    file <- do+        let name = "99_Beginning.latex"+        let target = tmpdir ++ "/" ++ name+        liftIO $ withFile target WriteMode $ \handle -> do+            hWrite handle beginning+        return name++    let env' = env { intermediateFilenamesFrom = file : files }+    setApplicationState env'++setupEndingFile :: Program Env ()+setupEndingFile = do+    env <- getApplicationState+    let tmpdir = tempDirectoryFrom env+        files = intermediateFilenamesFrom env++    file <- do+        let name = "ZZ_Ending.latex"+        let target = tmpdir ++ "/" ++ name+        liftIO $ withFile target WriteMode $ \handle -> do+            hWrite handle ending+        return name++    let env' = env { intermediateFilenamesFrom = file : files }+    setApplicationState env'++processBookFile :: FilePath -> Program Env Bookfile processBookFile file = do-    contents <- liftIO (T.readFile file)-    list <- filterM skipNotFound (possibilities contents)-    debugS "fragments" (length list)-    return list-  where-    -- filter out blank lines and lines commented out-    possibilities :: Text -> [FilePath]-    possibilities = map T.unpack . filter (not . T.null)-        . filter (not . T.isPrefixOf "#") . T.lines+    contents <- liftIO (readFile file) +    let result = runParser parseBookfile file contents+    bookfile <- case result of+        Left err -> do+            write (intoRope (errorBundlePretty err))+            terminate 1+        Right value -> return value++    list1 <- filterM skipNotFound (preamblesFrom bookfile)+    debugS "preambles" (length list1)++    list2 <- filterM skipNotFound (fragmentsFrom bookfile)+    debugS "fragments" (length list2)++    return bookfile { preamblesFrom = list1, fragmentsFrom = list2 }+  where     skipNotFound :: FilePath -> Program t Bool     skipNotFound fragment = do         probe <- liftIO (doesFileExist fragment)@@ -335,25 +421,13 @@ produceResult :: Program Env () produceResult = do     env <- getApplicationState-    let tmpdir = tempDirectoryFrom env-        master = masterFilenameFrom env+    let master = masterFilenameFrom env         files = intermediateFilenamesFrom env -    params <- getCommandLine-    files' <- case lookupOptionFlag "builtin-preamble" params of-        Nothing     -> return files-        Just True   -> do-            let name = "ZZ_Ending.latex"-            let target = tmpdir ++ "/" ++ name-            liftIO $ withFile target WriteMode $ \handle -> do-                hWrite handle ending-            return (name:files)-        Just _      -> invalid-     debugS "master" master     liftIO $ withFile master WriteMode $ \handle -> do         hPutStrLn handle ("\\RequirePackage{import}")-        forM_ (reverse files') $ \file -> do+        forM_ (reverse files) $ \file -> do             let (path,name) = splitFileName file             hPutStrLn handle ("\\subimport{" ++ path ++ "}{" ++ name ++ "}") 
src/RenderMain.hs view
@@ -42,10 +42,7 @@         , Argument "bookfile" [quote|             The file containing the list of fragments making up this book.             If the argument is specified as "Hobbit.book" then "Hobbit"-            will be used as the basename for the intermediate .latex file-            and the final output .pdf file. The list file should contain-            filenames, one per line, of the fragments you wish to render-            into a complete document.+            will be used as the basename for the final output .pdf file.           |]         ]) 
+ tests/CheckBookfileParser.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE ScopedTypeVariables #-}++module CheckBookfileParser+    ( checkBookfileParser+    )+where++import Core.Text+import Test.Hspec+import Text.Megaparsec++import Environment (Bookfile(..))+import ParseBookfile++checkBookfileParser :: Spec+checkBookfileParser = do+    describe "Parse bookfile format" $ do+        it "Correctly parses a complete first line" $ do+            parseMaybe parseMagicLine "% publish v2\n" `shouldBe` Just 2+        it "Errors if first line has incorrect syntax" $ do+            parseMaybe parseMagicLine "%\n" `shouldBe` Nothing+            parseMaybe parseMagicLine "%publish\n" `shouldBe` Nothing+            parseMaybe parseMagicLine "% publish\n" `shouldBe` Nothing+            parseMaybe parseMagicLine "% publish \n" `shouldBe` Nothing+            parseMaybe parseMagicLine "% publish v\n" `shouldBe` Nothing+            parseMaybe parseMagicLine "% publish  v2\n" `shouldBe` Nothing+            parseMaybe parseMagicLine "% publish v1\n" `shouldBe` Nothing+            parseMaybe parseMagicLine "% publish v2 asdf\n" `shouldBe` Nothing++        it "Correctly parses a preamble line" $ do+            parseMaybe parseFileLine "preamble.latex" `shouldBe` Just "preamble.latex"+        it "Parses two filenames in a list" $ do+            parseMaybe (many (parseFileLine <* parseBlank)) "one.markdown\ntwo.markdown"+                `shouldBe` Just (["one.markdown", "two.markdown"] :: [FilePath])++        it "Parses two filenames with a blank line between them" $ do+            parseMaybe (many (parseFileLine <* parseBlank)) [quote|+one.markdown++two.markdown+            |]+                `shouldBe` Just (["one.markdown", "two.markdown"] :: [FilePath])++        it "Correctly parses a begin end end pragmas" $ do+            parseMaybe parseBeginLine "% begin\n" `shouldBe` Just ()+            parseMaybe parseEndLine "% end\n" `shouldBe` Just ()++        it "Correctly parses a complete bookfile" $ do+            parseMaybe parseBookfile [quote|+% publish v2+preamble.latex+% begin+Introduction.markdown+Conclusion.markdown+% end+            |] `shouldBe` Just (Bookfile 2 ["preamble.latex"] ["Introduction.markdown", "Conclusion.markdown"])++        it "Correctly parses a complete bookfile with no preamble" $ do+            parseMaybe parseBookfile [quote|+% publish v2+% begin+Introduction.markdown+Conclusion.markdown+% end+            |] `shouldBe` Just (Bookfile 2 [] ["Introduction.markdown", "Conclusion.markdown"])
tests/CheckTableProperties.hs view
@@ -120,7 +120,7 @@ -----------------------------------------------------------------------             |] -        it "a minimal complete example reformats properly" $+        it "A minimal complete example reformats properly" $           let             table = [quote| | First | Second | Third |
tests/TestSuite.hs view
@@ -2,8 +2,9 @@  import Test.Hspec -import Core.System+import CheckBookfileParser import CheckTableProperties+import Core.System import CompareFragments  main :: IO ()@@ -14,3 +15,4 @@ suite = do     checkTableProperties     checkByComparingFragments+    checkBookfileParser