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 +8/−1
- README.md +0/−0
- app/Main.hs +6/−7
- embedded/default-config.yaml +6/−0
- headroom.cabal +3/−5
- src/Headroom/Command.hs +7/−0
- src/Headroom/Command/Run.hs +25/−12
- src/Headroom/Configuration.hs +1/−0
- src/Headroom/FileSupport.hs +13/−16
- src/Headroom/FileSystem.hs +33/−10
- src/Headroom/Regex.hs +55/−0
- src/Headroom/Types.hs +12/−4
- test/Headroom/FileSystemSpec.hs +12/−0
- test/Headroom/RegexSpec.hs +32/−0
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