packages feed

cmark-sections 0.1.0.3 → 0.2.0.0

raw patch · 4 files changed

+160/−128 lines, 4 filesdep ~basedep ~base-preludePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, base-prelude

API changes (from Hackage documentation)

- CMark.Sections: Ann :: Text -> a -> Annotated a
- CMark.Sections: [annSource] :: Annotated a -> Text
- CMark.Sections: [annValue] :: Annotated a -> a
- CMark.Sections: [contentAnn] :: Section a b -> b
- CMark.Sections: [headingAnn] :: Section a b -> a
- CMark.Sections: [prefaceAnn] :: Document a b -> b
- CMark.Sections: commonmarkToAnnotatedNodes :: [CMarkOption] -> Text -> Annotated [Node]
- CMark.Sections: data Annotated a
- CMark.Sections: instance Data.Foldable.Foldable CMark.Sections.Annotated
- CMark.Sections: instance Data.Traversable.Traversable CMark.Sections.Annotated
- CMark.Sections: instance GHC.Base.Functor CMark.Sections.Annotated
- CMark.Sections: instance GHC.Base.Monoid a => GHC.Base.Monoid (CMark.Sections.Annotated a)
- CMark.Sections: instance GHC.Classes.Eq a => GHC.Classes.Eq (CMark.Sections.Annotated a)
- CMark.Sections: instance GHC.Show.Show a => GHC.Show.Show (CMark.Sections.Annotated a)
+ CMark.Sections: WithSource :: Text -> a -> WithSource a
+ CMark.Sections: commonmarkToNodesWithSource :: [CMarkOption] -> Text -> WithSource [Node]
+ CMark.Sections: data WithSource a
+ CMark.Sections: getSource :: WithSource a -> Text
+ CMark.Sections: instance Data.Foldable.Foldable CMark.Sections.WithSource
+ CMark.Sections: instance Data.Traversable.Traversable CMark.Sections.WithSource
+ CMark.Sections: instance GHC.Base.Functor CMark.Sections.WithSource
+ CMark.Sections: instance GHC.Base.Monoid a => GHC.Base.Monoid (CMark.Sections.WithSource a)
+ CMark.Sections: instance GHC.Classes.Eq a => GHC.Classes.Eq (CMark.Sections.WithSource a)
+ CMark.Sections: instance GHC.Show.Show a => GHC.Show.Show (CMark.Sections.WithSource a)
+ CMark.Sections: stripSource :: WithSource a -> a
- CMark.Sections: Document :: Annotated [Node] -> b -> Forest (Section a b) -> Document a b
+ CMark.Sections: Document :: (b, WithSource [Node]) -> Forest (Section a b) -> Document a b
- CMark.Sections: Section :: Int -> Annotated [Node] -> a -> Annotated [Node] -> b -> Section a b
+ CMark.Sections: Section :: Int -> (a, WithSource [Node]) -> (b, WithSource [Node]) -> Section a b
- CMark.Sections: [content] :: Section a b -> Annotated [Node]
+ CMark.Sections: [content] :: Section a b -> (b, WithSource [Node])
- CMark.Sections: [heading] :: Section a b -> Annotated [Node]
+ CMark.Sections: [heading] :: Section a b -> (a, WithSource [Node])
- CMark.Sections: [preface] :: Document a b -> Annotated [Node]
+ CMark.Sections: [preface] :: Document a b -> (b, WithSource [Node])
- CMark.Sections: flattenDocument :: Document a b -> Annotated [Node]
+ CMark.Sections: flattenDocument :: Document a b -> WithSource [Node]
- CMark.Sections: flattenForest :: Forest (Section a b) -> Annotated [Node]
+ CMark.Sections: flattenForest :: Forest (Section a b) -> WithSource [Node]
- CMark.Sections: flattenSection :: Section a b -> Annotated [Node]
+ CMark.Sections: flattenSection :: Section a b -> WithSource [Node]
- CMark.Sections: flattenTree :: Tree (Section a b) -> Annotated [Node]
+ CMark.Sections: flattenTree :: Tree (Section a b) -> WithSource [Node]
- CMark.Sections: nodesToDocument :: Annotated [Node] -> Document () ()
+ CMark.Sections: nodesToDocument :: WithSource [Node] -> Document () ()

Files

CHANGELOG.md view
@@ -1,3 +1,14 @@+# 0.2++* Renamed `Annotated` to `WithSource`.++* Renamed `commonmarkToAnnotatedNodes` to `commonmarkToNodesWithSource`.++* Renamed `annSource` and `annValue` to `getSource` and `stripSource`.++* Joined `preface`, `header`, `content` with their annotations (using a+  tuple).+ # 0.1.0.3  * Bumped hspec and cmark versions.@@ -10,6 +21,6 @@  * Bumped hspec version. -# 0.1.0.0+# 0.1  First release.
cmark-sections.cabal view
@@ -1,5 +1,5 @@ name:                cmark-sections-version:             0.1.0.3+version:             0.2.0.0 synopsis:            Represent cmark-parsed Markdown as a tree of sections description:   Represent cmark-parsed Markdown as a tree of sections@@ -39,8 +39,8 @@   main-is:             Main.hs   type:                exitcode-stdio-1.0   build-depends:       QuickCheck >= 2.8 && < 3-                     , base >=4.7 && <5-                     , base-prelude == 1.*+                     , base+                     , base-prelude                      , cmark                      , cmark-sections                      , containers
lib/CMark/Sections.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE-TemplateHaskell, RecordWildCards, DeriveFunctor, DeriveFoldable,@@ -9,8 +8,9 @@   #-}  -{- |-This library lets you parse Markdown into a hierarchical structure (delimited by headings). For instance, let's say your document looks like this:+{- | This library lets you parse Markdown into a hierarchical structure+(delimited by headings). For instance, let's say your document looks like+this:  @ This is the preface.@@ -38,6 +38,7 @@  @ 'preface': "This is the preface."+ 'sections':     * 'heading': __"First chapter"__       'content': "This chapter doesn't have sections."@@ -54,22 +55,26 @@             'sections': [] @ -That's what this library does. Moreover, it lets you access the Markdown source of every node of the tree.+That's what this library does. Moreover, it lets you access the Markdown+source of every node of the tree.  In most cases the only thing you need to do is something like this:  @-'nodesToDocument' . 'commonmarkToAnnotatedNodes' ['optSafe', 'optNormalize']+'nodesToDocument' . 'commonmarkToNodesWithSource' ['optSafe', 'optNormalize'] @ -You can preprocess parsed Markdown after doing 'commonmarkToAnnotatedNodes' as long as you don't add or remove any top-level nodes.+You can preprocess parsed Markdown after doing 'commonmarkToNodesWithSource'+as long as you don't add or remove any top-level nodes. -} module CMark.Sections (   -- * Parse Markdown to trees-  commonmarkToAnnotatedNodes,+  commonmarkToNodesWithSource,   nodesToDocument,-  Annotated(..),+  WithSource(..),+    getSource,+    stripSource,   Section(..),   Document(..), @@ -97,67 +102,78 @@ import Data.List.Split  -{- |-A data type for annotating things with their source. In this library we only use @Annotated [Node]@, which stands for “some Markdown nodes + source”.+{- | A data type for annotating things with their source. In this library we+only use @WithSource [Node]@, which stands for “some Markdown nodes + source”. -}-data Annotated a = Ann {-  annSource :: Text,-  annValue  :: a }+data WithSource a = WithSource Text a   deriving (Eq, Show, Functor, Foldable, Traversable) -instance Monoid a => Monoid (Annotated a) where-  mempty = Ann "" mempty-  Ann s1 v1 `mappend` Ann s2 v2 = Ann (s1 <> s2) (v1 <> v2)+-- | Extract source from 'WithSource' (it's stored there in a field).+getSource :: WithSource a -> Text+getSource (WithSource src _) = src -{- |-A section in the Markdown tree. Does not contain subsections (the tree is built using 'Tree.Forest' from "Data.Tree").+-- | Extract data from 'WithSource'.+stripSource :: WithSource a -> a+stripSource (WithSource _ x) = x++instance Monoid a => Monoid (WithSource a) where+  mempty = WithSource "" mempty+  WithSource s1 v1 `mappend` WithSource s2 v2 =+    WithSource (s1 <> s2) (v1 <> v2)++{- | A section in the Markdown tree.++Sections do not contain subsections; i.e. `Section` isn't recursive and the+tree structure is provided by "Data.Tree".++In a @Section a b@, the heading is coupled with a value of type @a@, and+content – with a value of type @b@. This is occasionally useful. -} data Section a b = Section {   -- | Level (from 1 to 6).-  level      :: Int,-  heading    :: Annotated [Node],-  headingAnn :: a,+  level   :: Int,+  -- | Heading+  heading :: (a, WithSource [Node]),   -- | Text between the heading and the first subsection. Can be empty.-  content    :: Annotated [Node],-  contentAnn :: b }+  content :: (b, WithSource [Node])+  }   deriving (Eq, Show) -{- |-The whole parsed Markdown tree. The first parameter is the type of annotations for headings (i.e. sections), the second – chunks of text (which are all associated with sections except for the preface).+{- | The whole parsed Markdown tree. In a @Document a b@, headings are+annotated with @a@ and content blocks – with @b@. -} data Document a b = Document {   -- | Text before the first section. Can be empty.-  preface    :: Annotated [Node],-  prefaceAnn :: b,-  sections   :: Tree.Forest (Section a b) }+  preface  :: (b, WithSource [Node]),+  sections :: Tree.Forest (Section a b) }   deriving (Eq, Show) -{- |-'commonmarkToAnnotatedNodes' parses Markdown with the given options and extracts nodes from the initial 'DOCUMENT' node.+{- | 'commonmarkToNodesWithSource' parses Markdown with the given options and+extracts nodes from the initial 'DOCUMENT' node. -}-commonmarkToAnnotatedNodes :: [CMarkOption] -> Text -> Annotated [Node]-commonmarkToAnnotatedNodes opts s = Ann s ns+commonmarkToNodesWithSource :: [CMarkOption] -> Text -> WithSource [Node]+commonmarkToNodesWithSource opts src = WithSource src ns   where-    Node _ DOCUMENT ns = commonmarkToNode opts s+    Node _ DOCUMENT ns = commonmarkToNode opts src  {- | Break Markdown into pieces:  @-    blah blah blah               }-                                 }----> init-    blah blah blah               }+    blah blah blah               }---- preface+                                 }----+    blah blah blah               }---- -    # foo                        }-                                 }-    blah blah                    }----> (heading, blocks after)+    # foo                        }---- heading                                  }-    blah blah                    }--    ## bar                       }-                                 }----> (heading, blocks after)-    blah blah                    }+    blah blah                    }---- blocks after+                                 }----+    blah blah                    }---- -    ...+    ## bar                       }---- heading+                                 }+    blah blah                    }---- blocks after+                                 }----+    ...                          }---- @ -} breakAtHeadings@@ -181,40 +197,39 @@ cut   :: Node      -- ^ First node to include   -> Node      -- ^ First node to exclude-  -> Text+  -> Text      -- ^ Source that was parsed   -> Text cut a b = T.unlines . take (start b - start a) . drop (start a - 1) . T.lines  cutTo-  :: Node-  -> Text+  :: Node      -- ^ First node to exclude+  -> Text      -- ^ Source that was parsed   -> Text cutTo b = T.unlines . take (start b - 1) . T.lines  cutFrom-  :: Node-  -> Text+  :: Node      -- ^ First node to include+  -> Text      -- ^ Source that was parsed   -> Text cutFrom a = T.unlines . drop (start a - 1) . T.lines -{- |-Turn a list of Markdown nodes into a tree.+{- | Turn a list of Markdown nodes into a tree. -}-nodesToDocument :: Annotated [Node] -> Document () ()-nodesToDocument (Ann src nodes) = do+nodesToDocument :: WithSource [Node] -> Document () ()+nodesToDocument (WithSource src nodes) = do   -- Break at headings   let prefaceNodes :: [Node]       restNodes :: [(Node, [Node])]       (prefaceNodes, restNodes) = breakAtHeadings nodes   -- Annotate the first block with the source. If there are no headings at   -- all, we just copy everything; otherwise we cut until the first heading.-  let prefaceAnnotated :: Annotated [Node]+  let prefaceAnnotated :: WithSource [Node]       prefaceAnnotated = case restNodes of-        []    -> Ann src prefaceNodes-        (x:_) -> Ann (cutTo (fst x) src) prefaceNodes+        []    -> WithSource src prefaceNodes+        (x:_) -> WithSource (cutTo (fst x) src) prefaceNodes   -- Annotate other blocks with their sources by cutting until the position   -- of the next block-  let blocks :: [((Int, Annotated [Node]), Annotated [Node])]+  let blocks :: [((Int, WithSource [Node]), WithSource [Node])]       blocks = do         ((heading, afterBlocks), mbNext) <-             zip restNodes (tail (map Just restNodes ++ [Nothing]))@@ -227,40 +242,51 @@               ([], _)            -> ""               (x:_, Just (y, _)) -> cut x y src               (x:_, Nothing)     -> cutFrom x src-        return ((hLevel, Ann hSrc hNodes),-                Ann afterBlocksSrc afterBlocks)+        return ((hLevel, WithSource hSrc hNodes),+                WithSource afterBlocksSrc afterBlocks)   -- A function for turning blocks into a tree   let makeTree [] = []       makeTree (((level, heading), content) : xs) =         let (nested, others) = span (\x -> x^._1._1 > level) xs             section = Section {-              headingAnn = (),-              contentAnn = (),-              .. }+              level   = level,+              heading = ((), heading),+              content = ((), content)+              }         in  Tree.Node section (makeTree nested) : makeTree others   -- Return the result   Document {-    preface    = prefaceAnnotated,-    prefaceAnn = (),-    sections   = makeTree blocks }+    preface  = ((), prefaceAnnotated),+    sections = makeTree blocks+    }  {- $monoid-note -Note that you can use ('<>') to combine 'Annotated' nodes together.+Note that you can use ('<>') to combine 'WithSource' nodes together. It will+concatenate sources and parsed Markdown.++I'm not sure how valid this operation is for Markdown, but probably+more-or-less valid (when you exclude corner cases like missing newlines at+the end and duplicate links). Maybe cmark doesn't even allow duplicate links,+I don't know. -} -flattenDocument :: Document a b -> Annotated [Node]-flattenDocument Document{..} = preface <> flattenForest sections+-- | Turn the whole parsed-and-broken-down 'Document' into a list of nodes.+flattenDocument :: Document a b -> WithSource [Node]+flattenDocument Document{..} = snd preface <> flattenForest sections -flattenSection :: Section a b -> Annotated [Node]+-- | Turn a section into a list of nodes.+flattenSection :: Section a b -> WithSource [Node] flattenSection Section{..} =-  Ann (annSource heading <> annSource content)-      (headingNode : annValue content)+  WithSource (getSource (snd heading) <> getSource (snd content))+             (headingNode : stripSource (snd content))   where-    headingNode = Node Nothing (HEADING level) (annValue heading)+    headingNode = Node Nothing (HEADING level) (stripSource (snd heading)) -flattenTree :: Tree.Tree (Section a b) -> Annotated [Node]+-- | Turn a "Data.Tree" 'Tree.Tree' into a list of nodes.+flattenTree :: Tree.Tree (Section a b) -> WithSource [Node] flattenTree (Tree.Node r f) = flattenSection r <> flattenForest f -flattenForest :: Tree.Forest (Section a b) -> Annotated [Node]+-- | Turn a "Data.Tree" 'Tree.Forest' into a list of nodes.+flattenForest :: Tree.Forest (Section a b) -> WithSource [Node] flattenForest = mconcat . map flattenSection . concatMap Tree.flatten
tests/Main.hs view
@@ -27,71 +27,66 @@ main :: IO () main = hspec $ do   let mkSect level heading content ns =-        Tree.Node Section{headingAnn = (),contentAnn = (),..} ns+        Tree.Node+          (Section level ((), heading) ((), content))+          ns   describe "converting:" $ do     it "empty document" $ do       let src = ""-          prefaceAnn = ()-          preface = mempty+          preface = ((), mempty)           sections = []-      nodesToDocument (commonmarkToAnnotatedNodes [] src)+      nodesToDocument (commonmarkToNodesWithSource [] src)         `shouldBe` Document{..}     it "spaces" $ do       let src = "  \n\n  \n"-          prefaceAnn = ()-          preface = Ann "  \n\n  \n" []+          preface = ((), WithSource "  \n\n  \n" [])           sections = []-      nodesToDocument (commonmarkToAnnotatedNodes [] src)+      nodesToDocument (commonmarkToNodesWithSource [] src)         `shouldBe` Document{..}     it "paragraph" $ do       let src = "x"-          prefaceAnn = ()-          preface = Ann "x" [-            Node (Just (PosInfo 1 1 1 1)) PARAGRAPH [text "x"] ]+          preface = ((), WithSource "x" [+            Node (Just (PosInfo 1 1 1 1)) PARAGRAPH [text "x"] ])           sections = []-      nodesToDocument (commonmarkToAnnotatedNodes [] src)+      nodesToDocument (commonmarkToNodesWithSource [] src)         `shouldBe` Document{..}     it "3 paragraphs" $ do       let src = T.unlines ["","x","","","y","","z",""]-          prefaceAnn = ()-          preface = Ann "\nx\n\n\ny\n\nz\n\n" [+          preface = ((), WithSource "\nx\n\n\ny\n\nz\n\n" [             Node (Just (PosInfo 2 1 2 1)) PARAGRAPH [text "x"],             Node (Just (PosInfo 5 1 5 1)) PARAGRAPH [text "y"],-            Node (Just (PosInfo 7 1 7 1)) PARAGRAPH [text "z"] ]+            Node (Just (PosInfo 7 1 7 1)) PARAGRAPH [text "z"] ])           sections = []-      nodesToDocument (commonmarkToAnnotatedNodes [] src)+      nodesToDocument (commonmarkToNodesWithSource [] src)         `shouldBe` Document{..}     it "headers" $ do       let src = T.unlines ["# 1", "", "## 2", "", "## 3"]-          prefaceAnn = ()-          preface = mempty+          preface = ((), mempty)           sections = [-            mkSect 1 (Ann "# 1\n\n" [text "1"]) mempty [-              mkSect 2 (Ann "## 2\n\n" [text "2"]) mempty [],-              mkSect 2 (Ann "## 3\n" [text "3"]) mempty [] ] ]-      nodesToDocument (commonmarkToAnnotatedNodes [] src)+            mkSect 1 (WithSource "# 1\n\n" [text "1"]) mempty [+              mkSect 2 (WithSource "## 2\n\n" [text "2"]) mempty [],+              mkSect 2 (WithSource "## 3\n" [text "3"]) mempty [] ] ]+      nodesToDocument (commonmarkToNodesWithSource [] src)         `shouldBe` Document{..}     it "headers+content" $ do       let src = T.unlines ["# 1", "", "## 2", "test", "## 3"]-          prefaceAnn = ()-          preface = mempty+          preface = ((), mempty)           sections = [-            mkSect 1 (Ann "# 1\n\n" [text "1"]) mempty [-              mkSect 2 (Ann "## 2\n" [text "2"])-                (Ann "test\n" [Node (Just (PosInfo 4 1 4 4)) PARAGRAPH-                               [text "test"]]) [],-              mkSect 2 (Ann "## 3\n" [text "3"]) mempty [] ] ]-      nodesToDocument (commonmarkToAnnotatedNodes [] src)+            mkSect 1 (WithSource "# 1\n\n" [text "1"]) mempty [+              mkSect 2 (WithSource "## 2\n" [text "2"])+                (WithSource "test\n" [Node (Just (PosInfo 4 1 4 4)) PARAGRAPH+                                      [text "test"]]) [],+              mkSect 2 (WithSource "## 3\n" [text "3"]) mempty [] ] ]+      nodesToDocument (commonmarkToNodesWithSource [] src)         `shouldBe` Document{..}     it "preface+headers" $ do       let src = T.unlines ["blah", "# 1", "", "## 2", "", "## 3"]-          prefaceAnn = ()-          preface = commonmarkToAnnotatedNodes [] "blah\n"+          preface = ((), commonmarkToNodesWithSource [] "blah\n")           sections = [-            mkSect 1 (Ann "# 1\n\n" [text "1"]) mempty [-              mkSect 2 (Ann "## 2\n\n" [text "2"]) mempty [],-              mkSect 2 (Ann "## 3\n" [text "3"]) mempty [] ] ]-      nodesToDocument (commonmarkToAnnotatedNodes [] src)+            mkSect 1 (WithSource "# 1\n\n" [text "1"]) mempty [+              mkSect 2 (WithSource "## 2\n\n" [text "2"]) mempty [],+              mkSect 2 (WithSource "## 3\n" [text "3"]) mempty [] ] ]+      nodesToDocument (commonmarkToNodesWithSource [] src)         `shouldBe` Document{..}    describe "reconstruction:" $ do@@ -110,7 +105,7 @@     modifyMaxSize (*20) $ modifyMaxSuccess (*10) $       prop "QuickCheck" $         forAllShrink mdGen shrinkMD $ \(T.concat -> src) ->-          let md1 = commonmarkToAnnotatedNodes [] src+          let md1 = commonmarkToNodesWithSource [] src               md2 = flattenDocument . nodesToDocument $ md1               err = printf "%s: %s /= %s" (show src) (show md1) (show md2)           in  counterexample err (compareMD md1 md2)@@ -120,26 +115,26 @@  fromToDoc :: Text -> Expectation fromToDoc src =-  flattenDocument (nodesToDocument (commonmarkToAnnotatedNodes [] src))-    `shouldBeMD` commonmarkToAnnotatedNodes [] src+  flattenDocument (nodesToDocument (commonmarkToNodesWithSource [] src))+    `shouldBeMD` commonmarkToNodesWithSource [] src -shouldBeMD :: Annotated [Node] -> Annotated [Node] -> Expectation+shouldBeMD :: WithSource [Node] -> WithSource [Node] -> Expectation shouldBeMD x y = x `shouldSatisfy` (compareMD y)  -- | Check that pieces of Markdown are equivalent (modulo trailing newline -- and position info).-compareMD :: Annotated [Node] -> Annotated [Node] -> Bool+compareMD :: WithSource [Node] -> WithSource [Node] -> Bool compareMD x y =-  map (\(Node _ a b) -> Node Nothing a b) (annValue x) ==-  map (\(Node _ a b) -> Node Nothing a b) (annValue y)+  map (\(Node _ a b) -> Node Nothing a b) (stripSource x) ==+  map (\(Node _ a b) -> Node Nothing a b) (stripSource y)   &&-  or [annSource x == annSource y,-      and [not (T.isSuffixOf "\n" (annSource x)),-           T.isSuffixOf "\n" (annSource y),-           annSource x == T.init (annSource y)],-      and [not (T.isSuffixOf "\n" (annSource y)),-           T.isSuffixOf "\n" (annSource x),-           annSource y == T.init (annSource x)] ]+  or [getSource x == getSource y,+      and [not (T.isSuffixOf "\n" (getSource x)),+           T.isSuffixOf "\n" (getSource y),+           getSource x == T.init (getSource y)],+      and [not (T.isSuffixOf "\n" (getSource y)),+           T.isSuffixOf "\n" (getSource x),+           getSource y == T.init (getSource x)] ]  -- | Try to shrink Markdown. shrinkMD :: [Text] -> [[Text]]