haskell-src-match (empty) → 0.0.0.1
raw patch · 7 files changed
+614/−0 lines, 7 filesdep +basedep +containersdep +filepath
Dependencies added: base, containers, filepath, haskell-src-exts, haskell-src-match, hspec, interpolatedstring-perl6, pretty-simple, split, template-haskell, text, transformers
Files
- ChangeLog.md +0/−0
- LICENSE +11/−0
- README.md +27/−0
- haskell-src-match.cabal +64/−0
- src/Language/Haskell/SourceMatch.hs +329/−0
- test/Spec.hs +1/−0
- test/TestSourceMatchSpec.hs +182/−0
+ ChangeLog.md view
+ LICENSE view
@@ -0,0 +1,11 @@+Copyright 2019-2020 Migamake Pte Ltd++Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,27 @@+# Goal++Leverage your knowledge of haskell-src-exts for parsing Haskell source files, and checking that they contain prescribed fragments.+This is a general problem of functional testing of code generators:+One would like to make it modular, but at the same time as close to produced source as possible.+I propose that you parse a Haskell output from code generator with haskell-src-exts and then match on the produced file.+Matching would be best done using TemplateHaskell.+Or you may also use Haskell-src-exts here in quasiquotes.+so this could be an independent library that we use to make xml-typelift and json-autotype better.++# Milestones++1. Parse input module with `haskell-src-exts`, and get a list of type declarations, and a list of instance declarations.+ (Easy)+2. Use `haskell-src-exts` to get a data structure from quasiquote.+3. Make a matching function out of a data structure returned by quasiquote.+4. Add convenience interface to parse output file with `haskell-src-exts` and match patterns.+5. Change all identifiers starting with an underbar (like `_function`) to be variables.+ Modify matching function accordingly+6. Change all type identifiers starting with an `U_` to be variables.+7. Change all class identitifers starting with an `U_` to be variables.+8. Make a test suite for xml-typelift using this.++# Possible improvements for the future++Not yet clear that we need this last feature for testing:+* Generate variable substitutions from successful match.
+ haskell-src-match.cabal view
@@ -0,0 +1,64 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: a1cd2efb7d734ba074effd214b51394ba67535d578ee19de1530ed5f7e221260++name: haskell-src-match+version: 0.0.0.1+synopsis: Testing code generators piece by piece.+description: Matching of Haskell source fragments for code generator unit tests.+category: testing+homepage: https://github.com/migamake/haskell-src-match#readme+bug-reports: https://github.com/migamake/haskell-src-match/issues+author: Dmitry Krylov, Michal J. Gajda+maintainer: haskell-src-match@migamake.com+copyright: 2018-2019, Migamake Pte Ltd+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/migamake/haskell-src-match++library+ exposed-modules:+ Language.Haskell.SourceMatch+ other-modules:+ Paths_haskell_src_match+ hs-source-dirs:+ src+ build-depends:+ base >=4.3 && <4.15+ , containers+ , haskell-src-exts+ , interpolatedstring-perl6+ , pretty-simple+ , split+ , template-haskell+ , transformers+ default-language: Haskell2010++test-suite match-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ TestSourceMatchSpec+ Paths_haskell_src_match+ hs-source-dirs:+ test+ build-depends:+ base+ , filepath+ , haskell-src-match+ , hspec+ , interpolatedstring-perl6+ , template-haskell+ , text+ default-language: Haskell2010
+ src/Language/Haskell/SourceMatch.hs view
@@ -0,0 +1,329 @@+-- | This module will implement pattern matching on Haskell source as parsed by `haskell-src-exts`.+--+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TemplateHaskell #-}+module Language.Haskell.SourceMatch+ ( isStringMatchedToDecl+ , isFileMatchedToDecl+ , MatchResult+ --+ , T_1, T_2, T_3, T_4, T_5, T_6, T_7, T_8, T_9, T_10+ ) where+++import Control.Monad+import Control.Monad.Trans.Class as T+import Control.Monad.Trans.Except+import Control.Monad.Trans.State+import Data.Either+import Data.List+import Data.List.Split+import Data.Map.Strict (Map)+import Data.Maybe+import Language.Haskell.Exts+import Language.Haskell.Exts.Parser+import Language.Haskell.Exts.Syntax as HS+import Language.Haskell.TH as TH+import Language.Haskell.TH.Lib+import Language.Haskell.TH.Quote(QuasiQuoter(..))+import Language.Haskell.TH.Syntax as TH+import Text.InterpolatedString.Perl6 (qc)+import qualified Data.Map.Strict as Map+import qualified Language.Haskell.TH.Syntax as TH++-- | Dummy Types to use as metavariables+data T_1+data T_2+data T_3+data T_4+data T_5+data T_6+data T_7+data T_8+data T_9+data T_10+++matchExpr, matchDecl :: QuasiQuoter+-- | This should return TH expression that will type to `Matches Exp`+matchExpr = QuasiQuoter {+ quoteExp = quoterToBeImplemented+ , quotePat = quoterUndefined+ , quoteType = quoterUndefined+ , quoteDec = quoterUndefined+ }+++quoterToBeImplemented _ = [| undefined |]+quoterUndefined _ = undefined+++-- | This should return TH expression that will type to `Matches Decl`+matchDecl = QuasiQuoter {+ quoteExp = quoterToBeImplemented+ , quotePat = quoterUndefined+ , quoteType = quoterUndefined+ , quoteDec = quoterUndefined+ }+++-- | All declarations for:+-- * `data` types+-- * `newtype` types+-- * `type` aliases+dataDecls :: HS.Module a -> Map.Map String [Decl a]+dataDecls = undefined+++instanceDecls :: HS.Module a -> Map.Map String [Decl a]+instanceDecls = undefined+++-- | Milestones for `Match`:+type Matches a = a -> Bool+-- ^ Above is key to writing first tests for Homplexity!+++defParseMode :: ParseMode+defParseMode = defaultParseMode+++-- | This function should read input string, and check that it contains given declaration.+--+isStringMatchedToDecl :: String -> DecsQ -> IO (Either MatchingError MatchResult)+isStringMatchedToDecl declarationStr expectedDecl =+ case parseDeclWithMode defParseMode declarationStr of+ ParseFailed _ errorMessage ->+ fail [qc|Can't parse "{declarationStr}", error occurs: {errorMessage}|] -- TODO+ ParseOk hsParseResult -> do+ allDecls@(thParseResult:_) <- runQ expectedDecl+ when (null allDecls) $ fail [qc|Empty `expectedDecl`|]+ when (length allDecls >= 2) $ fail [qc|Too long `expectedDecl` is not supported|]+ let result = tryMatch thParseResult hsParseResult+ return result+++isFileMatchedToDecl :: FilePath -> DecsQ -> IO (Either MatchingError MatchResult)+isFileMatchedToDecl filepath expectedDecl = do+ allDecls@(thParseResult:_) <- runQ expectedDecl+ when (null allDecls) $ fail [qc|Empty `expectedDecl`|]+ when (length allDecls >= 2) $ fail [qc|Too long `expectedDecl` is not supported|]+ --+ file <- readFile filepath+ let extensions = maybe [] snd $ readExtensions file+ case parseModuleWithMode (defParseMode { extensions = extensions }) file of+ ParseFailed _ errorMessage ->+ fail [qc|Can't parse "{filepath}", error occurs: {errorMessage}|]+ ParseOk (HS.Module _ _ _ _ decls) -> do+ let someMatchings = rights $ map (tryMatch thParseResult) decls+ case someMatchings of+ [] -> return $ Left (MatchingError "No matches")+ (m:_) -> return $ Right m+++tryMatch :: (Show a)+ => TH.Dec+ -> HS.Decl a+ -> Either MatchingError MatchResult+tryMatch thDecl hsDecl = runExcept $ execStateT (thToHsMatchDeclaration thDecl hsDecl) Map.empty+++--+-- * Matching itself+--++data MetaVariable = MetaVariableDecl String+ | MetaVariableConstructor String+ | MetaVariableVar String+ | MetaVariableType String+ deriving (Eq, Ord, Show)+++-- | Result of matching (metavariables unification)+--+type MatchResult = Map MetaVariable String+++newtype MatchingError = MatchingError String deriving Show+++matchError :: String -> MatchState a+matchError = T.lift . throwE . MatchingError+++-- | State of matching process+--+type MatchState a = StateT MatchResult (Except MatchingError) a+++--+-- * Some useful utilities for matching state+--++-- | Add match to match result. Fail if metavariable already matched+addMatch :: MetaVariable -> String -> MatchState ()+addMatch metaVar hsName = get >>= Map.alterF insertKey metaVar >>= put+ where+ insertKey Nothing = return $ Just hsName+ insertKey k@(Just hsName')+ | hsName == hsName' = return k+ | otherwise = matchError [qc|Var ({metaVar}, {hsName}) already matched|]+++-- | Check length of lists and `zipWithM` their+zipWithLengthCheckM_ :: (Show a, Show b)+ => String -> (a -> b -> MatchState c) -> [a] -> [b] -> MatchState ()+zipWithLengthCheckM_ listName f as bs+ | length as /= length bs = matchError [qc|{listName} list length not matched|]+ | otherwise = zipWithM_ f as bs+++--+-- * Matcher+--+++thToHsMatchDeclaration :: (Show a)+ => TH.Dec+ -> HS.Decl a+ -> MatchState ()+thToHsMatchDeclaration+ (TH.DataD _ thDataName _ _ thConstructors thDerivings)+ (HS.DataDecl _ (DataType _) _context (DHead _ hsDataName) hsConstructors hsDerivings) = do+ thToHsMatchIdents datatypeDeclMatcher thDataName hsDataName+ zipWithLengthCheckM_ "contstructors" thToHsMatchConstructors thConstructors hsConstructors+ zipWithLengthCheckM_ "derivings" thToHsMatchDerivings thDerivings hsDerivings+thToHsMatchDeclaration th hs = matchError [qc|Mismatch: {th} =/= {hs}|]+++thToHsMatchConstructors :: (Show a)+ => Con+ -> QualConDecl a+ -> MatchState ()+thToHsMatchConstructors+ (TH.RecC thConstructorName thFields)+ (HS.QualConDecl _ _ _ (RecDecl _ hsConstructorName hsFields)) = do+ thToHsMatchIdents constructorMatcher thConstructorName hsConstructorName+ zipWithLengthCheckM_ "fields" thToHsMatchConstructorArgs thFields hsFields+thToHsMatchConstructors th hs = matchError [qc|Constructors mismatch: {th} =/ {hs}|]+++thToHsMatchConstructorArgs :: (Show a)+ => TH.VarBangType+ -> HS.FieldDecl a+ -> MatchState ()+thToHsMatchConstructorArgs+ (thName, _thBang, thType)+ (HS.FieldDecl _ names@(hsName:_) hsType) = do+ when (null names) $ matchError "Number of TH names and HS names not matched"+ when (length names > 1) $ matchError "Many names is record declaration not supported"+ thToHsMatchIdents varMatcher thName hsName+ thToHsMatchTypes thType hsType+thToHsMatchConstructorArgs th hs = matchError [qc|Constructor args mismatch: {th} =/ {hs}|]+++thToHsMatchTypes :: (Show a)+ => TH.Type+ -> HS.Type a+ -> MatchState ()+thToHsMatchTypes+ (TH.AppT ListT (ConT tsType))+ (HS.TyList _ (TyCon _ hsType')) = do+ thToHsMatchIdents typenameMatcher tsType (hsQNameToName hsType')+thToHsMatchTypes+ (TH.ConT tsType)+ (HS.TyCon _ hsType') = do+ thToHsMatchIdents typenameMatcher tsType (hsQNameToName hsType')+thToHsMatchTypes th hs = matchError [qc|Types args mismatch: {th} =/ {hs}|]+++thToHsMatchDerivings :: (Show a)+ => TH.DerivClause+ -> HS.Deriving a+ -> MatchState ()+thToHsMatchDerivings+ (TH.DerivClause _ thTypes)+ (HS.Deriving _ _ instRules) = do+ let thDerivNames = map thDerivTypeToNameStr thTypes+ thDerivNames' = sort thDerivNames -- TODO Does order of derivings important or not?+ hsDerivNames = map hsInstRuleToNameStr instRules+ hsDerivNames' = sort hsDerivNames+ when (thDerivNames' /= hsDerivNames') $+ matchError [qc|Derivings TH: {thDerivNames'} is not matched to {hsDerivNames'}|]+ return ()+ where+ thDerivTypeToNameStr (ConT name) = thUnqualName name+ thDerivTypeToNameStr t = error [qc|Unsupported {t}|]+ hsInstRuleToNameStr (IRule _ _ _ (IHCon _ qname)) =+ case hsQNameToName qname of+ Ident _ sname -> sname+ Symbol _ sname -> sname+ hsInstRuleToNameStr inst = error [qc|Unsupported {inst}|]+ thUnqualName (Name (OccName occ) _) = occ+++thToHsMatchIdents :: (Show a) => IdentMatcher -> TH.Name -> HS.Name a -> MatchState ()+thToHsMatchIdents matcher thName (Ident _ hsName) =+ maybe (matchError [qc|TH: '{thNameStr}' is not matched to '{hsName}'|])+ (\(thName', hsName') -> addMatch thName' hsName')+ (matcher thNameStr hsName)+ where+ thNameStr = show thName+thToHsMatchIdents matcher thName hsName = matchError [qc|Mismatch: {thName} {hsName}|]+++--+-- * Utilities for matchers+--+++hsQNameToName :: (Show a) => HS.QName a -> HS.Name a+hsQNameToName (Qual _ _ name) = name+hsQNameToName (UnQual _ name) = name+hsQNameToName t = error [qc|Type identifier '{t}' unsupported|]+-- TODO implement qualification support+++type IdentMatcher = String -> String -> Maybe (MetaVariable, String)+++-- | Matcher for data types+datatypeDeclMatcher :: IdentMatcher+datatypeDeclMatcher th hs+ | hs `isPrefixOf` th = Just (MetaVariableDecl hs, hs)+ | otherwise = Nothing+++-- | Matcher for constructor (meta)variables+constructorMatcher :: IdentMatcher+constructorMatcher = templateMatcher False MetaVariableConstructor "C"+++-- | Matcher for variable (meta)variables+varMatcher :: IdentMatcher+varMatcher = templateMatcher False MetaVariableVar "v"+++-- | Matcher for type (meta)variables+typenameMatcher :: IdentMatcher+typenameMatcher th hs = templateMatcher True MetaVariableType "T" (removeQualificationIfTemplate th) hs+ where+ -- Main.T_2 -> T_2+ removeQualificationIfTemplate name =+ case splitOn "." name of+ [] -> error "Impossible happened"+ [_justOneName] -> name+ path -> last path+++templateMatcher :: Bool -> (String -> MetaVariable) -> String -> IdentMatcher+templateMatcher exactMatch metavarConstr prefix th hs+ | length sth >= (if exactMatch then 2 else 3) && head sth == prefix+ = if exactMatch then Just (metavarConstr th, hs)+ else Just (metavarConstr $ intercalate "_" (init sth), hs)+ | hs `isPrefixOf` th = Just (metavarConstr hs, hs)+ | otherwise = Nothing+ where+ sth = splitOn "_" th+
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/TestSourceMatchSpec.hs view
@@ -0,0 +1,182 @@+{-# LANGUAGE TemplateHaskell #-}+module TestSourceMatchSpec where+++import Data.Text (Text) -- for template declarations+import GHC.Generics+import Language.Haskell.SourceMatch+import Language.Haskell.TH.Lib+import System.FilePath.Posix+import Test.Hspec+++checkTopLevelWithVariables1 :: DecsQ+checkTopLevelWithVariables1 = [d|+ data TopLevel = C_1 {+ v_1 :: [T_2]+ } deriving (Show, Eq, GHC.Generics.Generic)|]+++checkTopLevelWithVariables2 :: DecsQ+checkTopLevelWithVariables2 = [d|+ data TopLevel = TopLevel {+ v_1 :: [T_2]+ } deriving (Show, Eq, GHC.Generics.Generic)|]+++checkTopLevelWithVariables3 :: DecsQ+checkTopLevelWithVariables3 = [d|+ data TopLevel = TopLevel {+ v_1 :: [T_2]+ } deriving (Eq, Show, GHC.Generics.Generic)|]+++checkTopLevelWithVariables4 :: DecsQ+checkTopLevelWithVariables4 = [d|+ data TopLevel = TopLevel {+ v_1 :: [String]+ } deriving (Eq, Show, GHC.Generics.Generic)|]+++checkTopLevelWithVariables5 :: DecsQ+checkTopLevelWithVariables5 = [d|+ data TopLevel = TopLevel {+ v :: [String]+ } deriving (Eq, Show, GHC.Generics.Generic)|]+++checkTopLevelWithVariables5file :: DecsQ+checkTopLevelWithVariables5file = [d|+ data TopLevel = TopLevel {+ topLevelColorsArray :: [T_1]+ } deriving (Eq, Show, GHC.Generics.Generic)|]+++-- * Wrong decls+++wrongCheckTopLevelWithVariables1 :: DecsQ+wrongCheckTopLevelWithVariables1 = [d|+ data TopLevel = C_1 {+ x_1 :: [T_2]+ } deriving (Show, Eq, GHC.Generics.Generic)|]+++wrongCheckTopLevelWithVariables2 :: DecsQ+wrongCheckTopLevelWithVariables2 = [d|+ data TopLevel = CropLevel {+ v_1 :: [T_2]+ } deriving (Show, Eq, GHC.Generics.Generic)|]+++wrongCheckTopLevelWithVariables3 :: DecsQ+wrongCheckTopLevelWithVariables3 = [d|+ data TopLevel = C_1 {+ v_1 :: [T_2]+ } deriving (Show, GHC.Generics.Generic)|]+++wrongCheckTopLevelWithVariables4 :: DecsQ+wrongCheckTopLevelWithVariables4 = [d|+ data TopLevel = C_1 {+ v_1 :: Maybe T_2+ } deriving (Show, Eq, GHC.Generics.Generic)|]+++wrongCheckTopLevelWithVariables5 :: DecsQ+wrongCheckTopLevelWithVariables5 = [d|+ data TopLevel = C_1+ { v_1 :: [T_2]+ , v_2 :: [T_3]+ } deriving (Eq, Show, GHC.Generics.Generic)|]+++miscFileCheck1 :: DecsQ+miscFileCheck1 = [d|+ data ColorsArrayElt = ColorsArrayElt {+ colorsArrayEltHexValue :: Text,+ colorsArrayEltColorName :: Text+ } deriving (Show, Eq, GHC.Generics.Generic)|]+++spec :: Spec+spec = describe "Key test operators" $ do+ describe "isStringMatchedToDecl" $ do+ let decl = "data TopLevel = TopLevel { v :: [String] } deriving (Show, Eq, GHC.Generics.Generic)"+ --+ it "matches with all metavariables" $ example $+ decl `stringShouldMatchDecl` checkTopLevelWithVariables1+ it "matches with constructor and var-metavariable" $ example $+ decl `stringShouldMatchDecl` checkTopLevelWithVariables2+ it "order of deriving is unimportant" $ example $+ decl `stringShouldMatchDecl` checkTopLevelWithVariables3+ it "matches with type and var-metavariable" $ example $+ decl `stringShouldMatchDecl` checkTopLevelWithVariables4+ it "matches with full-defined declaration" $ example $+ decl `stringShouldMatchDecl` checkTopLevelWithVariables5+ --+ it "doesn't match with wrong var-metavariable" $ example $+ decl `stringShouldNotMatchDecl` wrongCheckTopLevelWithVariables1+ it "doesn't match with wrong named constructor" $ example $+ decl `stringShouldNotMatchDecl` wrongCheckTopLevelWithVariables2+ it "doesn't match with shortened deriving list" $ example $+ decl `stringShouldNotMatchDecl` wrongCheckTopLevelWithVariables3+ it "doesn't match with wrong field type" $ example $+ decl `stringShouldNotMatchDecl` wrongCheckTopLevelWithVariables4+ it "doesn't match with different number of fields" $ example $+ decl `stringShouldNotMatchDecl` wrongCheckTopLevelWithVariables5+ describe "isFileMatchedToDecl" $ do+ --+ -- File match checking+ --+ it "matches with all metavariables" $ example $+ (testFile "colors.hs") `fileShouldMatchDecl` checkTopLevelWithVariables1+ it "matches with constructor and var-metavariable" $ example $+ (testFile "colors.hs") `fileShouldMatchDecl` checkTopLevelWithVariables2+ it "order of deriving is unimportant" $ example $+ (testFile "colors.hs") `fileShouldMatchDecl` checkTopLevelWithVariables3+ -- checkTopLevelWithVariables4 have other type+ it "matches with full-defined declaration" $ example $+ (testFile "colors.hs") `fileShouldMatchDecl` checkTopLevelWithVariables5file+ it "doesn't match with wrong var-metavariable" $ example $+ (testFile "colors.hs") `fileShouldNotMatchDecl` wrongCheckTopLevelWithVariables1+ it "doesn't match with wrong named constructor" $ example $+ (testFile "colors.hs") `fileShouldNotMatchDecl` wrongCheckTopLevelWithVariables2+ it "doesn't match with shortened deriving list" $ example $+ (testFile "colors.hs") `fileShouldNotMatchDecl` wrongCheckTopLevelWithVariables3+ it "doesn't match with wrong field type" $ example $+ (testFile "colors.hs") `fileShouldNotMatchDecl` wrongCheckTopLevelWithVariables4+ it "doesn't match with different number of fields" $ example $+ (testFile "colors.hs") `fileShouldNotMatchDecl` wrongCheckTopLevelWithVariables5+ it "match with other declarations in file" $ example $+ (testFile "colors.hs") `fileShouldMatchDecl` miscFileCheck1+++-- * Utils for testing+++stringShouldMatchDecl :: (HasCallStack) => String -> DecsQ -> Expectation+stringShouldMatchDecl str decl = do+ Right _ <- isStringMatchedToDecl str decl+ return ()+++stringShouldNotMatchDecl :: (HasCallStack) => String -> DecsQ -> Expectation+stringShouldNotMatchDecl str decl = do+ Left _ <- isStringMatchedToDecl str decl+ return ()+++fileShouldMatchDecl :: (HasCallStack) => FilePath -> DecsQ -> Expectation+fileShouldMatchDecl filepath decl = do+ Right _ <- isFileMatchedToDecl filepath decl+ return ()++fileShouldNotMatchDecl :: (HasCallStack) => FilePath -> DecsQ -> Expectation+fileShouldNotMatchDecl filepath decl = do+ Left _ <- isFileMatchedToDecl filepath decl+ return ()++testFile :: FilePath -> FilePath+testFile testFilepath = "test" </> "test-data" </> testFilepath+