orgmode-parse 0.2.3 → 0.3.0
raw patch · 12 files changed
+236/−161 lines, 12 files
Files
- CHANGELOG.md +11/−0
- README.md +11/−20
- orgmode-parse.cabal +3/−4
- src/Data/OrgMode/Parse/Attoparsec/Drawer/Generic.hs +10/−11
- src/Data/OrgMode/Parse/Attoparsec/Section.hs +12/−11
- src/Data/OrgMode/Parse/Attoparsec/Time.hs +23/−22
- src/Data/OrgMode/Parse/Attoparsec/Util.hs +15/−16
- src/Data/OrgMode/Types.hs +51/−20
- test/Document.hs +87/−46
- test/Test.hs +7/−4
- test/Timestamps.hs +1/−1
- test/Util.hs +5/−6
CHANGELOG.md view
@@ -1,3 +1,14 @@+0.3.0+- Added support for parsing the following types of markup (thank you to+ @zhujinxuan for the significant contribution!):+ - `*bold*`+ - `/italic/`+ - `_underlined_`+ - `=verbatim=`+ - `~code~`+ - `+strikethrough`+ - LaTex markup+ 0.2.2 - Support building with GHC 8.4.3 (thank you @zhujinxuan!)
README.md view
@@ -6,7 +6,6 @@ combinators for org-mode structured text. - [What's Finished](#whats-finished)-- [What's Planned](#whats-planned) - [Building](#building) You can find the package on [Hackage](https://hackage.haskell.org/package/orgmode-parse).@@ -27,30 +26,22 @@ - [X] Scheduled and deadline timestamps (timestamp, range, duration, periodicity) - [X] Active and inactive timestamps - [X] Clock timestamps-- [ ] Markup- - [ ] Emphasis- - [ ] Bold- - [ ] Italic- - [ ] Strikethrough- - [ ] Underline+- [-] Markup+ - [-] Emphasis+ - [X] Bold+ - [X] Italic+ - [X] Strikethrough+ - [X] Underline - [ ] Superscript - [ ] Subscript- - [ ] Code / monospaced+ - [X] Code / monospaced - [ ] Tables- - [ ] Lists- - [ ] Unordered lists- - [ ] Numbered lists+ - [-] Lists+ - [X] Unordered lists+ - [X] Numbered lists - [ ] Checkbox modified lists - [ ] Blocks (src / quote / example blocks) -Parsing org-mode markup is currently being worked on.--## What's Planned (outside of what's not finished)-1. Modernizing this library and adding significantly more documentation to it-2. Writing a sister library, `orgmode-pretty`, providing a pretty printer- implementation for an org-mode AST-3. Pandoc integration- ## Building There are a few ways to build this library if you're developing a patch: @@ -61,7 +52,7 @@ development: ```shell-$ nix-shell --attr orgmode-parse.env release.nix+$ nix-shell $ cabal build ```
orgmode-parse.cabal view
@@ -1,5 +1,5 @@ Name: orgmode-parse-Version: 0.2.3+Version: 0.3.0 Author: Parnell Springmeyer <parnell@digitalmentat.com> Maintainer: Parnell Springmeyer <parnell@digitalmentat.com> License: BSD3@@ -16,8 +16,7 @@ document markup. . The provided Attoparsec combinators parse the human-readable and- textual representation into a simple AST for storage or output to- another format (HTML? Markdown?).+ textual representation into a simple AST. Cabal-Version: >= 1.10 Build-Type: Simple@@ -64,7 +63,7 @@ Type: exitcode-stdio-1.0 Default-Language: Haskell2010 Hs-Source-Dirs: test, src- Ghc-Options: -Wall+ Ghc-Options: -Wall -fwarn-tabs -funbox-strict-fields -fno-warn-orphans -fno-warn-unused-do-bind -fbreak-on-error Main-Is: Test.hs Other-Modules: Document,
src/Data/OrgMode/Parse/Attoparsec/Drawer/Generic.hs view
@@ -20,8 +20,7 @@ where import Control.Applicative ((*>), (<*))-import Data.Attoparsec.Text-import Data.Attoparsec.Types as Attoparsec+import Data.Attoparsec.Text ((<?>), char, takeWhile1, skipSpace, Parser, manyTill, asciiCI) import Data.Text (Text) import qualified Data.Text as Text @@ -33,7 +32,7 @@ -- > :MYTEXT: -- > whatever I want, can go in here except for headlines and drawers -- > :END:-parseDrawer :: Attoparsec.Parser Text Drawer+parseDrawer :: Parser Drawer parseDrawer = Drawer <$> parseDrawerName <*>@@ -44,23 +43,23 @@ -- > :DRAWERNAME: -- > my text in a drawer -- > :END:-parseDrawerName :: Attoparsec.Parser Text Text+parseDrawerName :: Parser Text parseDrawerName =- skipSpace *> skip (== ':') *>+ skipSpace *> char ':' *> takeWhile1 (/= ':') <*- skip (== ':') <* skipSpace+ char ':' <* skipSpace -- | Parse drawer delimiters, e.g the beginning and end of a property -- drawer: -- -- > :PROPERTIES: -- > :END:-parseDrawerDelim :: Text -> Attoparsec.Parser Text Text+parseDrawerDelim :: Text -> Parser Text parseDrawerDelim v =- skipSpace *> skip (== ':') *>+ skipSpace *> char ':' *> asciiCI v <*- skip (== ':') <* Util.skipOnlySpace+ char ':' <* Util.skipOnlySpace -- | Parse the @:END:@ of a drawer.-drawerEnd :: Attoparsec.Parser Text Text-drawerEnd = parseDrawerDelim "END"+drawerEnd :: Parser Text+drawerEnd = parseDrawerDelim "END" <?> "Expected Drawer End :END:"
src/Data/OrgMode/Parse/Attoparsec/Section.hs view
@@ -13,15 +13,15 @@ module Data.OrgMode.Parse.Attoparsec.Section where -import Data.Attoparsec.Text (many', option, skipSpace)-import Data.Attoparsec.Types as Attoparsec+import Control.Applicative ()+import Data.Attoparsec.Text (skipSpace, many', option)+import qualified Data.Attoparsec.Text as Attoparsec.Text import Data.Monoid ()+ import Data.OrgMode.Parse.Attoparsec.Drawer import Data.OrgMode.Parse.Attoparsec.Time-import qualified Data.OrgMode.Parse.Attoparsec.Util as Util-import Data.Text (Text)-import qualified Data.Text as Text-+import Data.OrgMode.Parse.Attoparsec.Util (skipEmptyLines)+import Data.OrgMode.Parse.Attoparsec.Block (parseBlocks) import Data.OrgMode.Types -- | Parse a heading section@@ -29,13 +29,14 @@ -- Headline sections contain optionally a property drawer, -- a list of clock entries, code blocks (not yet implemented), -- plain lists (not yet implemented), and unstructured text.-parseSection :: Attoparsec.Parser Text Section-parseSection =- Section+parseSection :: Attoparsec.Text.Parser Section+parseSection = skipEmptyLines *> parseSection' <* skipEmptyLines+ where+ parseSection' = Section <$> option Nothing (Just <$> (skipSpace *> parseTimestamp <* skipSpace)) <*> (Plns <$> parsePlannings) <*> many' parseClock <*> option mempty parseProperties <*> option mempty parseLogbook- <*> many' parseDrawer- <*> (Text.unlines <$> many' Util.nonHeadline)+ <*> parseBlocks+
src/Data/OrgMode/Parse/Attoparsec/Time.hs view
@@ -22,26 +22,26 @@ ) where -import Control.Applicative-import qualified Data.Attoparsec.ByteString as Attoparsec.ByteString-import Data.Attoparsec.Combinator as Attoparsec+import Control.Applicative +import qualified Data.Attoparsec.ByteString as Attoparsec.ByteString+import Data.Attoparsec.Combinator as Attoparsec import Data.Attoparsec.Text-import Data.Attoparsec.Types as Attoparsec (Parser)-import qualified Data.ByteString.Char8 as BS-import Data.Functor (($>))-import Data.HashMap.Strict (HashMap, fromList)-import Data.Maybe (listToMaybe)-import Data.Monoid ()-import qualified Data.OrgMode.Parse.Attoparsec.Util as Util+import Data.Attoparsec.Types as Attoparsec (Parser)+import qualified Data.ByteString.Char8 as BS+import Data.Functor (($>))+import Data.HashMap.Strict (HashMap, fromList)+import Data.Maybe (listToMaybe)+import Data.Monoid ()+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Thyme.Format (buildTime, timeParser)+import Data.Thyme.LocalTime (Hours, Minutes)+import System.Locale (defaultTimeLocale)+import Data.Semigroup ((<>)) import Data.OrgMode.Types-import Data.Semigroup ((<>))-import Data.Text (Text)-import qualified Data.Text as Text-import Data.Thyme.Format (buildTime, timeParser)-import Data.Thyme.LocalTime (Hours, Minutes)-import Prelude hiding (repeat)-import System.Locale (defaultTimeLocale) +import Prelude hiding (repeat)+ -- | Parse a planning line. -- -- Plannings inhabit a heading section and are formatted as a keyword@@ -50,7 +50,7 @@ -- -- > DEADLINE: <2015-05-10 17:00> CLOSED: <2015-04-1612:00> parsePlannings :: Attoparsec.Parser Text (HashMap PlanningKeyword Timestamp)-parsePlannings = fromList <$> many' (skipSpace *> planning <* Util.skipOnlySpace)+parsePlannings = fromList <$> many' (skipSpace *> planning <* skipSpace) where planning = (,) <$> pType <* char ':' <*> (skipSpace *> parseTimestamp) pType = choice [string "SCHEDULED" $> SCHEDULED@@ -67,8 +67,9 @@ parseClock :: Attoparsec.Parser Text Clock parseClock = Clock <$> ((,) <$> (skipSpace *> string "CLOCK: " *> ts) <*> dur) where- ts = optional parseTimestamp- dur = optional (string " => " *> skipSpace *> parseHM)+ ts = option Nothing (Just <$> parseTimestamp)+ dur = option Nothing (Just <$> (string " => "+ *> skipSpace *> parseHM)) -- | Parse a timestamp. --@@ -110,7 +111,7 @@ where optionalBracketedDateTime =- optional (string "--" *> parseBracketedDateTime)+ option Nothing (Just <$> (string "--" *> parseBracketedDateTime)) -- | Parse a single time part.@@ -137,7 +138,7 @@ closingBracket <- char '>' <|> char ']' finally brkDateTime openingBracket closingBracket where- optionalParse p = optional p <* skipSpace+ optionalParse p = option Nothing (Just <$> p) <* skipSpace maybeListParse p = listToMaybe <$> many' p <* skipSpace activeBracket ((=='<') -> active) = if active then Active else Inactive
src/Data/OrgMode/Parse/Attoparsec/Util.hs view
@@ -8,18 +8,22 @@ Attoparsec utilities. -} + module Data.OrgMode.Parse.Attoparsec.Util-( skipOnlySpace-, nonHeadline+( skipOnlySpace,+ nonHeadline,+ module Data.OrgMode.Parse.Attoparsec.Util.ParseLinesTill )+ where -import Control.Applicative ((<|>))+import Data.Semigroup ((<>)) import qualified Data.Attoparsec.Text as Attoparsec.Text-import Data.Attoparsec.Types (Parser)-import Data.Text (Text)+import Data.Attoparsec.Text (Parser, takeTill, isEndOfLine, endOfLine, notChar, isHorizontalSpace)+import Data.Text (Text, cons) import qualified Data.Text as Text import Data.Functor (($>))+import Data.OrgMode.Parse.Attoparsec.Util.ParseLinesTill -- | Skip whitespace characters, only! --@@ -27,17 +31,12 @@ -- @Data.Char@ which also includes control characters such as a return -- and newline which we need to *not* consume in some cases during -- parsing.-skipOnlySpace :: Parser Text ()-skipOnlySpace = Attoparsec.Text.skipWhile spacePred- where- spacePred s = s == ' ' || s == '\t'+skipOnlySpace :: Parser ()+skipOnlySpace = Attoparsec.Text.skipWhile isHorizontalSpace -- | Parse a non-heading line of a section.-nonHeadline :: Parser Text Text-nonHeadline = nonHeadline0 <|> nonHeadline1+nonHeadline :: Parser Text+nonHeadline = nonHeadline0 <> nonHeadline1 where- nonHeadline0 = Attoparsec.Text.endOfLine $> Text.pack ""- nonHeadline1 = Text.pack <$> do- h <- Attoparsec.Text.notChar '*'- t <- Attoparsec.Text.manyTill Attoparsec.Text.anyChar Attoparsec.Text.endOfLine- pure (h:t)+ nonHeadline0 = endOfLine $> Text.empty+ nonHeadline1 = cons <$> notChar '*' <*> (takeTill isEndOfLine <* endOfLine)
src/Data/OrgMode/Types.hs view
@@ -23,7 +23,7 @@ , DelayType (..) , Depth (..) , Document (..)-, Drawer (..)+, Drawer , Duration , Headline (..) , Logbook (..)@@ -41,18 +41,23 @@ , TimeUnit (..) , Timestamp (..) , YearMonthDay (..)+, Block (..)+, MarkupText (..)+, Item (..)+, sectionDrawer ) where -import Control.Monad (mzero)-import Data.Aeson ((.:), (.=))-import qualified Data.Aeson as Aeson-import Data.Hashable (Hashable (..))-import Data.HashMap.Strict (HashMap, fromList, keys, toList)-import Data.Text (Text, pack)-import Data.Thyme.Calendar (YearMonthDay (..))-import Data.Thyme.LocalTime (Hour, Hours, Minute, Minutes)+import Control.Monad (mzero)+import Data.Aeson ((.:), (.=))+import qualified Data.Aeson as Aeson+import Data.Hashable (Hashable (..))+import Data.HashMap.Strict (HashMap, fromList, keys, toList)+import Data.Text (Text, pack)+import Data.Thyme.Calendar (YearMonthDay (..))+import Data.Thyme.LocalTime (Hour, Hours, Minute, Minutes) import GHC.Generics-import Data.Semigroup (Semigroup)+import Data.Semigroup (Semigroup)+ -- | Org-mode document. data Document = Document { documentText :: Text -- ^ Text occurring before any Org headlines@@ -82,6 +87,7 @@ instance Aeson.ToJSON Depth instance Aeson.FromJSON Depth + -- | Section of text directly following a headline. data Section = Section { sectionTimestamp :: Maybe Timestamp -- ^ A headline's section timestamp@@ -89,14 +95,46 @@ , sectionClocks :: [Clock] -- ^ A list of clocks , sectionProperties :: Properties -- ^ A map of properties from the :PROPERTY: drawer , sectionLogbook :: Logbook -- ^ A list of clocks from the :LOGBOOK: drawer- , sectionDrawers :: [Drawer] -- ^ A list of parsed user-defined drawers- , sectionParagraph :: Text -- ^ Arbitrary text+ , sectionBlocks :: [Block] -- ^ Content of Section } deriving (Show, Eq, Generic) +sectionDrawer :: Section -> [Block]+sectionDrawer s = filter isDrawer (sectionBlocks s)+ where+ isDrawer (Drawer _ _) = True+ isDrawer _ = False+ newtype Properties = Properties { unProperties :: HashMap Text Text } deriving (Show, Eq, Generic, Semigroup, Monoid) +data MarkupText+ = Plain Text+ | LaTeX Text+ | Verbatim Text+ | Code Text+ | Bold [MarkupText]+ | Italic [MarkupText]+ | UnderLine [MarkupText]+ | Strikethrough [MarkupText]+ deriving (Show, Eq, Generic) +newtype Item = Item [Block] deriving (Show, Eq, Generic, Semigroup, Monoid)++data Block = OrderedList [Item] | UnorderedList [Item] | Paragraph [MarkupText] | Drawer {+ name :: Text+ , contents :: Text+} deriving (Show, Eq, Generic)+type Drawer = Block++instance Aeson.ToJSON MarkupText+instance Aeson.FromJSON MarkupText++instance Aeson.ToJSON Item+instance Aeson.FromJSON Item++instance Aeson.ToJSON Block+instance Aeson.FromJSON Block+ instance Aeson.ToJSON Properties instance Aeson.FromJSON Properties @@ -107,14 +145,6 @@ instance Aeson.ToJSON Logbook instance Aeson.FromJSON Logbook -data Drawer = Drawer- { name :: Text- , contents :: Text- } deriving (Show, Eq, Generic)--instance Aeson.ToJSON Drawer-instance Aeson.FromJSON Drawer- -- | Sum type indicating the active state of a timestamp. data ActiveState = Active@@ -310,3 +340,4 @@ instance Hashable PlanningKeyword where hashWithSalt salt k = hashWithSalt salt (fromEnum k)+
test/Document.hs view
@@ -3,18 +3,20 @@ module Document where import Data.Attoparsec.Text-import Data.Text+import Data.Text hiding (map) import qualified Data.Text as Text import qualified Data.Text.IO as TextIO import Test.Tasty import Test.Tasty.HUnit-import Data.HashMap.Strict+import Data.HashMap.Strict hiding (map) import Data.OrgMode.Parse.Attoparsec.Document import Data.OrgMode.Parse.Attoparsec.Time import Data.OrgMode.Types import Util+import Util.Builder + parserSmallDocumentTests :: TestTree parserSmallDocumentTests = testGroup "Attoparsec Small Document" [ testCase "Parse Empty Document" $@@ -29,12 +31,6 @@ , testCase "Parse Headline with Planning" $ testDocS samplePText samplePParse - , testCase "Parse Headline with properties" $- testDocS sampleP2Text sampleP2Parse-- , testCase "Parse Headline with scheduled" $- testDocS sampleP3Text sampleP3Parse- , testCase "Parse Headline no \n" $ testDocS "* T" (Document "" [emptyHeadline {title="T"}]) @@ -49,18 +45,19 @@ testDocS s r = expectParse (parseDocument kw) s (Right r) testDocFile = do- doc <- TextIO.readFile "test/test-document.org"+ doc <- TextIO.readFile "test/docs/test-document.org" let testDoc = parseOnly (parseDocument kw) doc assertBool "Expected to parse document" (parseSucceeded testDoc) testSubtreeListItemDocFile = do- doc <- TextIO.readFile "test/subtree-list-items.org"+ doc <- TextIO.readFile "test/docs/subtree-list-items.org" - let subtreeListItemsDoc = parseOnly (parseDocument []) doc+ -- let subtreeListItemsDoc = parseOnly (parseDocument []) doc - assertBool "Expected to parse document" (subtreeListItemsDoc == goldenSubtreeListItemDoc)+ -- assertBool "Expected to parse document" (subtreeListItemsDoc == goldenSubtreeListItemDoc)+ expectParse (parseDocument []) doc goldenSubtreeListItemDoc kw = ["TODO", "CANCELED", "DONE"] pText = "Paragraph text\n.No headline here.\n##--------\n"@@ -76,8 +73,8 @@ sampleAParse :: Document sampleAParse = Document sampleParagraph- [emptyHeadline {title="Test1", tags=["Hi there"]}- ,emptyHeadline {section=emptySection{sectionParagraph=" *\n"}}+ -- Headline shall have space after *+ [emptyHeadline {title="Test1", tags=["Hi there"], section = emptySection {sectionBlocks= [toP (Bold [])]}} ,emptyHeadline {title="Test2", tags=["Two","Tags"]} ] @@ -97,36 +94,6 @@ Right con = parseOnly parsePlannings "SCHEDULED: <2015-06-12 Fri>" -sampleP2Text :: Text-sampleP2Text =- Text.concat ["* Test3_1\n"- ," :PROPERTIES:\n"- ," :CATEGORY: testCategory\n"- ," :END:"- ]--sampleP2Parse :: Document-sampleP2Parse =- Document "" [ emptyHeadline {- title = "Test3_1"- , section = emptySection {- sectionProperties = Properties (fromList [("CATEGORY", "testCategory")])}}]--sampleP3Text :: Text-sampleP3Text =- Text.concat ["* Test4_1\n"- ," SCHEDULED: <2004-02-29 Sun 10:20>"- ]--sampleP3Parse :: Document-sampleP3Parse =- Document "" [ emptyHeadline {- title = "Test4_1"- , section = emptySection {- sectionPlannings = Plns con}}]- where- Right con = parseOnly parsePlannings "SCHEDULED: <2004-02-29 Sun 10:20>"- emptyHeadline :: Headline emptyHeadline = Headline@@ -148,7 +115,81 @@ spaces = flip Text.replicate " " emptySection :: Section-emptySection = Section Nothing (Plns mempty) mempty mempty mempty mempty mempty+emptySection = Section Nothing (Plns mempty) mempty mempty mempty mempty +plainParagraphs :: Text -> [Block]+plainParagraphs str = [Paragraph [Plain str]]+ goldenSubtreeListItemDoc :: Either String Document-goldenSubtreeListItemDoc = Right (Document {documentText = "", documentHeadlines = [Headline {depth = Depth 1, stateKeyword = Nothing, priority = Nothing, title = "Header1", timestamp = Nothing, stats = Nothing, tags = [], section = Section {sectionTimestamp = Nothing, sectionPlannings = Plns (fromList []), sectionClocks = [], sectionProperties = Properties {unProperties = fromList []}, sectionLogbook = Logbook {unLogbook = []}, sectionDrawers = [], sectionParagraph = ""}, subHeadlines = [Headline {depth = Depth 2, stateKeyword = Nothing, priority = Nothing, title = "Header2", timestamp = Nothing, stats = Nothing, tags = [], section = Section {sectionTimestamp = Nothing, sectionPlannings = Plns (fromList []), sectionClocks = [], sectionProperties = Properties {unProperties = fromList []}, sectionLogbook = Logbook {unLogbook = []}, sectionDrawers = [], sectionParagraph = ""}, subHeadlines = [Headline {depth = Depth 3, stateKeyword = Nothing, priority = Nothing, title = "Header3", timestamp = Nothing, stats = Nothing, tags = [], section = Section {sectionTimestamp = Nothing, sectionPlannings = Plns (fromList []), sectionClocks = [], sectionProperties = Properties {unProperties = fromList [("ONE","two")]}, sectionLogbook = Logbook {unLogbook = []}, sectionDrawers = [], sectionParagraph = "\n * Item1\n * Item2\n"}, subHeadlines = []}]},Headline {depth = Depth 2, stateKeyword = Nothing, priority = Nothing, title = "Header4", timestamp = Nothing, stats = Nothing, tags = [], section = Section {sectionTimestamp = Nothing, sectionPlannings = Plns (fromList []), sectionClocks = [], sectionProperties = Properties {unProperties = fromList []}, sectionLogbook = Logbook {unLogbook = []}, sectionDrawers = [], sectionParagraph = ""}, subHeadlines = []}]}]})+goldenSubtreeListItemDoc = Right (Document + {documentText = "", + documentHeadlines = [Headline + {depth = Depth 1,+ stateKeyword = Nothing,+ priority = Nothing,+ title = "Header1", + timestamp = Nothing,+ stats = Nothing,+ tags = [], + section = Section + {sectionTimestamp = Nothing,+ sectionPlannings = Plns (fromList []), + sectionClocks = [], + sectionProperties = Properties {unProperties = fromList []}, + sectionLogbook = Logbook {unLogbook = []}, + sectionBlocks = [] + },+ subHeadlines = [Headline {+ depth = Depth 2,+ stateKeyword = Nothing,+ priority = Nothing,+ title = "Header2", + timestamp = Nothing,+ stats = Nothing,+ tags = [], + section = Section {+ sectionTimestamp = Nothing,+ sectionPlannings = Plns (fromList []), + sectionClocks = [], + sectionProperties = Properties { unProperties = fromList []},+ sectionLogbook = Logbook {unLogbook = []},+ sectionBlocks = []+ },+ subHeadlines = [Headline {+ depth = Depth 3,+ stateKeyword = Nothing,+ priority = Nothing,+ title = "Header3",+ timestamp = Nothing,+ stats = Nothing,+ tags = [],+ section = Section {+ sectionTimestamp = Nothing,+ sectionPlannings = Plns (fromList []),+ sectionClocks = [],+ sectionProperties = Properties {unProperties = fromList [("ONE","two")]},+ sectionLogbook = Logbook {unLogbook = []},+ sectionBlocks = [UnorderedList $ map toI [pack "Item1", pack "Item2"]]+ },+ subHeadlines = []+ }]+ },+ Headline {+ depth = Depth 2,+ stateKeyword = Nothing,+ priority = Nothing,+ title = "Header4",+ timestamp = Nothing,+ stats = Nothing,+ tags = [],+ section = Section {+ sectionTimestamp = Nothing,+ sectionPlannings = Plns (fromList []),+ sectionClocks = [],+ sectionProperties = Properties {unProperties = fromList []},+ sectionLogbook = Logbook {unLogbook = []},+ sectionBlocks = []+ },+ subHeadlines = []}+ ]}+]})
test/Test.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}- module Main where import Document@@ -8,6 +5,9 @@ import Headline import Test.Tasty import Timestamps+import Block.Paragraph+import Block.List+import Block.Blocks main :: IO () main = defaultMain tests@@ -18,6 +18,9 @@ [ parserHeadlineTests , parserDrawerTests , parserTimestampTests- , parserSmallDocumentTests+ , parserParagraphs+ , parserLists+ , parserBlocks , parserWeekdayTests+ , parserSmallDocumentTests ]
test/Timestamps.hs view
@@ -25,7 +25,7 @@ , testCase "Parse Sample Schedule" $ testPlanningS sExampleStrA sExampleResA ] where- testPlanning = testParser parsePlannings + testPlanning = testParser parsePlannings testPlanningS t r = expectParse parsePlannings t (Right r) (sExampleStrA, sExampleResA) =
test/Util.hs view
@@ -1,15 +1,14 @@ module Util where -import Data.Attoparsec.Text-import Data.Attoparsec.Types as TP+import Data.Attoparsec.Text (Parser, parseOnly) import Data.Either-import Data.Text as T+import Data.Text (pack, Text) import Test.HUnit -testParser :: TP.Parser Text a -> String -> Assertion-testParser f v = fromEither (parseOnly f $ T.pack v)+testParser :: Parser a -> String -> Assertion+testParser f v = fromEither (parseOnly f $ pack v) -expectParse :: (Eq a, Show a) => TP.Parser Text a -- ^ Parser under test+expectParse :: (Eq a, Show a) => Parser a -- ^ Parser under test -> Text -- ^ Message under test -> Either String a -- ^ Expected parse result -> Assertion