taskell 1.8.2.0 → 1.9.0.0
raw patch · 14 files changed
+594/−512 lines, 14 files
Files
- src/Config.hs +1/−1
- src/Data/Taskell/Task.hs +1/−0
- src/Data/Taskell/Task/Internal.hs +3/−0
- src/IO/Keyboard/Parser.hs +3/−3
- src/IO/Markdown.hs +3/−2
- src/IO/Markdown/Internal.hs +0/−148
- src/IO/Markdown/Parser.hs +67/−0
- src/IO/Markdown/Serializer.hs +92/−0
- src/IO/Taskell.hs +8/−3
- src/Utility/Parser.hs +3/−0
- taskell.cabal +5/−3
- test/IO/Markdown/ParserTest.hs +214/−0
- test/IO/Markdown/SerializerTest.hs +194/−0
- test/IO/MarkdownTest.hs +0/−352
src/Config.hs view
@@ -9,7 +9,7 @@ import Data.FileEmbed (embedFile) version :: Text-version = "1.8.2"+version = "1.9.0" usage :: Text usage = decodeUtf8 $(embedFile "templates/usage.txt")
src/Data/Taskell/Task.hs view
@@ -9,6 +9,7 @@ , subtasks , blank , new+ , create , duplicate , setDescription , appendDescription
src/Data/Taskell/Task/Internal.hs view
@@ -27,6 +27,9 @@ $(makeLenses ''Task) -- operations+create :: Text -> Maybe Due -> Maybe Text -> Seq ST.Subtask -> Task+create name' due' description' subtasks' = Task name' description' subtasks' due'+ blank :: Task blank = Task "" Nothing empty Nothing
src/IO/Keyboard/Parser.hs view
@@ -30,8 +30,8 @@ bindingP :: Parser [Binding] bindingP = lexeme $ (keyP <|> charP) `sepBy` char ',' -line :: Parser [(Binding, ActionType)]-line =+lineP :: Parser [(Binding, ActionType)]+lineP = stripComments $ do name <- read <$> word _ <- lexeme $ char '='@@ -39,7 +39,7 @@ pure $ (, name) <$> binds bindingsP :: Parser Bindings-bindingsP = stripComments $ concat <$> many' line+bindingsP = stripComments $ concat <$> many' lineP -- run parser bindings :: Text -> Either Text Bindings
src/IO/Markdown.hs view
@@ -1,7 +1,8 @@ module IO.Markdown ( parse- , stringify+ , serialize , MarkdownInfo(MarkdownInfo) ) where -import IO.Markdown.Internal+import IO.Markdown.Parser (parse)+import IO.Markdown.Serializer (MarkdownInfo (MarkdownInfo), serialize)
− src/IO/Markdown/Internal.hs
@@ -1,148 +0,0 @@-{-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE OverloadedStrings #-}--module IO.Markdown.Internal where--import ClassyPrelude--import Control.Lens ((.~), (^.))--import Data.Sequence (adjust')-import qualified Data.Text as T (splitOn, strip)-import Data.Text.Encoding (decodeUtf8With)--import Data.Time.Zones (TZ)--import Data.Taskell.Date (Due, textToTime, timeToOutput, timeToOutputLocal)-import Data.Taskell.List (List, count, tasks, title, updateFn)-import Data.Taskell.Lists (Lists, appendToLast, newList)-import qualified Data.Taskell.Subtask as ST (Subtask, complete, name, new)-import qualified Data.Taskell.Task as T (Task, addSubtask, appendDescription, description, due,- name, new, subtasks)--import qualified IO.Config as C (Config, markdown)-import IO.Config.Markdown (Config, descriptionOutput, dueOutput, localTimes,- subtaskOutput, taskOutput, titleOutput)--data MarkdownInfo = MarkdownInfo- { mdTZ :: TZ- , mdConfig :: Config- }--type ReaderMarkdown = Reader MarkdownInfo Text---- parse code-addSubItem :: Text -> Lists -> Lists-addSubItem t ls = adjust' updateList i ls- where- i = length ls - 1- st- | "[ ] " `isPrefixOf` t = ST.new (drop 4 t) False- | "[x] " `isPrefixOf` t = ST.new (drop 4 t) True- | otherwise = ST.new t False- updateList l = updateFn j (T.addSubtask st) l- where- j = count l - 1--addDescription :: Text -> Lists -> Lists-addDescription t ls = adjust' updateList i ls- where- i = length ls - 1- updateList l = updateFn j (T.appendDescription t) l- where- j = count l - 1--addDue :: Text -> Lists -> Lists-addDue t ls = adjust' updateList i ls- where- i = length ls - 1- updateList l = updateFn j (T.due .~ textToTime t) l- where- j = count l - 1--prefix :: Config -> Text -> (Config -> Text) -> (Text -> Lists -> Lists) -> Maybe (Lists -> Lists)-prefix config str get set- | pre `isPrefixOf` str = Just $ set (drop (length pre) str)- | otherwise = Nothing- where- pre = get config `snoc` ' '--matches :: [(Config -> Text, Text -> Lists -> Lists)]-matches =- [ (titleOutput, newList)- , (taskOutput, appendToLast . T.new)- , (descriptionOutput, addDescription)- , (dueOutput, addDue)- , (subtaskOutput, addSubItem)- ]--start :: Config -> (Lists, [Int]) -> (Text, Int) -> (Lists, [Int])-start config (current, errs) (text, line) =- case find isJust $ uncurry (prefix config text) <$> matches of- Just (Just set) -> (set current, errs)- _ ->- if not (null (T.strip text))- then (current, errs <> [line])- else (current, errs)--decodeError :: String -> Maybe Word8 -> Maybe Char-decodeError _ _ = Just '\65533'--parse :: C.Config -> ByteString -> Either Text Lists-parse config s = do- let lns = lines $ decodeUtf8With decodeError s- let fn = start (C.markdown config)- let acc = (empty, [])- let (lists, errs) = foldl' fn acc $ zip lns [1 ..]- if null errs- then Right lists- else Left $ "could not parse line(s) " <> intercalate ", " (tshow <$> errs)---- stringify code-subtaskSymbol :: Bool -> Text-subtaskSymbol True = "[x]"-subtaskSymbol False = "[ ]"--subtaskStringify :: ST.Subtask -> ReaderMarkdown-subtaskStringify st = do- symbol <- subtaskOutput <$> asks mdConfig- pure . concat $ [symbol, " ", subtaskSymbol (st ^. ST.complete), " ", st ^. ST.name]--descriptionStringify :: Text -> ReaderMarkdown-descriptionStringify desc = do- symbol <- descriptionOutput <$> asks mdConfig- let add d = concat [symbol, " ", d]- pure . intercalate "\n" $ add <$> T.splitOn "\n" desc--dueStringify :: Due -> ReaderMarkdown-dueStringify time = do- symbol <- dueOutput <$> asks mdConfig- useLocal <- localTimes <$> asks mdConfig- tz <- asks mdTZ- let fn =- if useLocal- then timeToOutputLocal tz- else timeToOutput- pure $ concat [symbol, " ", fn time]--nameStringify :: Text -> ReaderMarkdown-nameStringify desc = do- symbol <- taskOutput <$> asks mdConfig- pure $ concat [symbol, " ", desc]--taskStringify :: T.Task -> ReaderMarkdown-taskStringify t = do- nameString <- nameStringify (t ^. T.name)- dueString <- fromMaybe "" <$> sequence (dueStringify <$> t ^. T.due)- descriptionString <- fromMaybe "" <$> sequence (descriptionStringify <$> t ^. T.description)- subtaskString <- intercalate "\n" <$> sequence (subtaskStringify <$> t ^. T.subtasks)- pure . unlines . filter (/= "") $ [nameString, dueString, descriptionString, subtaskString]--listStringify :: List -> ReaderMarkdown-listStringify list = do- symbol <- titleOutput <$> asks mdConfig- taskString <- concat <$> sequence (taskStringify <$> list ^. tasks)- pure $ concat [symbol, " ", list ^. title, "\n\n", taskString]--stringify :: Lists -> ReaderMarkdown-stringify ls = intercalate "\n" <$> sequence (listStringify <$> ls)
+ src/IO/Markdown/Parser.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}++module IO.Markdown.Parser+ ( parse+ ) where++import ClassyPrelude++import Data.Attoparsec.Text hiding (parse)++import qualified Data.Taskell.Date as D (Due, textToTime)+import qualified Data.Taskell.List as L (List, create)+import qualified Data.Taskell.Lists as LS (Lists)+import qualified Data.Taskell.Subtask as ST (Subtask, new)+import qualified Data.Taskell.Task as T (Task, create)++import IO.Config.Markdown (Config, descriptionOutput, dueOutput, subtaskOutput, taskOutput,+ titleOutput)+import Utility.Parser (lexeme, line)++-- config symbol parsing+type Symbol = (Config -> Text) -> Parser ()++symP :: Config -> Symbol+symP config fn = string (fn config) *> char ' ' $> ()++-- utility functions+emptyMay :: (MonoFoldable a) => a -> Maybe a+emptyMay a =+ if null a+ then Nothing+ else Just a++-- parsers+subtaskCompleteP :: Parser Bool+subtaskCompleteP = (== 'x') <$> (char '[' *> (char 'x' <|> char ' ') <* char ']' <* char ' ')++subtaskP :: Symbol -> Parser ST.Subtask+subtaskP sym = flip ST.new <$> (sym subtaskOutput *> subtaskCompleteP) <*> line++taskDescriptionP :: Symbol -> Parser (Maybe Text)+taskDescriptionP sym = emptyMay <$> (intercalate "\n" <$> many' (sym descriptionOutput *> line))++dueP :: Symbol -> Parser (Maybe D.Due)+dueP sym = (D.textToTime =<<) <$> optional (sym dueOutput *> line)++taskNameP :: Symbol -> Parser Text+taskNameP sym = sym taskOutput *> line++taskP :: Symbol -> Parser T.Task+taskP sym =+ T.create <$> taskNameP sym <*> dueP sym <*> taskDescriptionP sym <*>+ (fromList <$> many' (subtaskP sym))++listTitleP :: Symbol -> Parser Text+listTitleP sym = lexeme $ sym titleOutput *> line++listP :: Symbol -> Parser L.List+listP sym = L.create <$> listTitleP sym <*> (fromList <$> many' (taskP sym))++markdownP :: Symbol -> Parser LS.Lists+markdownP sym = fromList <$> many1 (listP sym) <* endOfInput++-- parse+parse :: Config -> Text -> Either Text LS.Lists+parse config txt = first (const "Could not parse file.") (parseOnly (markdownP (symP config)) txt)
+ src/IO/Markdown/Serializer.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}++module IO.Markdown.Serializer+ ( serialize+ , MarkdownInfo(MarkdownInfo)+ ) where++import ClassyPrelude++import Control.Lens ((^.))++import qualified Data.Text as T (splitOn)++import Data.Time.Zones (TZ)++import Data.Taskell.Date (Due, timeToOutput, timeToOutputLocal)+import Data.Taskell.List (List, tasks, title)+import Data.Taskell.Lists (Lists)+import qualified Data.Taskell.Subtask as ST (Subtask, complete, name)+import qualified Data.Taskell.Task as T (Task, description, due, name, subtasks)++import IO.Config.Markdown (Config, descriptionOutput, dueOutput, localTimes, subtaskOutput,+ taskOutput, titleOutput)++data MarkdownInfo = MarkdownInfo+ { mdTZ :: TZ+ , mdConfig :: Config+ }++type ReaderMarkdown = Reader MarkdownInfo++-- utility functions+askConf :: (Config -> a) -> ReaderMarkdown a+askConf fn = fn <$> asks mdConfig++strMay :: (Applicative m) => (a -> m Text) -> Maybe a -> m Text+strMay _ Nothing = pure ""+strMay fn (Just a) = fn a++space :: Text -> Text -> Text+space symbol txt = symbol <> " " <> txt++timeFn :: ReaderMarkdown (Due -> Text)+timeFn = bool timeToOutput <$> (timeToOutputLocal <$> asks mdTZ) <*> askConf localTimes++-- serializers+subtaskCompleteS :: Bool -> Text+subtaskCompleteS True = "[x]"+subtaskCompleteS False = "[ ]"++subtaskS :: ST.Subtask -> ReaderMarkdown Text+subtaskS st = do+ symbol <- askConf subtaskOutput+ pure $ unwords [symbol, subtaskCompleteS (st ^. ST.complete), st ^. ST.name]++subtasksS :: Seq ST.Subtask -> ReaderMarkdown Text+subtasksS sts = intercalate "\n" <$> sequence (subtaskS <$> sts)++descriptionS :: Text -> ReaderMarkdown Text+descriptionS desc = do+ symbol <- askConf descriptionOutput+ pure . intercalate "\n" $ space symbol <$> T.splitOn "\n" desc++dueS :: Due -> ReaderMarkdown Text+dueS due = do+ symbol <- askConf dueOutput+ fn <- timeFn+ pure $ space symbol (fn due)++nameS :: Text -> ReaderMarkdown Text+nameS desc = space <$> askConf taskOutput <*> pure desc++taskS :: T.Task -> ReaderMarkdown Text+taskS t =+ unlines . filter (/= "") <$>+ sequence+ [ nameS (t ^. T.name)+ , strMay dueS (t ^. T.due)+ , strMay descriptionS (t ^. T.description)+ , subtasksS (t ^. T.subtasks)+ ]++listS :: List -> ReaderMarkdown Text+listS list = do+ symbol <- askConf titleOutput+ taskString <- concat <$> sequence (taskS <$> list ^. tasks)+ pure . space symbol $ concat [list ^. title, "\n\n", taskString]++-- serialize+serialize :: Lists -> ReaderMarkdown Text+serialize ls = intercalate "\n" <$> sequence (listS <$> ls)
src/IO/Taskell.hs view
@@ -8,6 +8,7 @@ import Control.Monad.Reader (runReader) import Data.FileEmbed (embedFile)+import Data.Text.Encoding (decodeUtf8With) import System.Directory (doesFileExist, getCurrentDirectory) import Data.Time.Zones (TZ)@@ -20,7 +21,7 @@ import qualified IO.Config.GitHub as GitHub (token) import qualified IO.Config.Trello as Trello (token) -import IO.Markdown (MarkdownInfo (MarkdownInfo), parse, stringify)+import IO.Markdown (MarkdownInfo (MarkdownInfo), parse, serialize) import qualified IO.HTTP.GitHub as GitHub (GitHubIdentifier, getLists) import qualified IO.HTTP.Trello as Trello (TrelloBoardID, getLists)@@ -147,8 +148,12 @@ writeData :: TZ -> Config -> Lists -> FilePath -> IO () writeData tz config tasks path = void (writeFile path output) where- output = encodeUtf8 $ runReader (stringify tasks) (MarkdownInfo tz (markdown config))+ output = encodeUtf8 $ runReader (serialize tasks) (MarkdownInfo tz (markdown config)) -- reads json file+decodeError :: String -> Maybe Word8 -> Maybe Char+decodeError _ _ = Just '\65533'+ readData :: FilePath -> ReaderConfig (Either Text Lists)-readData path = parse <$> asks ioConfig <*> readFile path+readData path =+ parse <$> (markdown <$> asks ioConfig) <*> (decodeUtf8With decodeError <$> readFile path)
src/Utility/Parser.hs view
@@ -9,5 +9,8 @@ lexeme :: Parser a -> Parser a lexeme p = skipSpace *> p <* skipSpace +line :: Parser Text+line = (takeTill isEndOfLine <* endOfLine) <|> takeText+ word :: Parser Text word = lexeme $ pack <$> many1 letter
taskell.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.12 name: taskell-version: 1.8.2.0+version: 1.9.0.0 license: BSD3 license-file: LICENSE copyright: 2019 Mark Wales@@ -49,7 +49,8 @@ Events.State.Types Events.State.Types.Mode IO.Config.Markdown- IO.Markdown.Internal+ IO.Markdown.Parser+ IO.Markdown.Serializer IO.HTTP.GitHub IO.HTTP.Trello.List IO.HTTP.Trello.ChecklistItem@@ -160,7 +161,8 @@ IO.Keyboard.ParserTest IO.Keyboard.TypesTest IO.KeyboardTest- IO.MarkdownTest+ IO.Markdown.ParserTest+ IO.Markdown.SerializerTest IO.TrelloTest UI.FieldTest Paths_taskell
+ test/IO/Markdown/ParserTest.hs view
@@ -0,0 +1,214 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++module IO.Markdown.ParserTest+ ( test_parser+ ) where++import ClassyPrelude++import Test.Tasty++import Test.Tasty.HUnit++import Control.Lens ((&), (.~))+import Data.FileEmbed (embedFile)++import Data.Taskell.Date (textToTime)+import Data.Taskell.List (create)+import Data.Taskell.Lists (Lists, analyse, appendToLast, newList)++import qualified Data.Taskell.Subtask as ST (new)+import Data.Taskell.Task (Task, addSubtask, due, new, setDescription)+import IO.Config.Markdown (Config (Config), defaultConfig, descriptionOutput, dueOutput,+ localTimes, subtaskOutput, taskOutput, titleOutput)+import IO.Markdown.Parser (parse)++-- error message+err :: Either Text Lists+err = Left "Could not parse file."++-- complete taskell file+file :: Text+file = decodeUtf8 $(embedFile "test/IO/data/roadmap.md")++-- alternative markdown configs+alternativeConfig :: Config+alternativeConfig =+ Config+ { titleOutput = "##"+ , taskOutput = "###"+ , descriptionOutput = ">"+ , dueOutput = "@"+ , subtaskOutput = "-"+ , localTimes = True+ }++-- useful records+task :: Task+task = new "Test Item"++list :: Lists+list = newList "Test" empty++listWithItem :: Lists+listWithItem = appendToLast task list++multiList :: Lists+multiList =+ fromList+ [create "Test" (fromList [new "Test Item"]), create "Test 2" (fromList [new "Test Item 2"])]++makeSubTask :: Text -> Bool -> Lists+makeSubTask t b = appendToLast (addSubtask (ST.new t b) task) list++taskWithSummary :: Task+taskWithSummary = setDescription "Summary" task++taskWithMultiLineSummary :: Task+taskWithMultiLineSummary = setDescription "Summary Line 1\nSummary Line 2" task++listWithSummaryItem :: Lists+listWithSummaryItem = appendToLast taskWithSummary list++listWithMultiLineSummaryItem :: Lists+listWithMultiLineSummaryItem = appendToLast taskWithMultiLineSummary list++taskWithDueDate :: Task+taskWithDueDate = task & due .~ textToTime "2018-04-12"++listWithDueDateItem :: Lists+listWithDueDateItem = appendToLast taskWithDueDate list++-- tests+test_parser :: TestTree+test_parser =+ testGroup+ "IO.Markdown"+ [ testGroup+ "Default Format"+ [ testCase+ "List Title"+ (assertEqual "One list" (Right list) (parse defaultConfig "## Test"))+ , testCase+ "List item"+ (assertEqual+ "List item"+ (Right listWithItem)+ (parse defaultConfig "## Test\n\n- Test Item"))+ , testCase+ "Multiple Lists"+ (assertEqual+ "List item"+ (Right multiList)+ (parse defaultConfig "## Test\n\n- Test Item\n\n## Test 2\n\n- Test Item 2"))+ , testCase+ "Summary"+ (assertEqual+ "Summary"+ (Right listWithSummaryItem)+ (parse defaultConfig "## Test\n\n- Test Item\n > Summary"))+ , testCase+ "List Item with multi-line Summary"+ (assertEqual+ "List item with a summary"+ (Right listWithMultiLineSummaryItem)+ (parse+ defaultConfig+ "## Test\n\n- Test Item\n > Summary Line 1\n > Summary Line 2"))+ , testCase+ "Due Date"+ (assertEqual+ "Due Date"+ (Right listWithDueDateItem)+ (parse defaultConfig "## Test\n\n- Test Item\n @ 2018-04-12"))+ , testCase+ "Sub-Task"+ (assertEqual+ "List item with Sub-Task"+ (Right (makeSubTask "Blah" False))+ (parse defaultConfig "## Test\n\n- Test Item\n * [ ] Blah"))+ , testCase+ "Complete Sub-Task"+ (assertEqual+ "List item with Sub-Task"+ (Right (makeSubTask "Blah" True))+ (parse defaultConfig "## Test\n\n- Test Item\n * [x] Blah"))+ ]+ , testGroup+ "Alternative Format"+ [ testCase+ "List Title"+ (assertEqual "One list" (Right list) (parse alternativeConfig "## Test"))+ , testCase+ "List item"+ (assertEqual+ "List item"+ (Right listWithItem)+ (parse alternativeConfig "## Test\n\n### Test Item"))+ , testCase+ "Multiple Lists"+ (assertEqual+ "List item"+ (Right multiList)+ (parse+ alternativeConfig+ "## Test\n\n### Test Item\n\n## Test 2\n\n### Test Item 2"))+ , testCase+ "Summary"+ (assertEqual+ "Summary"+ (Right listWithSummaryItem)+ (parse alternativeConfig "## Test\n\n### Test Item\n> Summary"))+ , testCase+ "List Item with multi-line Summary"+ (assertEqual+ "List item with a summary"+ (Right listWithMultiLineSummaryItem)+ (parse+ alternativeConfig+ "## Test\n\n### Test Item\n> Summary Line 1\n> Summary Line 2"))+ , testCase+ "Due Date"+ (assertEqual+ "Due Date"+ (Right listWithDueDateItem)+ (parse alternativeConfig "## Test\n\n### Test Item\n@ 2018-04-12"))+ , testCase+ "Sub-Task"+ (assertEqual+ "List item with Sub-Task"+ (Right (makeSubTask "Blah" False))+ (parse alternativeConfig "## Test\n\n### Test Item\n- [ ] Blah"))+ , testCase+ "Complete Sub-Task"+ (assertEqual+ "List item with Sub-Task"+ (Right (makeSubTask "Blah" True))+ (parse alternativeConfig "## Test\n\n### Test Item\n- [x] Blah"))+ ]+ , testGroup+ "Errors"+ [ testCase "Blank line" (assertEqual "Parse Error" err (parse defaultConfig ""))+ , testCase+ "Just spaces"+ (assertEqual "Parse Error" err (parse defaultConfig " "))+ , testCase+ "Just whitespace"+ (assertEqual "Parse Error" err (parse defaultConfig " \n \n \t "))+ , testCase "Error" (assertEqual "Parse Error" err (parse defaultConfig "Test"))+ , testCase+ "List item without list"+ (assertEqual "Parse Error" err (parse defaultConfig "- Test Item"))+ , testCase+ "Sub task without list item"+ (assertEqual "Parse Error" err (parse defaultConfig "## Test\n * Blah"))+ ]+ , testCase+ "File"+ (assertEqual+ "Parses whole file"+ (Right "test\nLists: 6\nTasks: 202")+ (analyse "test" <$> parse defaultConfig file))+ ]
+ test/IO/Markdown/SerializerTest.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++module IO.Markdown.SerializerTest+ ( test_serializer+ ) where++import ClassyPrelude++import Test.Tasty+import Test.Tasty.HUnit++import Control.Lens ((&), (.~))+import Control.Monad.Reader (runReader)+import Data.FileEmbed (embedFile)++import Data.Time.Zones (utcTZ)+import Data.Time.Zones.All (TZLabel (America__New_York), tzByLabel)++import Data.Taskell.Date (textToTime)+import qualified Data.Taskell.List as L (append, empty)+import Data.Taskell.Lists (Lists, appendToLast, newList)+import qualified Data.Taskell.Subtask as ST (new)+import Data.Taskell.Task (Task, addSubtask, due, new, setDescription)+import IO.Config.Markdown (Config (..), defaultConfig)+import IO.Markdown.Parser (parse)+import IO.Markdown.Serializer (MarkdownInfo (MarkdownInfo), serialize)++-- complete taskell file+file :: Text+file = decodeUtf8 $(embedFile "test/IO/data/roadmap.md")++-- alternative markdown configs+alternativeConfig :: Config+alternativeConfig =+ Config+ { titleOutput = "##"+ , taskOutput = "###"+ , descriptionOutput = ">"+ , dueOutput = "@"+ , subtaskOutput = "-"+ , localTimes = True+ }++-- useful records+task :: Task+task = new "Test Item"++list :: Lists+list = newList "Test" empty++lists :: Lists+lists = fromList [L.append task (L.empty "To Do"), L.append (new "Fish") (L.empty "Done")]++listWithItem :: Lists+listWithItem = appendToLast task list++makeSubTask :: Text -> Bool -> Lists+makeSubTask t b = appendToLast (addSubtask (ST.new t b) task) list++makeSubTasks :: [Text] -> Bool -> Lists+makeSubTasks ts b = appendToLast (foldr addSubtask task subtasks) list+ where+ subtasks = flip ST.new b <$> ts++taskWithSummary :: Task+taskWithSummary = setDescription "Summary" task++taskWithMultiLineSummary :: Task+taskWithMultiLineSummary = setDescription "Summary Line 1\nSummary Line 2" task++listWithSummaryItem :: Lists+listWithSummaryItem = appendToLast taskWithSummary list++listWithMultiLineSummaryItem :: Lists+listWithMultiLineSummaryItem = appendToLast taskWithMultiLineSummary list++taskWithDueDate :: Task+taskWithDueDate = task & due .~ textToTime "2018-04-12"++listWithDueDateItem :: Lists+listWithDueDateItem = appendToLast taskWithDueDate list++taskWithDueTime :: Task+taskWithDueTime = task & due .~ textToTime "2018-04-12 12:00 UTC"++listWithDueTimeItem :: Lists+listWithDueTimeItem = appendToLast taskWithDueTime list++-- reader+defaultReader :: MarkdownInfo+defaultReader = MarkdownInfo utcTZ defaultConfig++alternativeReader :: MarkdownInfo+alternativeReader = MarkdownInfo utcTZ alternativeConfig++-- simplify tests+serialize' :: MarkdownInfo -> Lists -> Text+serialize' md ls = runReader (serialize ls) md++-- tests+test_serializer :: TestTree+test_serializer =+ testGroup+ "IO.Markdown"+ [ testGroup+ "Serialisation"+ [ testGroup+ "Default Format"+ [ testCase+ "Standard lists"+ (assertEqual+ "Markdown formatted output"+ "## To Do\n\n- Test Item\n\n## Done\n\n- Fish\n"+ (serialize' defaultReader lists))+ , testCase+ "Standard list"+ (assertEqual+ "Markdown formatted output"+ "## Test\n\n- Test Item\n"+ (serialize' defaultReader listWithItem))+ , testCase+ "Standard list with summary"+ (assertEqual+ "Markdown formatted output"+ "## Test\n\n- Test Item\n > Summary\n"+ (serialize' defaultReader listWithSummaryItem))+ , testCase+ "Standard list with date"+ (assertEqual+ "Markdown formatted output"+ "## Test\n\n- Test Item\n @ 2018-04-12\n"+ (serialize' defaultReader listWithDueDateItem))+ , testCase+ "Standard list with date - timezone"+ (assertEqual+ "Use UTC timezone"+ "## Test\n\n- Test Item\n @ 2018-04-12 12:00 UTC\n"+ (serialize'+ (MarkdownInfo (tzByLabel America__New_York) defaultConfig)+ listWithDueTimeItem))+ , testCase+ "Standard list with sub-task"+ (assertEqual+ "Markdown formatted output"+ "## Test\n\n- Test Item\n * [x] Blah\n"+ (serialize' defaultReader (makeSubTask "Blah" True)))+ , testCase+ "Standard list with sub-tasks"+ (assertEqual+ "Markdown formatted output"+ "## Test\n\n- Test Item\n * [x] Blah\n * [x] Cow\n * [x] Spoon\n"+ (serialize'+ defaultReader+ (makeSubTasks ["Spoon", "Cow", "Blah"] True)))+ , testCase+ "Standard list with multi-line summary"+ (assertEqual+ "Markdown formatted output"+ "## Test\n\n- Test Item\n > Summary Line 1\n > Summary Line 2\n"+ (serialize' defaultReader listWithMultiLineSummaryItem))+ ]+ , testGroup+ "Alternative Format"+ [ testCase+ "Standard list"+ (assertEqual+ "Markdown formatted output"+ "## Test\n\n### Test Item\n"+ (serialize' alternativeReader listWithItem))+ , testCase+ "Standard list with sub-task"+ (assertEqual+ "Markdown formatted output"+ "## Test\n\n### Test Item\n- [ ] Blah\n"+ (serialize' alternativeReader (makeSubTask "Blah" False)))+ , testCase+ "Standard list with date - timezone"+ (assertEqual+ "Uses local timezone"+ "## Test\n\n### Test Item\n@ 2018-04-12 08:00 EDT\n"+ (serialize'+ (MarkdownInfo (tzByLabel America__New_York) alternativeConfig)+ listWithDueTimeItem))+ ]+ ]+ , testCase+ "Parse then serialize"+ (assertEqual+ "Returns same text"+ (Right file)+ (serialize' defaultReader <$> parse defaultConfig file))+ ]
− test/IO/MarkdownTest.hs
@@ -1,352 +0,0 @@-{-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE OverloadedStrings #-}--module IO.MarkdownTest- ( test_markdown- ) where--import ClassyPrelude--import Test.Tasty-import Test.Tasty.ExpectedFailure (ignoreTest)-import Test.Tasty.HUnit--import Control.Lens ((&), (.~))-import Control.Monad.Reader (runReader)--import Data.Time.Zones (utcTZ)-import Data.Time.Zones.All (TZLabel (America__New_York), tzByLabel)--import Data.Taskell.Date (textToTime)-import qualified Data.Taskell.List as L (append, empty)-import Data.Taskell.Lists (Lists, appendToLast, newList)-import qualified Data.Taskell.Subtask as ST (new)-import Data.Taskell.Task (Task, addSubtask, due, new, setDescription)-import qualified IO.Config as C (defaultConfig)-import IO.Config.Markdown (Config (..), defaultConfig)-import IO.Markdown.Internal (MarkdownInfo (MarkdownInfo), parse, start, stringify)---- alternative markdown configs-alternativeConfig :: Config-alternativeConfig =- Config- { titleOutput = "##"- , taskOutput = "###"- , descriptionOutput = ">"- , dueOutput = "@"- , subtaskOutput = "-"- , localTimes = True- }---- useful records-task :: Task-task = new "Test Item"--list :: Lists-list = newList "Test" empty--lists :: Lists-lists = fromList [L.append task (L.empty "To Do"), L.append (new "Fish") (L.empty "Done")]--listWithItem :: Lists-listWithItem = appendToLast task list--makeSubTask :: Text -> Bool -> Lists-makeSubTask t b = appendToLast (addSubtask (ST.new t b) task) list--makeSubTasks :: [Text] -> Bool -> Lists-makeSubTasks ts b = appendToLast (foldr addSubtask task subtasks) list- where- subtasks = flip ST.new b <$> ts--taskWithSummary :: Task-taskWithSummary = setDescription "Summary" task--taskWithMultiLineSummary :: Task-taskWithMultiLineSummary = setDescription "Summary Line 1\nSummary Line 2" task--listWithSummaryItem :: Lists-listWithSummaryItem = appendToLast taskWithSummary list--listWithMultiLineSummaryItem :: Lists-listWithMultiLineSummaryItem = appendToLast taskWithMultiLineSummary list--taskWithDueDate :: Task-taskWithDueDate = task & due .~ textToTime "2018-04-12"--listWithDueDateItem :: Lists-listWithDueDateItem = appendToLast taskWithDueDate list--taskWithDueTime :: Task-taskWithDueTime = task & due .~ textToTime "2018-04-12 12:00 UTC"--listWithDueTimeItem :: Lists-listWithDueTimeItem = appendToLast taskWithDueTime list---- reader-defaultReader :: MarkdownInfo-defaultReader = MarkdownInfo utcTZ defaultConfig--alternativeReader :: MarkdownInfo-alternativeReader = MarkdownInfo utcTZ alternativeConfig---- simplify tests-stringify' :: MarkdownInfo -> Lists -> Text-stringify' md ls = runReader (stringify ls) md---- tests-test_markdown :: TestTree-test_markdown =- testGroup- "IO.Markdown"- [ testGroup- "Line Parsing"- [ testGroup- "Default Format"- [ testCase- "List Title"- (assertEqual- "One list"- (list, [])- (start defaultConfig (empty, []) ("## Test", 1)))- , testCase- "Blank line"- (assertEqual- "Nothing"- (empty, [])- (start defaultConfig (empty, []) ("", 1)))- , testCase- "Just spaces"- (assertEqual- "Nothing"- (empty, [])- (start defaultConfig (empty, []) (" ", 1)))- , testCase- "Error"- (assertEqual- "Error on line 1"- (empty, [1])- (start defaultConfig (empty, []) ("Test", 1)))- , testCase- "List item"- (assertEqual- "List item"- (listWithItem, [])- (start defaultConfig (list, []) ("- Test Item", 1)))- , testCase- "Summary"- (assertEqual- "Summary"- (listWithSummaryItem, [])- (start defaultConfig (listWithItem, []) (" > Summary", 1)))- , testCase- "Due Date"- (assertEqual- "Due Date"- (listWithDueDateItem, [])- (start defaultConfig (listWithItem, []) (" @ 2018-04-12", 1)))- , testCase- "Sub-Task"- (assertEqual- "List item with Sub-Task"- (makeSubTask "Blah" False, [])- (start defaultConfig (listWithItem, []) (" * Blah", 1)))- , testCase- "Complete Sub-Task"- (assertEqual- "List item with Sub-Task"- (makeSubTask "Blah" True, [])- (start defaultConfig (listWithItem, []) (" * [x] Blah", 1)))- , ignoreTest $- testCase- "List item without list"- (assertEqual- "Parse Error"- (empty, [1])- (start defaultConfig (empty, []) ("- Test Item", 1)))- , ignoreTest $- testCase- "Sub task without list item"- (assertEqual- "Parse Error"- (list, [1])- (start defaultConfig (list, []) (" * Blah", 1)))- ]- , testGroup- "Alternative Format"- [ testCase- "List Title"- (assertEqual- "One list"- (list, [])- (start alternativeConfig (empty, []) ("## Test", 1)))- , testCase- "Blank line"- (assertEqual- "Nothing"- (empty, [])- (start alternativeConfig (empty, []) ("", 1)))- , testCase- "Just spaces"- (assertEqual- "Nothing"- (empty, [])- (start alternativeConfig (empty, []) (" ", 1)))- , testCase- "Error"- (assertEqual- "Error on line 1"- (empty, [1])- (start alternativeConfig (empty, []) ("* Test", 1)))- , testCase- "List item"- (assertEqual- "List item"- (listWithItem, [])- (start alternativeConfig (list, []) ("### Test Item", 1)))- , testCase- "Sub-Task"- (assertEqual- "List item with Sub-Task"- (makeSubTask "Blah" False, [])- (start alternativeConfig (listWithItem, []) ("- [ ] Blah", 1)))- , testCase- "Complete Sub-Task"- (assertEqual- "List item with Sub-Task"- (makeSubTask "Blah" True, [])- (start alternativeConfig (listWithItem, []) ("- [x] Blah", 1)))- , testCase- "Blank Sub-Task"- (assertEqual- "List item with blank Sub-Task"- (makeSubTask "" True, [])- (start alternativeConfig (listWithItem, []) ("- [x] ", 1)))- , testCase- "Sub-Task (old style)"- (assertEqual- "List item with Sub-Task"- (makeSubTask "Blah" False, [])- (start alternativeConfig (listWithItem, []) ("- Blah", 1)))- ]- ]- , testGroup- "Parsing"- [ testCase- "List Title"- (assertEqual- "One empty list"- (Right list)- (parse C.defaultConfig (encodeUtf8 "## Test")))- , testCase- "List Items"- (assertEqual- "List with item"- (Right listWithItem)- (parse C.defaultConfig (encodeUtf8 "## Test\n- Test Item")))- , testCase- "List Item with Summary"- (assertEqual- "List item with a summary"- (Right listWithSummaryItem)- (parse C.defaultConfig (encodeUtf8 "## Test\n- Test Item\n > Summary")))- , testCase- "Parsing Errors"- (assertEqual- "Errors"- (Left "could not parse line(s) 3, 5")- (parse- C.defaultConfig- (encodeUtf8 "## Test\n- Test Item\n* Spoon\n- Test Item\nCow")))- , testCase- "List Item with multi-line Summary"- (assertEqual- "List item with a summary"- (Right listWithMultiLineSummaryItem)- (parse- C.defaultConfig- (encodeUtf8- "## Test\n- Test Item\n > Summary Line 1\n > Summary Line 2")))- ]- , testGroup- "Stringification"- [ testGroup- "Default Format"- [ testCase- "Standard lists"- (assertEqual- "Markdown formatted output"- "## To Do\n\n- Test Item\n\n## Done\n\n- Fish\n"- (stringify' defaultReader lists))- , testCase- "Standard list"- (assertEqual- "Markdown formatted output"- "## Test\n\n- Test Item\n"- (stringify' defaultReader listWithItem))- , testCase- "Standard list with summary"- (assertEqual- "Markdown formatted output"- "## Test\n\n- Test Item\n > Summary\n"- (stringify' defaultReader listWithSummaryItem))- , testCase- "Standard list with date"- (assertEqual- "Markdown formatted output"- "## Test\n\n- Test Item\n @ 2018-04-12\n"- (stringify' defaultReader listWithDueDateItem))- , testCase- "Standard list with date - timezone"- (assertEqual- "Use UTC timezone"- "## Test\n\n- Test Item\n @ 2018-04-12 12:00 UTC\n"- (stringify'- (MarkdownInfo (tzByLabel America__New_York) defaultConfig)- listWithDueTimeItem))- , testCase- "Standard list with sub-task"- (assertEqual- "Markdown formatted output"- "## Test\n\n- Test Item\n * [x] Blah\n"- (stringify' defaultReader (makeSubTask "Blah" True)))- , testCase- "Standard list with sub-tasks"- (assertEqual- "Markdown formatted output"- "## Test\n\n- Test Item\n * [x] Blah\n * [x] Cow\n * [x] Spoon\n"- (stringify'- defaultReader- (makeSubTasks ["Spoon", "Cow", "Blah"] True)))- , testCase- "Standard list with multi-line summary"- (assertEqual- "Markdown formatted output"- "## Test\n\n- Test Item\n > Summary Line 1\n > Summary Line 2\n"- (stringify' defaultReader listWithMultiLineSummaryItem))- ]- , testGroup- "Alternative Format"- [ testCase- "Standard list"- (assertEqual- "Markdown formatted output"- "## Test\n\n### Test Item\n"- (stringify' alternativeReader listWithItem))- , testCase- "Standard list with sub-task"- (assertEqual- "Markdown formatted output"- "## Test\n\n### Test Item\n- [ ] Blah\n"- (stringify' alternativeReader (makeSubTask "Blah" False)))- , testCase- "Standard list with date - timezone"- (assertEqual- "Uses local timezone"- "## Test\n\n### Test Item\n@ 2018-04-12 08:00 EDT\n"- (stringify'- (MarkdownInfo (tzByLabel America__New_York) alternativeConfig)- listWithDueTimeItem))- ]- ]- ]