packages feed

headroom 0.2.0.0 → 0.2.1.0

raw patch · 14 files changed

+213/−55 lines, 14 filesdep −lensdep −pcre-heavydep −validationPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies removed: lens, pcre-heavy, validation

API changes (from Hackage documentation)

+ Headroom.FileSystem: excludePaths :: [Text] -> [FilePath] -> [FilePath]
+ Headroom.Regex: compile' :: Text -> Regex
+ Headroom.Regex: joinPatterns :: [Text] -> Maybe Text
+ Headroom.Regex: match' :: Regex -> Text -> Maybe [Text]
+ Headroom.Types: NoExcludedPaths :: ConfigurationError
+ Headroom.Types: [cExcludedPaths] :: Configuration -> ![Text]
+ Headroom.Types: [croDryRun] :: CommandRunOptions -> !Bool
+ Headroom.Types: [croExcludedPaths] :: CommandRunOptions -> ![Text]
+ Headroom.Types: [pcExcludedPaths] :: PartialConfiguration -> !Last [Text]
- Headroom.Types: CommandRunOptions :: !Maybe RunMode -> ![FilePath] -> ![FilePath] -> ![Text] -> !Bool -> CommandRunOptions
+ Headroom.Types: CommandRunOptions :: !Maybe RunMode -> ![FilePath] -> ![Text] -> ![FilePath] -> ![Text] -> !Bool -> !Bool -> CommandRunOptions
- Headroom.Types: Configuration :: !RunMode -> ![FilePath] -> ![FilePath] -> !HashMap Text Text -> !HeadersConfig -> Configuration
+ Headroom.Types: Configuration :: !RunMode -> ![FilePath] -> ![Text] -> ![FilePath] -> !HashMap Text Text -> !HeadersConfig -> Configuration
- Headroom.Types: PartialConfiguration :: !Last RunMode -> !Last [FilePath] -> !Last [FilePath] -> !Last (HashMap Text Text) -> !PartialHeadersConfig -> PartialConfiguration
+ Headroom.Types: PartialConfiguration :: !Last RunMode -> !Last [FilePath] -> !Last [Text] -> !Last [FilePath] -> !Last (HashMap Text Text) -> !PartialHeadersConfig -> PartialConfiguration
- Headroom.Types: Run :: [FilePath] -> [FilePath] -> [Text] -> Maybe RunMode -> Bool -> Command
+ Headroom.Types: Run :: [FilePath] -> [Text] -> [FilePath] -> [Text] -> Maybe RunMode -> Bool -> Bool -> Command

Files

CHANGELOG.md view
@@ -1,7 +1,14 @@ # Changelog All notable changes to this project will be documented in this file. -## 0.2.0.0 (not released yet)+## 0.2.1.0 (released 2020-04-29)+- [#41] Add `--dry-run` option to allow test run without changing files.+- [#44] Don't touch files whose contents have not changed.+- [#46] Add `-e|--excluded-path=REGEX` option to exclude source paths.+- Bump _LTS Haskell_ to `15.10`.+- Remove unused dependencies.++## 0.2.0.0 (released 2020-04-25) - [#28] Allow license headers to be anywhere in the file, not only at the very beginning. - [#31] Render templates for each source file instead of once (blocker for [#25]) - [#32] Allow custom user configuration for license headers.
README.md view
app/Main.hs view
@@ -13,7 +13,7 @@ {-# LANGUAGE LambdaCase        #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-}-+{-# LANGUAGE RecordWildCards   #-} module Main where  import           Headroom.Command               ( commandParser )@@ -46,9 +46,8 @@ bootstrap :: Command -> IO () bootstrap = \case   c@(Gen _ _) -> do-    genMode <- parseGenMode c-    commandGen (CommandGenOptions genMode)-  Init licenseType sourcePaths ->-    commandInit (CommandInitOptions sourcePaths licenseType)-  Run sourcePaths templatePaths variables runMode debug -> commandRun-    (CommandRunOptions runMode sourcePaths templatePaths variables debug)+    cgoGenMode <- parseGenMode c+    commandGen CommandGenOptions { .. }+  Init cioLicenseType cioSourcePaths -> commandInit CommandInitOptions { .. }+  Run croSourcePaths croExcludedPaths croTemplatePaths croVariables croRunMode croDebug croDryRun+    -> commandRun CommandRunOptions { .. }
embedded/default-config.yaml view
@@ -19,6 +19,12 @@ ## the user. # source-paths: [] +## Allows to define list of regular expressions that will be matched against+## 'source-paths' and such paths will be excluded from processing. Same as+## '-e|--excluded-path=REGEX' command line argument (can be used multiple times+## for more than one path).+excluded-paths: []+ ## Paths to template files (either files or directories), ## same as '-t|--template-path=PATH' command line argument (can be used multiple ## times for more than one path).
headroom.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.12 name: headroom-version: 0.2.0.0+version: 0.2.1.0 license: BSD3 license-file: LICENSE copyright: Copyright (c) 2019-2020 Vaclav Svejcar@@ -123,6 +123,7 @@         Headroom.FileSystem         Headroom.FileType         Headroom.Meta+        Headroom.Regex         Headroom.Serialization         Headroom.Template         Headroom.Template.Mustache@@ -143,16 +144,13 @@         base >=4.7 && <5,         either >=5.0.1.1 && <5.1,         file-embed >=0.0.11.2 && <0.1,-        lens >=4.18.1 && <4.19,         mustache >=2.3.1 && <2.4,         optparse-applicative >=0.15.1.0 && <0.16,-        pcre-heavy >=1.0.0.2 && <1.1,         pcre-light >=0.4.1.0 && <0.5,         rio >=0.1.15.0 && <0.2,         template-haskell >=2.15.0.0 && <2.16,         text >=1.2.4.0 && <1.3,         time >=1.9.3 && <1.10,-        validation ==1.1.*,         yaml >=0.11.3.0 && <0.12  executable headroom@@ -199,6 +197,7 @@         Headroom.FileSupportSpec         Headroom.FileSystemSpec         Headroom.FileTypeSpec+        Headroom.RegexSpec         Headroom.SerializationSpec         Headroom.Template.MustacheSpec         Headroom.Types.EnumExtraSpec@@ -217,6 +216,5 @@         headroom -any,         hspec >=2.7.1 && <2.8,         optparse-applicative >=0.15.1.0 && <0.16,-        pcre-heavy >=1.0.0.2 && <1.1,         pcre-light >=0.4.1.0 && <0.5,         rio >=0.1.15.0 && <0.2
src/Headroom/Command.hs view
@@ -68,6 +68,12 @@           )     <*> many           (strOption+            (long "excluded-path" <> short 'e' <> metavar "REGEX" <> help+              "path to exclude from source code file paths"+            )+          )+    <*> many+          (strOption             (long "template-path" <> short 't' <> metavar "PATH" <> help               "path to header template file/directory"             )@@ -96,6 +102,7 @@                 )           )     <*> switch (long "debug" <> help "produce more verbose output")+    <*> switch (long "dry-run" <> help "execute dry run (no changes to files)")  genOptions :: Parser Command genOptions =
src/Headroom/Command/Run.hs view
@@ -33,7 +33,8 @@                                                 , extractFileInfo                                                 , replaceHeader                                                 )-import           Headroom.FileSystem            ( fileExtension+import           Headroom.FileSystem            ( excludePaths+                                                , fileExtension                                                 , findFilesByExts                                                 , findFilesByTypes                                                 , loadFile@@ -119,6 +120,7 @@            -> IO ()             -- ^ execution result commandRun opts = bootstrap (env' opts) (croDebug opts) $ do   logInfo $ display productInfo+  warnOnDryRun   startTS            <- liftIO getPOSIXTime   templates          <- loadTemplates   sourceFiles        <- findSourceFiles (M.keys templates)@@ -134,21 +136,28 @@     , displayShow (endTS - startTS)     , " second(s)."     ]+  warnOnDryRun +warnOnDryRun :: (HasLogFunc env, HasRunOptions env) => RIO env ()+warnOnDryRun = do+  CommandRunOptions {..} <- view runOptionsL+  when croDryRun $ logWarn "[!] Running with '--dry-run', no files are changed!" + findSourceFiles :: (HasConfiguration env, HasLogFunc env)                 => [FileType]                 -> RIO env [FilePath] findSourceFiles fileTypes = do   Configuration {..} <- view configurationL   logDebug $ "Using source paths: " <> displayShow cSourcePaths-  files <--    mconcat <$> mapM (findFilesByTypes cLicenseHeaders fileTypes) cSourcePaths-  logInfo $ mconcat ["Found ", display $ L.length files, " source file(s)"]-  pure files+  files <- mconcat <$> mapM (findFiles' cLicenseHeaders) cSourcePaths+  let files' = excludePaths cExcludedPaths files+  logInfo $ mconcat ["Found ", display $ L.length files', " source file(s)"]+  pure files'+  where findFiles' licenseHeaders = findFilesByTypes licenseHeaders fileTypes  -processSourceFiles :: (HasConfiguration env, HasLogFunc env)+processSourceFiles :: (HasConfiguration env, HasLogFunc env, HasRunOptions env)                    => Map FileType TemplateType                    -> [FilePath]                    -> RIO env (Int, Int)@@ -166,26 +175,29 @@   process (pr, (tt, ft, p)) = processSourceFile pr tt ft p  -processSourceFile :: (HasConfiguration env, HasLogFunc env)+processSourceFile :: (HasConfiguration env, HasLogFunc env, HasRunOptions env)                   => Progress                   -> TemplateType                   -> FileType                   -> FilePath                   -> RIO env Bool processSourceFile progress template fileType path = do-  Configuration {..} <- view configurationL-  fileContent        <- readFileUtf8 path+  Configuration {..}     <- view configurationL+  CommandRunOptions {..} <- view runOptionsL+  fileContent            <- readFileUtf8 path   let fileInfo = extractFileInfo fileType                                  (configByFileType cLicenseHeaders fileType)                                  fileContent       variables = cVariables <> fiVariables fileInfo   header                        <- renderTemplate variables template   (processed, action, message') <- chooseAction fileInfo header-  let message = if processed then message' else "Skipping file:        "+  let result  = action fileContent+      changed = processed && (fileContent /= result)+      message = if changed then message' else "Skipping file:        "   logDebug $ "File info: " <> displayShow fileInfo   logInfo $ mconcat [display progress, " ", display message, fromString path]-  writeFileUtf8 path (action fileContent)-  pure processed+  when (not croDryRun && changed) (writeFileUtf8 path result)+  pure changed   chooseAction :: (HasConfiguration env)@@ -252,6 +264,7 @@   pure PartialConfiguration     { pcRunMode        = maybe mempty pure (croRunMode runOptions)     , pcSourcePaths    = ifNot null (croSourcePaths runOptions)+    , pcExcludedPaths  = ifNot null (croExcludedPaths runOptions)     , pcTemplatePaths  = ifNot null (croTemplatePaths runOptions)     , pcVariables      = ifNot null variables     , pcLicenseHeaders = mempty
src/Headroom/Configuration.hs view
@@ -82,6 +82,7 @@ makeConfiguration PartialConfiguration {..} = do   cRunMode        <- lastOrError NoRunMode pcRunMode   cSourcePaths    <- lastOrError NoSourcePaths pcSourcePaths+  cExcludedPaths  <- lastOrError NoExcludedPaths pcExcludedPaths   cTemplatePaths  <- lastOrError NoTemplatePaths pcTemplatePaths   cVariables      <- lastOrError NoVariables pcVariables   cLicenseHeaders <- makeHeadersConfig pcLicenseHeaders
src/Headroom/FileSupport.hs view
@@ -13,7 +13,6 @@ {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-}-{-# LANGUAGE TemplateHaskell   #-} module Headroom.FileSupport   ( -- * File info extraction     extractFileInfo@@ -31,7 +30,10 @@   ) where -import           Control.Lens.TH                ( makeLensesFor )+import           Headroom.Regex                 ( compile'+                                                , joinPatterns+                                                , match'+                                                ) import           Headroom.Types                 ( FileInfo(..)                                                 , FileType(..)                                                 , HeaderConfig(..)@@ -41,16 +43,10 @@ import qualified RIO.HashMap                   as HM import qualified RIO.List                      as L import qualified RIO.Text                      as T-import           Text.Regex.PCRE.Light          ( Regex-                                                , compile-                                                , match-                                                )-import           Text.Regex.PCRE.Light.Char8    ( utf8 )+import           Text.Regex.PCRE.Light          ( Regex )  -makeLensesFor [("fiHeaderPos", "fiHeaderPosL")] ''FileInfo - -- | Extracts info about the processed file to be later used by the header -- detection/manipulation functions. extractFileInfo :: FileType     -- ^ type of the detected file@@ -175,14 +171,14 @@  -- | Finds very first line that matches the given /regex/ (numbered from zero). ----- >>> firstMatching (compile "^foo" [utf8]) ["some text", "foo bar", "foo baz", "last"]+-- >>> firstMatching (compile' "^foo") ["some text", "foo bar", "foo baz", "last"] -- Just 1 firstMatching :: Regex        -- /regex/ used for matching               -> [Text]       -- input lines               -> Maybe Int    -- matching line number firstMatching regex input = go input 0  where-  cond x = isJust $ match regex (encodeUtf8 x) []+  cond x = isJust $ match' regex x   go (x : _) i | cond x = Just i   go (_ : xs) i         = go xs (i + 1)   go []       _         = Nothing@@ -190,14 +186,14 @@  -- | Finds very last line that matches the given /regex/ (numbered from zero). ----- >>> lastMatching (compile "^foo" [utf8]) ["some text", "foo bar", "foo baz", "last"]+-- >>> lastMatching (compile' "^foo") ["some text", "foo bar", "foo baz", "last"] -- Just 2 lastMatching :: Regex        -- /regex/ used for matching              -> [Text]       -- input lines              -> Maybe Int    -- matching line number lastMatching regex input = go input 0 Nothing  where-  cond x = isJust $ match regex (encodeUtf8 x) []+  cond x = isJust $ match' regex x   go (x : xs) i _ | cond x = go xs (i + 1) (Just i)   go (_ : xs) i pos        = go xs (i + 1) pos   go []       _ pos        = pos@@ -232,9 +228,7 @@   sndSplitAt        = fromMaybe len (findSplit firstMatching sndSplit inLines)   inLines           = T.lines input   len               = L.length inLines-  findSplit f r i = compile' r >>= (`f` i)-  compile' [] = Nothing-  compile' ps = Just $ compile (encodeUtf8 $ T.intercalate "|" ps) [utf8]+  findSplit f ps i = compile' <$> joinPatterns ps >>= (`f` i)   -- TODO: https://github.com/vaclavsvejcar/headroom/issues/25@@ -248,3 +242,6 @@  stripLinesStart :: [Text] -> [Text] stripLinesStart = dropWhile (T.null . T.strip)++fiHeaderPosL :: Lens' FileInfo (Maybe (Int, Int))+fiHeaderPosL = lens fiHeaderPos (\x y -> x { fiHeaderPos = y })
src/Headroom/FileSystem.hs view
@@ -24,10 +24,16 @@   , createDirectory     -- * Working with Files Metadata   , fileExtension+    -- * Other+  , excludePaths   ) where  import           Headroom.FileType              ( listExtensions )+import           Headroom.Regex                 ( compile'+                                                , joinPatterns+                                                , match'+                                                ) import           Headroom.Types                 ( FileType                                                 , HeadersConfig(..)                                                 )@@ -42,20 +48,11 @@                                                 , takeExtension                                                 , (</>)                                                 )+import qualified RIO.List                      as L import qualified RIO.Text                      as T   --- | Returns file extension for given path (if file), or nothing otherwise.------ >>> fileExtension "path/to/some/file.txt"--- Just "txt"-fileExtension :: FilePath -> Maybe Text-fileExtension path = case takeExtension path of-  '.' : xs -> Just $ T.pack xs-  _        -> Nothing-- -- | Recursively finds files on given path whose filename matches the predicate. findFiles :: MonadIO m           => FilePath           -- ^ path to search@@ -102,8 +99,34 @@     pure $ concat paths  +-- | Returns file extension for given path (if file), or nothing otherwise.+--+-- >>> fileExtension "path/to/some/file.txt"+-- Just "txt"+fileExtension :: FilePath -> Maybe Text+fileExtension path = case takeExtension path of+  '.' : xs -> Just $ T.pack xs+  _        -> Nothing++ -- | Loads file content in UTF8 encoding. loadFile :: MonadIO m          => FilePath -- ^ file path          -> m Text   -- ^ file content loadFile = readFileUtf8+++-- | Takes list of patterns and file paths and returns list of file paths where+-- those matching the given patterns are excluded.+--+-- >>> excludePaths ["\\.hidden", "zzz"] ["foo/.hidden", "test/bar", "x/zzz/e"]+-- ["test/bar"]+excludePaths :: [Text]     -- ^ patterns describing paths to exclude+             -> [FilePath] -- ^ list of file paths+             -> [FilePath] -- ^ resulting list of file paths+excludePaths _        []    = []+excludePaths []       paths = paths+excludePaths patterns paths = go $ compile' <$> joinPatterns patterns+ where+  go Nothing      = paths+  go (Just regex) = L.filter (isNothing . match' regex . T.pack) paths
+ src/Headroom/Regex.hs view
@@ -0,0 +1,55 @@+{-|+Module      : Headroom.Regex+Description : Helper functions for regular expressions+Copyright   : (c) 2019-2020 Vaclav Svejcar+License     : BSD-3+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++Provides wrappers mainly around functions from "Text.Regex.PCRE.Light" that more+suits the needs of this application.+-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+module Headroom.Regex+  ( compile'+  , joinPatterns+  , match'+  )+where++import           RIO+import qualified RIO.Text                      as T+import           Text.Regex.PCRE.Light          ( Regex+                                                , compile+                                                )+import           Text.Regex.PCRE.Light.Char8    ( match+                                                , utf8+                                                )++++-- | Same as 'compile', but takes 'Text' on input and enables 'utf8' option+-- by default.+compile' :: Text  -- ^ regular expression to be compiled+         -> Regex -- ^ compiled regular expression+compile' regex = compile (encodeUtf8 regex) [utf8]+++-- | Joins list of patterns into single regex string. If the input list is+-- empty, 'Nothing' is returned.+--+-- >>> joinPatterns ["^foo", "^bar"]+-- Just "^foo|^bar"+joinPatterns :: [Text]     -- ^ list of patterns to join+             -> Maybe Text -- ^ joined patterns+joinPatterns [] = Nothing+joinPatterns ps = Just $ T.intercalate "|" ps+++-- | Same as 'match', but works with 'Text' and uses no additional options.+match' :: Regex        -- ^ a PCRE regular expression value produced by compile+       -> Text         -- ^ the subject text to match against+       -> Maybe [Text] -- ^ the result value+match' regex subject = fmap T.pack <$> match regex (T.unpack subject) []
src/Headroom/Types.hs view
@@ -123,6 +123,7 @@   | NoPutBefore !FileType      -- ^ no configuration for @put-before@   | NoRunMode                  -- ^ no configuration for @run-mode@   | NoSourcePaths              -- ^ no configuration for @source-paths@+  | NoExcludedPaths            -- ^ no configuration for @excluded-paths@   | NoTemplatePaths            -- ^ no configuration for @template-paths@   | NoVariables                -- ^ no configuration for @variables@   deriving (Eq, Show)@@ -137,9 +138,9 @@  -- | Application command. data Command-  = Run [FilePath] [FilePath] [Text] (Maybe RunMode) Bool -- ^ @run@ command-  | Gen Bool (Maybe (LicenseType, FileType))              -- ^ @gen@ command-  | Init LicenseType [FilePath]                           -- ^ @init@ command+  = Run [FilePath] [Text] [FilePath] [Text] (Maybe RunMode) Bool Bool -- ^ @run@ command+  | Gen Bool (Maybe (LicenseType, FileType))                          -- ^ @gen@ command+  | Init LicenseType [FilePath]                                       -- ^ @init@ command   deriving (Show)  --------------------------------------------------------------------------------@@ -161,9 +162,11 @@ data CommandRunOptions = CommandRunOptions   { croRunMode       :: !(Maybe RunMode) -- ^ used /Run/ command mode   , croSourcePaths   :: ![FilePath]      -- ^ source code file paths+  , croExcludedPaths :: ![Text]          -- ^ source paths to exclude   , croTemplatePaths :: ![FilePath]      -- ^ template file paths   , croVariables     :: ![Text]          -- ^ raw variables   , croDebug         :: !Bool            -- ^ whether to run in debug mode+  , croDryRun        :: !Bool            -- ^ whether to perform dry run   }   deriving (Eq, Show) @@ -218,6 +221,7 @@ data Configuration = Configuration   { cRunMode        :: !RunMode             -- ^ mode of the @run@ command   , cSourcePaths    :: ![FilePath]          -- ^ paths to source code files+  , cExcludedPaths  :: ![Text]              -- ^ excluded source paths   , cTemplatePaths  :: ![FilePath]          -- ^ paths to template files   , cVariables      :: !(HashMap Text Text) -- ^ variable values for templates   , cLicenseHeaders :: !HeadersConfig       -- ^ configuration of license headers@@ -269,6 +273,7 @@ data PartialConfiguration = PartialConfiguration   { pcRunMode        :: !(Last RunMode)             -- ^ mode of the @run@ command   , pcSourcePaths    :: !(Last [FilePath])          -- ^ paths to source code files+  , pcExcludedPaths  :: !(Last [Text])              -- ^ excluded source paths   , pcTemplatePaths  :: !(Last [FilePath])          -- ^ paths to template files   , pcVariables      :: !(Last (HashMap Text Text)) -- ^ variable values for templates   , pcLicenseHeaders :: !PartialHeadersConfig       -- ^ configuration of license headers@@ -311,6 +316,7 @@   parseJSON = withObject "PartialConfiguration" $ \obj -> do     pcRunMode        <- Last <$> obj .:? "run-mode"     pcSourcePaths    <- Last <$> obj .:? "source-paths"+    pcExcludedPaths  <- Last <$> obj .:? "excluded-paths"     pcTemplatePaths  <- Last <$> obj .:? "template-paths"     pcVariables      <- Last <$> obj .:? "variables"     pcLicenseHeaders <- obj .:? "license-headers" .!= mempty@@ -352,6 +358,7 @@   x <> y = PartialConfiguration     { pcRunMode        = pcRunMode x <> pcRunMode y     , pcSourcePaths    = pcSourcePaths x <> pcSourcePaths y+    , pcExcludedPaths  = pcExcludedPaths x <> pcExcludedPaths y     , pcTemplatePaths  = pcTemplatePaths x <> pcTemplatePaths y     , pcVariables      = pcVariables x <> pcVariables y     , pcLicenseHeaders = pcLicenseHeaders x <> pcLicenseHeaders y@@ -381,7 +388,7 @@                                 }  instance Monoid PartialConfiguration where-  mempty = PartialConfiguration mempty mempty mempty mempty mempty+  mempty = PartialConfiguration mempty mempty mempty mempty mempty mempty  instance Monoid PartialHeaderConfig where   mempty = PartialHeaderConfig mempty mempty mempty mempty mempty mempty@@ -432,6 +439,7 @@   NoPutBefore      fileType -> noProp "put-before" fileType   NoRunMode                 -> noFlag "run-mode"   NoSourcePaths             -> noFlag "source-paths"+  NoExcludedPaths           -> noFlag "excluded-paths"   NoTemplatePaths           -> noFlag "template-paths"   NoVariables               -> noFlag "variables"  where
test/Headroom/FileSystemSpec.hs view
@@ -47,3 +47,15 @@       filePaths <- listFiles "test-data/test-traverse/a.html"       let expected = ["test-data/test-traverse/a.html"]       sort filePaths `shouldBe` sort expected++  describe "excludePaths" $ do+    it "excludes paths matching selected pattern from input list" $ do+      let patterns = ["\\.stack-work", "remove\\.txt"]+          sample =+            [ "/foo/bar/.stack-work/xx"+            , "/hello/world"+            , "foo/bar/remove.txt"+            , "xx/yy"+            ]+          expected = ["/hello/world", "xx/yy"]+      excludePaths patterns sample `shouldBe` expected
+ test/Headroom/RegexSpec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+module Headroom.RegexSpec+  ( spec+  )+where++import           Headroom.Regex+import           RIO+import           Test.Hspec+++spec :: Spec+spec = do+  describe "joinPatterns" $ do+    it "returns Nothing for empty input" $ do+      joinPatterns [] `shouldBe` Nothing++    it "handles single item list input" $ do+      joinPatterns ["foo"] `shouldBe` Just "foo"++    it "handles multiple item list input" $ do+      joinPatterns ["^foo", "^bar"] `shouldBe` Just "^foo|^bar"+++  describe "match'" $ do+    it "matches regular expression against given sample" $ do+      let regex = compile' <$> joinPatterns ["foo", "bar"]+      (regex >>= (`match'` "xxx")) `shouldSatisfy` isNothing+      (regex >>= (`match'` "foz")) `shouldSatisfy` isNothing+      (regex >>= (`match'` "foosdas")) `shouldSatisfy` isJust+      (regex >>= (`match'` "barfoo")) `shouldSatisfy` isJust