submark 0.2.0 → 0.3.0
raw patch · 11 files changed
+435/−117 lines, 11 filesdep +regex-tdfadep ~hlintdep ~optparse-applicative
Dependencies added: regex-tdfa
Dependency ranges changed: hlint, optparse-applicative
Files
- CHANGELOG.md +19/−1
- README.md +33/−15
- app/Main.hs +42/−25
- lint/Main.hs +12/−0
- src/Text/CommonMark/Sub.hs +64/−14
- submark.cabal +14/−19
- test/HLint.hs +0/−12
- test/Main.hs +1/−0
- test/Spec.hs +0/−1
- test/Text/CommonMark/QQ.hs +1/−1
- test/Text/CommonMark/SubSpec.hs +249/−29
CHANGELOG.md view
@@ -1,5 +1,23 @@-`subtoml` changelog+`submark` changelog ===================++Version 0.3.0+-------------++Released on March 11, 2022.++ - Added options from `--h1-regex` to `--h6-regex`. [[#2]]+ - Added `Text.CommonMark.Sub.HeadingPattern` type. [[#2]]+ - Added `Text.CommonMark.Sub.HeadingTitlePattern` type. [[#2]]+ - The signature of `Text.CommonMark.Sub.extractSection` function was changed+ from `Level -> (Text -> Text -> Bool) -> Text -> Node -> Node` to+ `HeadingPattern -> Node -> Node`. [[#2]]+ - The signature of `Text.CommonMark.Sub.matchesHeading` function was changed+ from `Level -> (Text -> Text -> Bool) -> Text -> Node -> Bool` to+ `HeadingPattern -> Node -> Bool`. [[#2]]++[#2]: https://github.com/dahlia/submark/issues/2+ Version 0.2.0 -------------
README.md view
@@ -1,17 +1,17 @@ `submark`: Extract a part from CommonMark/Markdown docs ======================================================= -[![CircleCI][circleci-badge]][circleci]+[![GitHub Actions][gh-actions-badge]][gh-actions] [![Hackage][hackage-badge]][hackage] `submark` is a CLI program to extract some particular section from a given CommonMark/Markdown document. I use it for myself to extract-the latest version section from the CHANGELOG.md file, and then reuse the text+the latest version section from the *CHANGELOG.md* file, and then reuse the text for the corresponding release note on GitHub releases, during automated release process which is run on CI. -[circleci-badge]: https://circleci.com/gh/dahlia/submark.svg?style=shield-[circleci]: https://circleci.com/gh/dahlia/submark+[gh-actions-badge]: https://github.com/dahlia/submark/actions/workflows/build.yaml/badge.svg+[gh-actions]: https://github.com/dahlia/submark/actions/workflows/build.yaml [hackage-badge]: https://img.shields.io/hackage/v/submark.svg [hackage]: https://hackage.haskell.org/package/submark @@ -19,22 +19,21 @@ Download & installation ----------------------- -For Linux x86_64, executable binaries are available on [GitHub releases][].-Each file is a single executable, and statically linked so that it's executable-as a standalone without dependencies.+Prebuilt binaries for the following platforms and architectures are available on+[GitHub releases]: -For other platforms, you need to build it by yourself. It's written in Haskell,-so you need to install [Haskell Stack][] first. It can be built in the same-way other Haskell programs are:+ - Linux (x86_64)+ - macOS (x86_64)+ - Windows (win64) +For other platforms and architectures, you need to build it by yourself.+It's written in Haskell, so you need to install [Haskell Stack] first.+It can be built in the same way other Haskell programs are:+ ~~~~~~~~ bash $ stack setup && stack install ~~~~~~~~ -I'm going to officially support executable binaries for other platforms-if anyone asks. (I thought `submark` wouldn't be useful on other than Linux-since the most of CI machines are Linux, but it might be wrong.)- [GitHub releases]: https://github.com/dahlia/submark/releases [Haskell Stack]: https://haskellstack.org/ @@ -78,6 +77,17 @@ (18 KB) -- 17 Dec 2004 ~~~~~~~~ +Options from `--h1-regex` to `--h6-regex` take a regular expression instead of+an exact heading text:++~~~~~~~~ bash+$ submark --h2-regex "^(Down|Up)load$" index.text+## Download++[Markdown 1.0.1](http://daringfireball.net/projects/downloads/Markdown_1.0.1.zip)+(18 KB) -- 17 Dec 2004+~~~~~~~~+ The leading heading can be omitted: ~~~~~~~~ bash@@ -99,7 +109,7 @@ (18 KB) -- 17 Dec 2004 ~~~~~~~~ -By Unix convention, `-` means pipe: +By Unix convention, `-` means pipe: ~~~~~~~~ bash $ submark --h2 "Download" - < index.text@@ -116,3 +126,11 @@ ~~~~~~~~ [1]: https://daringfireball.net/projects/markdown/index.text+++Software license+----------------++This software is distributed under [GPL 3].++[GPL 3]: https://www.gnu.org/licenses/gpl-3.0.html
app/Main.hs view
@@ -1,5 +1,6 @@+{-# LANGUAGE DisambiguateRecordFields #-}+{-# LANGUAGE NamedFieldPuns #-} import Data.Maybe-import Data.Semigroup ((<>)) import System.IO import CMark@@ -9,9 +10,7 @@ import Text.CommonMark.Sub -data HeadingPattern = HeadingPattern Level Text deriving (Eq, Ord, Show)--data Extraction = Extraction +data Extraction = Extraction { outputFilePath :: FilePath , headingPattern :: HeadingPattern , ignoreCase :: Bool@@ -20,54 +19,74 @@ , inputFilePath :: FilePath } deriving (Eq, Ord, Show) -headingLevel :: Parser HeadingPattern-headingLevel =+headingPatternParser :: Parser HeadingPattern+headingPatternParser = foldl (<|>) empty parsers where levels :: [Level] levels = [1..6] parsers :: [Parser HeadingPattern]- parsers =- [ (HeadingPattern l . pack) <$> strOption+ parsers = titleTextParsers ++ titleRegexParsers+ titleTextParsers :: [Parser HeadingPattern]+ titleTextParsers =+ [ HeadingPattern l False . HeadingTitleText . pack <$> strOption ( long ("h" ++ show l) <> metavar "TITLE" <> help ("Extract the section with the heading level " ++- show l ++ ". Mutually exclusive with other --h* " ++- "options")+ show l ++ ". Note that it tries to match to the " +++ "heading title with no markup, which means " +++ "--h1 \"foo bar\" matches to both `# foo bar' and " +++ "`# _foo_ **bar**'. Mutually exclusive with other " +++ "--h* and --h*-regex options.") ) | l <- levels ]+ titleRegexParsers :: [Parser HeadingPattern]+ titleRegexParsers =+ [ HeadingPattern l False . HeadingTitleRegex . pack <$> strOption+ ( long ("h" ++ show l ++ "-regex")+ <> metavar "PATTERN"+ <> help ("Similar to --h" ++ show l ++ " except that it takes " +++ "a regular expression. Note that it tries to match " +++ "to the heading title with no markup, which means " +++ "--h1 \"fo+ ba[rz]\" matches to both `# foo bar' and " +++ "`# _foooo_ **baz**'. Mutually exclusive with other " +++ "--h* and --h*-regex options.")+ )+ | l <- levels+ ] parser :: Parser Extraction parser = Extraction+ -- CHECK: Write docs in README.md for a new option. <$> strOption ( long "out-file" <> short 'o' <> metavar "FILE" <> value "-" <> showDefault- <> help "Write output to FILE"+ <> help "Write output to FILE." )- <*> headingLevel+ <*> headingPatternParser <*> switch ( long "ignore-case" <> short 'i'- <> help "Ignore case distinctions"+ <> help "Ignore case distinctions." ) <*> switch ( long "omit-heading" <> short 'O'- <> help "Omit a leading heading"+ <> help "Omit a leading heading." ) <*> ( Just . read <$> strOption ( long "columns" <> short 'c' <> metavar "WIDTH" <> help ("Limit the maximum characters per line of the output. " ++- "No limit by default")+ "No limit by default.") ) <|> pure Nothing ) <**> helper <*> strArgument ( metavar "FILE"- <> help "CommonMark/Markdown text to extract from"+ <> help "CommonMark/Markdown text to extract from." ) parserInfo :: ParserInfo Extraction@@ -93,21 +112,19 @@ withFile o WriteMode action' extract :: Extraction -> IO ()-extract e@Extraction { headingPattern = (HeadingPattern level title)- , ignoreCase = ignoreCase'- , omitHeading = omitHeading'- , columnWidth = width+extract e@Extraction { headingPattern+ , ignoreCase+ , omitHeading+ , columnWidth } = do text <- withInputFile e TIO.hGetContents let doc = commonmarkToNode [] text- node = case (omitHeading', extractSection level equals title doc) of+ pat = headingPattern { caseInsensitive = ignoreCase }+ node = case (omitHeading, extractSection pat doc) of (True, Node p DOCUMENT (_ : xs)) -> Node p DOCUMENT xs (_, other) -> other- result = nodeToCommonmark [] (Just $ fromMaybe (-1) width) node+ result = nodeToCommonmark [] (Just $ fromMaybe (-1) columnWidth) node withOutputFile e (`TIO.hPutStrLn` result)- where- equals :: Text -> Text -> Bool- equals = if ignoreCase' then (\ a b -> toLower a == toLower b) else (==) main :: IO () main = execParser parserInfo >>= extract
+ lint/Main.hs view
@@ -0,0 +1,12 @@+import Language.Haskell.HLint+import System.Exit++arguments :: [String]+arguments = ["app", "src", "test"]++main :: IO ()+main = do+ hlints <- hlint arguments+ case hlints of+ [] -> exitSuccess+ _ -> exitFailure
src/Text/CommonMark/Sub.hs view
@@ -1,5 +1,10 @@+{-# LANGUAGE DisambiguateRecordFields #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-} module Text.CommonMark.Sub- ( Level+ ( HeadingPattern (..)+ , HeadingTitlePattern (..)+ , Level , extractSection , flattenInlineNodes , matchesHeading@@ -8,26 +13,71 @@ import Prelude hiding (concat) import CMark-import Data.Text (Text, concat, pack, strip)+import Data.Text (Text, concat, pack, strip, toLower)+import Text.Regex.TDFA+import Text.Regex.TDFA.Text -extractSection :: Level -> (Text -> Text -> Bool) -> Text -> Node -> Node-extractSection headingLevel equals headingTitle (Node pos DOCUMENT nodes) =+data HeadingPattern = HeadingPattern+ { headingLevel :: Level+ , caseInsensitive :: Bool+ , titlePattern :: HeadingTitlePattern+ } deriving (Eq, Ord, Show)++data HeadingTitlePattern+ = HeadingTitleRegex Text+ | HeadingTitleText Text+ deriving (Eq, Ord, Show)++extractSection :: HeadingPattern -> Node -> Node+extractSection pat@HeadingPattern { headingLevel } (Node pos DOCUMENT nodes) = Node pos DOCUMENT $ case headlessNodes of- (x : xs) -> x : takeWhile (isInSection headingLevel) xs+ (x : xs) -> x : takeWhile isInSection xs [] -> [] where- isInSection :: Level -> Node -> Bool- isInSection level (Node _ (HEADING lv) _) = lv > level- isInSection _ _ = True+ isInSection :: Node -> Bool+ isInSection (Node _ (HEADING lv) _) = lv > headingLevel+ isInSection _ = True headlessNodes :: [Node] headlessNodes =- dropWhile (not . matchesHeading headingLevel equals headingTitle) nodes-extractSection _ _ _ (Node pos _ _) = Node pos DOCUMENT []+ dropWhile (not . matchesHeading pat) nodes+extractSection _ (Node pos _ _) = Node pos DOCUMENT [] -matchesHeading :: Level -> (Text -> Text -> Bool) -> Text -> Node -> Bool-matchesHeading level equals text (Node _ (HEADING lv) nodes) =- lv == level && flattenInlineNodes nodes `equals` text-matchesHeading _ _ _ _ = False+matchesHeading :: HeadingPattern -> Node -> Bool+matchesHeading HeadingPattern { headingLevel+ , caseInsensitive+ , titlePattern+ } = case titlePattern of+ HeadingTitleText text -> if caseInsensitive+ then \ case+ (Node _ (HEADING lv) nodes) ->+ lv == headingLevel &&+ toLower (flattenInlineNodes nodes) == toLower text+ _ -> False+ else \ case+ (Node _ (HEADING lv) nodes) ->+ lv == headingLevel && flattenInlineNodes nodes == text+ _ -> False+ HeadingTitleRegex regexPat ->+ let compOption = CompOption+ { caseSensitive = not caseInsensitive+ , multiline = False+ , rightAssoc = True+ , newSyntax = True+ , lastStarGreedy = True+ }+ execOption = ExecOption { captureGroups = False }+ regex' = compile compOption execOption regexPat+ in+ case regex' of+ Left _ -> const False+ Right regex ->+ \ case+ (Node _ (HEADING lv) nodes) ->+ lv == headingLevel &&+ case execute regex (flattenInlineNodes nodes) of+ Right (Just _) -> True+ _ -> False+ _ -> False flattenInlineNodes :: [Node] -> Text flattenInlineNodes =
submark.cabal view
@@ -1,13 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.34.4. -- -- see: https://github.com/sol/hpack------ hash: 44a3cc6b647f89b49ca4596f50d12a252fc1f579338fb309629f51bb503696f7 name: submark-version: 0.2.0+version: 0.3.0 synopsis: Extract a part from CommonMark/Markdown docs category: Text stability: alpha@@ -39,10 +37,11 @@ Paths_submark hs-source-dirs: src- ghc-options: -fwarn-incomplete-uni-patterns+ ghc-options: -Wall -fwarn-incomplete-uni-patterns build-depends: base >=4.7 && <5 , cmark >=0.5.6 && <0.6.0+ , regex-tdfa >=1.3.1.2 && <1.4 , text ==1.* default-language: Haskell2010 @@ -52,48 +51,44 @@ Paths_submark hs-source-dirs: app- ghc-options: -fwarn-incomplete-uni-patterns+ ghc-options: -Wall -fwarn-incomplete-uni-patterns build-depends: base >=4.7 && <5 , cmark >=0.5.6 && <0.6.0- , optparse-applicative >=0.13.2.0 && <0.15.0.0+ , optparse-applicative >=0.13.2.0 && <0.17.0.0 , submark , text ==1.* if flag(static)- ghc-options: -fwarn-incomplete-uni-patterns -static -optl-static -optl-pthread -optc-Os -fPIC+ ghc-options: -Wall -fwarn-incomplete-uni-patterns -static -optl-static -optl-pthread -optc-Os -fPIC else- ghc-options: -fwarn-incomplete-uni-patterns+ ghc-options: -Wall -fwarn-incomplete-uni-patterns default-language: Haskell2010 test-suite hlint type: exitcode-stdio-1.0- main-is: HLint.hs+ main-is: Main.hs other-modules:- Spec- Text.CommonMark.QQ- Text.CommonMark.SubSpec Paths_submark hs-source-dirs:- test- ghc-options: -fwarn-incomplete-uni-patterns+ lint+ ghc-options: -Wall -fwarn-incomplete-uni-patterns build-depends: base >=4.7 && <5 , cmark >=0.5.6 && <0.6.0- , hlint >=2.0.9 && <3+ , hlint >=2.0.9 && <4 , text ==1.* default-language: Haskell2010 test-suite spec type: exitcode-stdio-1.0- main-is: Spec.hs+ main-is: Main.hs other-modules:- HLint Text.CommonMark.QQ Text.CommonMark.SubSpec Paths_submark hs-source-dirs: test- ghc-options: -fwarn-incomplete-uni-patterns+ ghc-options: -Wall -fwarn-incomplete-uni-patterns build-depends: base >=4.7 && <5 , cmark >=0.5.6 && <0.6.0
− test/HLint.hs
@@ -1,12 +0,0 @@-import Language.Haskell.HLint-import System.Exit--arguments :: [String]-arguments = ["app", "src", "test"]--main :: IO ()-main = do- hlints <- hlint arguments- case hlints of- [] -> exitSuccess- _ -> exitFailure
+ test/Main.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
− test/Spec.hs
@@ -1,1 +0,0 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
test/Text/CommonMark/QQ.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskellQuotes #-} module Text.CommonMark.QQ (doc, node, nodes) where import CMark
test/Text/CommonMark/SubSpec.hs view
@@ -2,16 +2,12 @@ {-# LANGUAGE QuasiQuotes #-} module Text.CommonMark.SubSpec (spec) where -import Data.Text import Test.Hspec import CMark (Node) import Text.CommonMark.QQ import Text.CommonMark.Sub -ciEq :: Text -> Text -> Bool-ciEq t t' = toLower t == toLower t'- source :: Node source = [doc|Doc title =======================@@ -65,31 +61,255 @@ spec :: Spec spec = do- specify "extractSection" $ do- extractSection 2 (==) "Section to extract" source `shouldBe` expected- extractSection 3 (==) "Section to extract" source `shouldNotBe` expected- extractSection 2 (==) "section to extract" source `shouldNotBe` expected- extractSection 3 (==) "section to extract" source `shouldNotBe` expected- -- Case insensitivity- extractSection 2 ciEq "Section to extract" source `shouldBe` expected- extractSection 3 ciEq "Section to extract" source `shouldNotBe` expected- extractSection 2 ciEq "section to extract" source `shouldBe` expected- extractSection 3 ciEq "section to extract" source `shouldNotBe` expected- specify "matchesHeading" $ do- [node|# Title|] `shouldSatisfy` matchesHeading 1 (==) "Title"- [node|### Title|] `shouldSatisfy` matchesHeading 3 (==) "Title"- [node|# Other title|] `shouldSatisfy`- matchesHeading 1 (==) "Other title"- [node|# Complex *title*|] `shouldSatisfy`- matchesHeading 1 (==) "Complex title"- [node|# Title|] `shouldNotSatisfy` matchesHeading 2 (==) "Title"- [node|# Title|] `shouldNotSatisfy` matchesHeading 1 (==) "Wrong title"- [node|# Title|] `shouldNotSatisfy` matchesHeading 1 (==) "title"- -- Case insensitivity- [node|# Title|] `shouldNotSatisfy` matchesHeading 1 (==) "title"- [node|# Title|] `shouldSatisfy` matchesHeading 1 ciEq "title"- [node|# Title|] `shouldNotSatisfy` matchesHeading 2 (==) "title"- [node|# Title|] `shouldNotSatisfy` matchesHeading 2 ciEq "title"+ describe "extractSection" $ do+ specify "w/ HeadingTitleText" $ do+ extractSection+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "Section to extract"+ }+ source+ `shouldBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "Section to extract"+ }+ source+ `shouldNotBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "section to extract"+ }+ source+ `shouldNotBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "section to extract"+ }+ source+ `shouldNotBe` expected+ -- Case insensitivity+ extractSection+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = True+ , titlePattern = HeadingTitleText "Section to extract"+ }+ source+ `shouldBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = True+ , titlePattern = HeadingTitleText "Section to extract"+ }+ source+ `shouldNotBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = True+ , titlePattern = HeadingTitleText "section to extract"+ }+ source+ `shouldBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = True+ , titlePattern = HeadingTitleText "section to extract"+ }+ source+ `shouldNotBe` expected+ specify "w/ HeadingTitleRegex" $ do+ extractSection+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = False+ , titlePattern = HeadingTitleRegex "to[[:space:]]+extract$"+ }+ source+ `shouldBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = False+ , titlePattern = HeadingTitleRegex "to[[:space:]]+extract$"+ }+ source+ `shouldNotBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = False+ , titlePattern = HeadingTitleRegex "TO[[:space:]]+EXTRACT$"+ }+ source+ `shouldNotBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = False+ , titlePattern = HeadingTitleRegex "TO[[:space:]]+EXTRACT$"+ }+ source+ `shouldNotBe` expected+ -- Case insensitivity+ extractSection+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = True+ , titlePattern = HeadingTitleRegex "to[[:space:]]+extract$"+ }+ source+ `shouldBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = True+ , titlePattern = HeadingTitleRegex "to[[:space:]]+extract$"+ }+ source+ `shouldNotBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = True+ , titlePattern = HeadingTitleRegex "TO[[:space:]]+EXTRACT$"+ }+ source+ `shouldBe` expected+ extractSection+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = True+ , titlePattern = HeadingTitleRegex "TO[[:space:]]+EXTRACT$"+ }+ source+ `shouldNotBe` expected+ describe "matchesHeading" $ do+ specify "w/ HeadingTitleText" $ do+ [node|# Title|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "Title"+ }+ [node|### Title|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "Title"+ }+ [node|# Other title|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "Other title"+ }+ [node|# Complex *title*|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "Complex title"+ }+ [node|# Title|] `shouldNotSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "Title"+ }+ [node|# Title|] `shouldNotSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "Wrong title"+ }+ [node|# Title|] `shouldNotSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern = HeadingTitleText "title"+ }+ -- Case insensitivity+ [node|# Title|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = True+ , titlePattern = HeadingTitleText "title"+ }+ [node|# Title|] `shouldNotSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = True+ , titlePattern = HeadingTitleText "title"+ }+ specify "w/ HeadingTitleRegex" $ do+ [node|# Title|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern = HeadingTitleRegex "^Title$"+ }+ [node|### Title|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 3+ , caseInsensitive = False+ , titlePattern = HeadingTitleRegex "^Title$"+ }+ [node|# Other title|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern =+ HeadingTitleRegex "^Other[[:space:]]+title$"+ }+ [node|# Complex *title*|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern =+ HeadingTitleRegex "^Complex[[:space:]]+title$"+ }+ [node|# Title|] `shouldNotSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = False+ , titlePattern = HeadingTitleRegex "^Title$"+ }+ [node|# Title|] `shouldNotSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern =+ HeadingTitleRegex "^Wrong[[:space:]]+title$"+ }+ [node|# Title|] `shouldNotSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = False+ , titlePattern = HeadingTitleRegex "$title$"+ }+ -- Case insensitivity+ [node|# Title|] `shouldSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 1+ , caseInsensitive = True+ , titlePattern = HeadingTitleRegex "^title$"+ }+ [node|# Title|] `shouldNotSatisfy` matchesHeading+ HeadingPattern+ { headingLevel = 2+ , caseInsensitive = True+ , titlePattern = HeadingTitleRegex "^title$"+ } specify "flattenInlineNodes" $ do flattenInlineNodes [nodes|*Test* nodes.|] `shouldBe` "Test nodes." flattenInlineNodes [nodes|Testing multiple paragraphs.