csv: fix multiple includes in one rules file

This commit is contained in:
Simon Michael 2017-07-05 16:04:48 +01:00
parent 6614aab5d7
commit e94f04311d

View File

@ -1,9 +1,9 @@
{-# LANGUAGE CPP #-}
{-| {-|
A reader for CSV data, using an extra rules file to help interpret the data. A reader for CSV data, using an extra rules file to help interpret the data.
-} -}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
@ -41,6 +41,7 @@ import Data.Ord
import qualified Data.Set as S import qualified Data.Set as S
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Time.Calendar (Day) import Data.Time.Calendar (Day)
#if MIN_VERSION_time(1,5,0) #if MIN_VERSION_time(1,5,0)
import Data.Time.Format (parseTimeM, defaultTimeLocale) import Data.Time.Format (parseTimeM, defaultTimeLocale)
@ -367,22 +368,18 @@ parseRulesFile :: FilePath -> ExceptT String IO CsvRules
parseRulesFile f = parseRulesFile f =
liftIO (readFile' f >>= expandIncludes (takeDirectory f)) >>= parseAndValidateCsvRules f liftIO (readFile' f >>= expandIncludes (takeDirectory f)) >>= parseAndValidateCsvRules f
-- | Look for hledger rules file-style include directives in this text, -- | Inline all files referenced by include directives in this hledger CSV rules text, recursively.
-- and interpolate the included files, recursively. -- Included file paths may be relative to the directory of the provided file path.
-- Included file paths may be relative to the directory of the
-- provided file path.
-- This is a cheap hack to avoid rewriting the CSV rules parser. -- This is a cheap hack to avoid rewriting the CSV rules parser.
expandIncludes :: FilePath -> T.Text -> IO T.Text expandIncludes dir content = mapM (expandLine dir) (T.lines content) >>= return . T.unlines
expandIncludes basedir content = do where
let (ls,rest) = break (T.isPrefixOf "include") $ T.lines content expandLine dir line =
case rest of case line of
[] -> return $ T.unlines ls (T.stripPrefix "include " -> Just f) -> expandIncludes dir' =<< T.readFile f'
((T.stripPrefix "include" -> Just f):ls') -> do where
let f' = basedir </> dropWhile isSpace (T.unpack f) f' = dir </> dropWhile isSpace (T.unpack f)
basedir' = takeDirectory f' dir' = takeDirectory f'
included <- readFile' f' >>= expandIncludes basedir' _ -> return line
return $ T.unlines [T.unlines ls, included, T.unlines ls']
ls' -> return $ T.unlines $ ls ++ ls' -- should never get here
-- | An error-throwing action that parses this text as CSV conversion rules -- | An error-throwing action that parses this text as CSV conversion rules
-- and runs some extra validation checks. The file path is for error messages. -- and runs some extra validation checks. The file path is for error messages.