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 +121/−104
- src/Environment.hs +6/−0
- src/FormatDocument.hs +24/−5
- src/FormatMain.hs +7/−1
- src/LatexOutputReader.hs +41/−0
- src/LatexPreamble.hs +11/−4
- src/OutputParser.hs +0/−41
- src/ParseBookfile.hs +60/−0
- src/RenderDocument.hs +122/−48
- src/RenderMain.hs +1/−4
- tests/CheckBookfileParser.hs +67/−0
- tests/CheckTableProperties.hs +1/−1
- tests/TestSuite.hs +3/−1
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