{-|
Read extra CLI arguments from a hledger config file.
-}

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE MultiWayIf #-}

module Hledger.Cli.Conf (
   Conf
  ,SectionName
  ,getConf
  ,getConf'
  ,nullconf
  ,confLookup
  ,activeConfFile
  ,activeLocalConfFile
  ,activeUserConfFile
  ,confFiles
  ,userConfFiles
  ,parseConf
)
where

import Control.Exception (handle)
import Control.Monad (void, forM)
import Control.Monad.Identity (Identity)
import Data.Functor ((<&>))
import qualified Data.Map as M
import Data.Maybe (catMaybes)
import Data.Text (Text)
import qualified Data.Text as T (pack)
import Safe (headMay, lastDef)
import System.Directory (getHomeDirectory, getXdgDirectory, XdgDirectory (XdgConfig), doesFileExist, getCurrentDirectory)
import System.FilePath ((</>), takeDirectory)
import Text.Megaparsec as M
import Text.Megaparsec.Char

import Hledger (error', strip, words', RawOpts, expandPath)
import Hledger.Read.Common
import Hledger.Utils.Parse
import Hledger.Utils.Debug
import Hledger.Data.RawOptions (collectopts)


-- | A hledger config file.
data Conf = Conf {
   Conf -> FilePath
confFile :: FilePath
  -- ,confText :: String
  ,Conf -> Int
confFormat :: Int
  ,Conf -> [ConfSection]
confSections :: [ConfSection]
} deriving (Conf -> Conf -> Bool
(Conf -> Conf -> Bool) -> (Conf -> Conf -> Bool) -> Eq Conf
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Conf -> Conf -> Bool
== :: Conf -> Conf -> Bool
$c/= :: Conf -> Conf -> Bool
/= :: Conf -> Conf -> Bool
Eq,Int -> Conf -> ShowS
[Conf] -> ShowS
Conf -> FilePath
(Int -> Conf -> ShowS)
-> (Conf -> FilePath) -> ([Conf] -> ShowS) -> Show Conf
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Conf -> ShowS
showsPrec :: Int -> Conf -> ShowS
$cshow :: Conf -> FilePath
show :: Conf -> FilePath
$cshowList :: [Conf] -> ShowS
showList :: [Conf] -> ShowS
Show)

-- | One section in a hledger config file.
data ConfSection = ConfSection {
   ConfSection -> FilePath
csName :: SectionName
  ,ConfSection -> [FilePath]
csArgs :: [Arg]
} deriving (ConfSection -> ConfSection -> Bool
(ConfSection -> ConfSection -> Bool)
-> (ConfSection -> ConfSection -> Bool) -> Eq ConfSection
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ConfSection -> ConfSection -> Bool
== :: ConfSection -> ConfSection -> Bool
$c/= :: ConfSection -> ConfSection -> Bool
/= :: ConfSection -> ConfSection -> Bool
Eq,Int -> ConfSection -> ShowS
[ConfSection] -> ShowS
ConfSection -> FilePath
(Int -> ConfSection -> ShowS)
-> (ConfSection -> FilePath)
-> ([ConfSection] -> ShowS)
-> Show ConfSection
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ConfSection -> ShowS
showsPrec :: Int -> ConfSection -> ShowS
$cshow :: ConfSection -> FilePath
show :: ConfSection -> FilePath
$cshowList :: [ConfSection] -> ShowS
showList :: [ConfSection] -> ShowS
Show)

-- | The name of a config file section, with surrounding brackets and whitespace removed.
type SectionName = String

-- | A command line argument to be passed to CmdArgs.process.
-- It seems this should be a single command line argument (or flag or flag value).
-- If it contains spaces, those are treated as part of a single argument, as with CMD a "b c".
type Arg = String

nullconf :: Conf
nullconf = Conf {
   confFile :: FilePath
confFile = FilePath
""
  ,confFormat :: Int
confFormat = Int
1
  ,confSections :: [ConfSection]
confSections = []
}

-- | The --conf or --no-conf or default config file specified by command line options.
data ConfFileSpec =
    SomeConfFile FilePath  -- ^ use config file specified with --conf
  | NoConfFile             -- ^ don't use any config file (--no-conf)
  | AutoConfFile           -- ^ use the config file found by directory search (default)
  deriving (ConfFileSpec -> ConfFileSpec -> Bool
(ConfFileSpec -> ConfFileSpec -> Bool)
-> (ConfFileSpec -> ConfFileSpec -> Bool) -> Eq ConfFileSpec
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ConfFileSpec -> ConfFileSpec -> Bool
== :: ConfFileSpec -> ConfFileSpec -> Bool
$c/= :: ConfFileSpec -> ConfFileSpec -> Bool
/= :: ConfFileSpec -> ConfFileSpec -> Bool
Eq,Int -> ConfFileSpec -> ShowS
[ConfFileSpec] -> ShowS
ConfFileSpec -> FilePath
(Int -> ConfFileSpec -> ShowS)
-> (ConfFileSpec -> FilePath)
-> ([ConfFileSpec] -> ShowS)
-> Show ConfFileSpec
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ConfFileSpec -> ShowS
showsPrec :: Int -> ConfFileSpec -> ShowS
$cshow :: ConfFileSpec -> FilePath
show :: ConfFileSpec -> FilePath
$cshowList :: [ConfFileSpec] -> ShowS
showList :: [ConfFileSpec] -> ShowS
Show)

-- Get the conf file specification from options,
-- considering the rightmost --conf or --no-conf option if any.
confFileSpecFromRawOpts :: RawOpts -> ConfFileSpec
confFileSpecFromRawOpts :: RawOpts -> ConfFileSpec
confFileSpecFromRawOpts = ConfFileSpec -> [ConfFileSpec] -> ConfFileSpec
forall a. a -> [a] -> a
lastDef ConfFileSpec
AutoConfFile ([ConfFileSpec] -> ConfFileSpec)
-> (RawOpts -> [ConfFileSpec]) -> RawOpts -> ConfFileSpec
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((FilePath, FilePath) -> Maybe ConfFileSpec)
-> RawOpts -> [ConfFileSpec]
forall a. ((FilePath, FilePath) -> Maybe a) -> RawOpts -> [a]
collectopts (FilePath, FilePath) -> Maybe ConfFileSpec
cfsFromRawOpt
  where
    cfsFromRawOpt :: (FilePath, FilePath) -> Maybe ConfFileSpec
cfsFromRawOpt (FilePath
"conf",FilePath
f)    = ConfFileSpec -> Maybe ConfFileSpec
forall a. a -> Maybe a
Just (ConfFileSpec -> Maybe ConfFileSpec)
-> ConfFileSpec -> Maybe ConfFileSpec
forall a b. (a -> b) -> a -> b
$ FilePath -> ConfFileSpec
SomeConfFile FilePath
f
    cfsFromRawOpt (FilePath
"no-conf",FilePath
_) = ConfFileSpec -> Maybe ConfFileSpec
forall a. a -> Maybe a
Just (ConfFileSpec -> Maybe ConfFileSpec)
-> ConfFileSpec -> Maybe ConfFileSpec
forall a b. (a -> b) -> a -> b
$ ConfFileSpec
NoConfFile
    cfsFromRawOpt (FilePath, FilePath)
_             = Maybe ConfFileSpec
forall a. Maybe a
Nothing

-- config reading

-- | Fetch all the arguments/options defined in a section with this name, if it exists.
-- This should be "general" for the unnamed first section, or a hledger command name.
confLookup :: SectionName -> Conf -> [Arg]
confLookup :: FilePath -> Conf -> [FilePath]
confLookup FilePath
cmd Conf{[ConfSection]
confSections :: Conf -> [ConfSection]
confSections :: [ConfSection]
confSections} =
  [FilePath]
-> ([FilePath] -> [FilePath]) -> Maybe [FilePath] -> [FilePath]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] ((FilePath -> [FilePath]) -> [FilePath] -> [FilePath]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap FilePath -> [FilePath]
words') (Maybe [FilePath] -> [FilePath]) -> Maybe [FilePath] -> [FilePath]
forall a b. (a -> b) -> a -> b
$  -- XXX PARTIAL
  FilePath -> Map FilePath [FilePath] -> Maybe [FilePath]
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup FilePath
cmd (Map FilePath [FilePath] -> Maybe [FilePath])
-> Map FilePath [FilePath] -> Maybe [FilePath]
forall a b. (a -> b) -> a -> b
$
  [(FilePath, [FilePath])] -> Map FilePath [FilePath]
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList [(FilePath
csName,[FilePath]
csArgs) | ConfSection{FilePath
csName :: ConfSection -> FilePath
csName :: FilePath
csName,[FilePath]
csArgs :: ConfSection -> [FilePath]
csArgs :: [FilePath]
csArgs} <- [ConfSection]
confSections]

-- | Try to read a hledger config from a config file specified by --conf,
-- or the first config file found in any of several default file paths.
-- If --no-conf was used, or if no file was specified or found, this returns a null Conf.
-- If a specified file, or the first file found, can not be read or parsed, this returns an error message.
-- Otherwise this returns the parsed Conf, and the file path.
getConf :: RawOpts -> IO (Either String (Conf, Maybe FilePath))
getConf :: RawOpts -> IO (Either FilePath (Conf, Maybe FilePath))
getConf RawOpts
rawopts = do
  -- As in Cli.hs, conf debug output always goes to stderr;
  -- that's ok as conf is a hledger cli feature for now.
  case RawOpts -> ConfFileSpec
confFileSpecFromRawOpts RawOpts
rawopts of
    ConfFileSpec
NoConfFile     -> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either FilePath (Conf, Maybe FilePath)
 -> IO (Either FilePath (Conf, Maybe FilePath)))
-> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. (a -> b) -> a -> b
$ (Conf, Maybe FilePath) -> Either FilePath (Conf, Maybe FilePath)
forall a b. b -> Either a b
Right ((Conf, Maybe FilePath) -> Either FilePath (Conf, Maybe FilePath))
-> (Conf, Maybe FilePath) -> Either FilePath (Conf, Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ FilePath -> (Conf, Maybe FilePath) -> (Conf, Maybe FilePath)
forall a. FilePath -> a -> a
dbg1Msg FilePath
"ignoring config files" (Conf
nullconf, Maybe FilePath
forall a. Maybe a
Nothing)
    SomeConfFile FilePath
f -> IO FilePath
getCurrentDirectory IO FilePath -> (FilePath -> IO FilePath) -> IO FilePath
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (FilePath -> FilePath -> IO FilePath)
-> FilePath -> FilePath -> IO FilePath
forall a b c. (a -> b -> c) -> b -> a -> c
flip FilePath -> FilePath -> IO FilePath
expandPath FilePath
f IO FilePath
-> (FilePath -> IO (Either FilePath (Conf, Maybe FilePath)))
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= FilePath -> IO (Either FilePath (Conf, Maybe FilePath))
readConfFile (FilePath -> IO (Either FilePath (Conf, Maybe FilePath)))
-> ShowS -> FilePath -> IO (Either FilePath (Conf, Maybe FilePath))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> ShowS
forall a. Show a => FilePath -> a -> a
dbg1 FilePath
"using specified config file"
    ConfFileSpec
AutoConfFile   -> do
      [FilePath]
fs <- IO [FilePath]
confFiles
      case [FilePath]
fs of
        FilePath
f:[FilePath]
_ -> FilePath -> [FilePath] -> IO ()
forall (m :: * -> *) a.
(MonadIO m, Show a) =>
FilePath -> a -> m ()
dbg8IO FilePath
"found config files" [FilePath]
fs IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> FilePath -> FilePath -> IO ()
forall (m :: * -> *) a.
(MonadIO m, Show a) =>
FilePath -> a -> m ()
dbg1IO FilePath
"using nearest config file" FilePath
f IO ()
-> IO (Either FilePath (Conf, Maybe FilePath))
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> FilePath -> IO (Either FilePath (Conf, Maybe FilePath))
readConfFile FilePath
f
        []  -> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either FilePath (Conf, Maybe FilePath)
 -> IO (Either FilePath (Conf, Maybe FilePath)))
-> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. (a -> b) -> a -> b
$ (Conf, Maybe FilePath) -> Either FilePath (Conf, Maybe FilePath)
forall a b. b -> Either a b
Right ((Conf, Maybe FilePath) -> Either FilePath (Conf, Maybe FilePath))
-> (Conf, Maybe FilePath) -> Either FilePath (Conf, Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ FilePath -> (Conf, Maybe FilePath) -> (Conf, Maybe FilePath)
forall a. FilePath -> a -> a
dbg1Msg FilePath
"no config file found" (Conf
nullconf, Maybe FilePath
forall a. Maybe a
Nothing)

-- | Like getConf but throws an error on failure.
getConf' :: RawOpts -> IO (Conf, Maybe FilePath)
getConf' :: RawOpts -> IO (Conf, Maybe FilePath)
getConf' RawOpts
rawopts = RawOpts -> IO (Either FilePath (Conf, Maybe FilePath))
getConf RawOpts
rawopts IO (Either FilePath (Conf, Maybe FilePath))
-> (Either FilePath (Conf, Maybe FilePath)
    -> IO (Conf, Maybe FilePath))
-> IO (Conf, Maybe FilePath)
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (FilePath -> IO (Conf, Maybe FilePath))
-> ((Conf, Maybe FilePath) -> IO (Conf, Maybe FilePath))
-> Either FilePath (Conf, Maybe FilePath)
-> IO (Conf, Maybe FilePath)
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (FilePath -> IO (Conf, Maybe FilePath)
forall a. FilePath -> a
error' (FilePath -> IO (Conf, Maybe FilePath))
-> ShowS -> FilePath -> IO (Conf, Maybe FilePath)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShowS
forall a. Show a => a -> FilePath
show) (Conf, Maybe FilePath) -> IO (Conf, Maybe FilePath)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return

-- | Read this config file and parse its contents, or return an error message.
readConfFile :: FilePath -> IO (Either String (Conf, Maybe FilePath))
readConfFile :: FilePath -> IO (Either FilePath (Conf, Maybe FilePath))
readConfFile FilePath
f = (IOError -> IO (Either FilePath (Conf, Maybe FilePath)))
-> IO (Either FilePath (Conf, Maybe FilePath))
-> IO (Either FilePath (Conf, Maybe FilePath))
forall e a. Exception e => (e -> IO a) -> IO a -> IO a
handle (\(IOError
e::IOError) -> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either FilePath (Conf, Maybe FilePath)
 -> IO (Either FilePath (Conf, Maybe FilePath)))
-> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. (a -> b) -> a -> b
$ FilePath -> Either FilePath (Conf, Maybe FilePath)
forall a b. a -> Either a b
Left (FilePath -> Either FilePath (Conf, Maybe FilePath))
-> FilePath -> Either FilePath (Conf, Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ IOError -> FilePath
forall a. Show a => a -> FilePath
show IOError
e) (IO (Either FilePath (Conf, Maybe FilePath))
 -> IO (Either FilePath (Conf, Maybe FilePath)))
-> IO (Either FilePath (Conf, Maybe FilePath))
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. (a -> b) -> a -> b
$ do
  -- avoid GHC 9.10.1's ugly stack trace when calling readFile on a nonexistent file
  Bool
exists <- FilePath -> IO Bool
doesFileExist FilePath
f
  case Bool
exists of
    Bool
False -> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either FilePath (Conf, Maybe FilePath)
 -> IO (Either FilePath (Conf, Maybe FilePath)))
-> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. (a -> b) -> a -> b
$ FilePath -> Either FilePath (Conf, Maybe FilePath)
forall a b. a -> Either a b
Left (FilePath -> Either FilePath (Conf, Maybe FilePath))
-> FilePath -> Either FilePath (Conf, Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ FilePath
f FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> FilePath
" does not exist"
    Bool
True -> do
      Either (ParseErrorBundle Text HledgerParseErrorData) [ConfSection]
ecs <- FilePath -> IO FilePath
readFile FilePath
f IO FilePath
-> (FilePath
    -> Either
         (ParseErrorBundle Text HledgerParseErrorData) [ConfSection])
-> IO
     (Either
        (ParseErrorBundle Text HledgerParseErrorData) [ConfSection])
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> FilePath
-> Text
-> Either
     (ParseErrorBundle Text HledgerParseErrorData) [ConfSection]
parseConf FilePath
f (Text
 -> Either
      (ParseErrorBundle Text HledgerParseErrorData) [ConfSection])
-> (FilePath -> Text)
-> FilePath
-> Either
     (ParseErrorBundle Text HledgerParseErrorData) [ConfSection]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> Text
T.pack
      case Either (ParseErrorBundle Text HledgerParseErrorData) [ConfSection]
ecs of
        Left ParseErrorBundle Text HledgerParseErrorData
err -> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either FilePath (Conf, Maybe FilePath)
 -> IO (Either FilePath (Conf, Maybe FilePath)))
-> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. (a -> b) -> a -> b
$ FilePath -> Either FilePath (Conf, Maybe FilePath)
forall a b. a -> Either a b
Left (FilePath -> Either FilePath (Conf, Maybe FilePath))
-> FilePath -> Either FilePath (Conf, Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ ParseErrorBundle Text HledgerParseErrorData -> FilePath
forall s e.
(VisualStream s, TraversableStream s, ShowErrorComponent e) =>
ParseErrorBundle s e -> FilePath
errorBundlePretty ParseErrorBundle Text HledgerParseErrorData
err -- customErrorBundlePretty err
        Right [ConfSection]
cs -> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either FilePath (Conf, Maybe FilePath)
 -> IO (Either FilePath (Conf, Maybe FilePath)))
-> Either FilePath (Conf, Maybe FilePath)
-> IO (Either FilePath (Conf, Maybe FilePath))
forall a b. (a -> b) -> a -> b
$ (Conf, Maybe FilePath) -> Either FilePath (Conf, Maybe FilePath)
forall a b. b -> Either a b
Right (Conf
nullconf{
          confFile     = f
          ,confFormat   = 1
          ,confSections = cs
          },
          FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just FilePath
f
          )

-- -- | Like readConf, but throw an error on failure.
-- readConfFile' :: FilePath -> IO (Conf, Maybe FilePath)
-- readConfFile' f = readConfFile f >>= either (error' . show) return

-- | Get the highest precedence config file, based on the current directory.
activeConfFile :: IO (Maybe FilePath)
activeConfFile :: IO (Maybe FilePath)
activeConfFile = [FilePath] -> Maybe FilePath
forall a. [a] -> Maybe a
headMay ([FilePath] -> Maybe FilePath)
-> IO [FilePath] -> IO (Maybe FilePath)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [FilePath]
confFiles

-- | Get the highest precedence local config file: 
-- a config file in the current directory or above, that is not a user-wide config file.
activeLocalConfFile :: IO (Maybe FilePath)
activeLocalConfFile :: IO (Maybe FilePath)
activeLocalConfFile = do
  [FilePath]
ufs <- IO [FilePath]
userConfFiles
  Maybe FilePath
mf <- [FilePath] -> Maybe FilePath
forall a. [a] -> Maybe a
headMay ([FilePath] -> Maybe FilePath)
-> IO [FilePath] -> IO (Maybe FilePath)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [FilePath]
confFiles
  Maybe FilePath -> IO (Maybe FilePath)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe FilePath -> IO (Maybe FilePath))
-> Maybe FilePath -> IO (Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ case Maybe FilePath
mf of
    Just FilePath
f | FilePath
f FilePath -> [FilePath] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [FilePath]
ufs -> FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just FilePath
f
    Maybe FilePath
_ -> Maybe FilePath
forall a. Maybe a
Nothing

-- | Get the highest precedence user-wide config file, based on the current directory.
-- (This may not be the active config file.)
activeUserConfFile :: IO (Maybe FilePath)
activeUserConfFile :: IO (Maybe FilePath)
activeUserConfFile = [FilePath] -> Maybe FilePath
forall a. [a] -> Maybe a
headMay ([FilePath] -> Maybe FilePath)
-> IO [FilePath] -> IO (Maybe FilePath)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [FilePath]
userConfFiles

-- | Get the possibleConfFiles which exist, based on the current directory.
confFiles :: IO [FilePath]
confFiles :: IO [FilePath]
confFiles = IO [FilePath]
possibleConfFiles IO [FilePath] -> ([FilePath] -> IO [FilePath]) -> IO [FilePath]
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [FilePath] -> IO [FilePath]
existingFiles

-- | Get the possibleUserConfFiles which exist, based on the current directory.
userConfFiles :: IO [FilePath]
userConfFiles :: IO [FilePath]
userConfFiles = IO [FilePath]
possibleUserConfFiles IO [FilePath] -> ([FilePath] -> IO [FilePath]) -> IO [FilePath]
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [FilePath] -> IO [FilePath]
existingFiles

-- | Filter a list of paths to just the existing files.
existingFiles :: [FilePath] -> IO [FilePath]
existingFiles :: [FilePath] -> IO [FilePath]
existingFiles [FilePath]
fs =
  ([Maybe FilePath] -> [FilePath])
-> IO [Maybe FilePath] -> IO [FilePath]
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [Maybe FilePath] -> [FilePath]
forall a. [Maybe a] -> [a]
catMaybes (IO [Maybe FilePath] -> IO [FilePath])
-> IO [Maybe FilePath] -> IO [FilePath]
forall a b. (a -> b) -> a -> b
$ [FilePath]
-> (FilePath -> IO (Maybe FilePath)) -> IO [Maybe FilePath]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [FilePath]
fs ((FilePath -> IO (Maybe FilePath)) -> IO [Maybe FilePath])
-> (FilePath -> IO (Maybe FilePath)) -> IO [Maybe FilePath]
forall a b. (a -> b) -> a -> b
$ \FilePath
f -> do
    Bool
exists <- FilePath -> IO Bool
doesFileExist FilePath
f
    Maybe FilePath -> IO (Maybe FilePath)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe FilePath -> IO (Maybe FilePath))
-> Maybe FilePath -> IO (Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ if Bool
exists then FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just FilePath
f else Maybe FilePath
forall a. Maybe a
Nothing

-- | Get the possible paths for a hledger config file, highest precedence first:
-- hledger.conf in the current directory, 
-- hledger.conf in any parent directory, 
-- .hledger.conf in the home directory,
-- or hledger.conf in the XdgConfig directory.
possibleConfFiles :: IO [FilePath]
possibleConfFiles :: IO [FilePath]
possibleConfFiles = do
  [FilePath]
ds   <- IO [FilePath]
possibleConfDirs
  FilePath
home <- IO FilePath
getHomeDirectory
  [FilePath] -> IO [FilePath]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([FilePath] -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
forall a b. (a -> b) -> a -> b
$ FilePath -> [FilePath] -> [FilePath]
forall a. Show a => FilePath -> a -> a
dbg8 FilePath
"possible config file paths" ([FilePath] -> [FilePath]) -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> a -> b
$
    (ShowS -> [FilePath] -> [FilePath])
-> [FilePath] -> ShowS -> [FilePath]
forall a b c. (a -> b -> c) -> b -> a -> c
flip ShowS -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map [FilePath]
ds (ShowS -> [FilePath]) -> ShowS -> [FilePath]
forall a b. (a -> b) -> a -> b
$ \FilePath
d -> FilePath
d FilePath -> ShowS
</> if FilePath
dFilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
==FilePath
home then FilePath
".hledger.conf" else FilePath
"hledger.conf"

-- | Like possibleConfFiles, but consider only user-wide hledger config files:
-- .hledger.conf in the home directory,
-- or hledger.conf in the XdgConfig directory.
possibleUserConfFiles :: IO [FilePath]
possibleUserConfFiles :: IO [FilePath]
possibleUserConfFiles = do
  FilePath
home <- IO FilePath
getHomeDirectory
  FilePath
xdgc <- XdgDirectory -> FilePath -> IO FilePath
getXdgDirectory XdgDirectory
XdgConfig FilePath
"hledger"
  let ds :: [FilePath]
ds = [FilePath
home,FilePath
xdgc]
  [FilePath] -> IO [FilePath]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([FilePath] -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
forall a b. (a -> b) -> a -> b
$ FilePath -> [FilePath] -> [FilePath]
forall a. Show a => FilePath -> a -> a
dbg8 FilePath
"possible user config file paths" ([FilePath] -> [FilePath]) -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> a -> b
$
    (ShowS -> [FilePath] -> [FilePath])
-> [FilePath] -> ShowS -> [FilePath]
forall a b c. (a -> b -> c) -> b -> a -> c
flip ShowS -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map [FilePath]
ds (ShowS -> [FilePath]) -> ShowS -> [FilePath]
forall a b. (a -> b) -> a -> b
$ \FilePath
d -> FilePath
d FilePath -> ShowS
</> if FilePath
dFilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
==FilePath
home then FilePath
".hledger.conf" else FilePath
"hledger.conf"

-- | Get the directories where a hledger config file could be, highest precedence first:
-- the current directory, any parent directory, the home directory, or the XdgConfig directory.
possibleConfDirs :: IO [FilePath]
possibleConfDirs :: IO [FilePath]
possibleConfDirs = do
  FilePath
xdgc <- XdgDirectory -> FilePath -> IO FilePath
getXdgDirectory XdgDirectory
XdgConfig FilePath
"hledger"
  FilePath
home <- IO FilePath
getHomeDirectory
  FilePath
here <- IO FilePath
getCurrentDirectory
  [FilePath]
dirs <- FilePath -> IO [FilePath]
getDirsUpToRoot FilePath
here
  let dirs2 :: [FilePath]
dirs2 = if FilePath
home FilePath -> [FilePath] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [FilePath]
dirs then [FilePath]
dirs else [FilePath]
dirs [FilePath] -> [FilePath] -> [FilePath]
forall a. Semigroup a => a -> a -> a
<> [FilePath
home]
  let dirs3 :: [FilePath]
dirs3 = if FilePath
xdgc FilePath -> [FilePath] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [FilePath]
dirs2 then [FilePath]
dirs2 else [FilePath]
dirs2 [FilePath] -> [FilePath] -> [FilePath]
forall a. Semigroup a => a -> a -> a
<> [FilePath
xdgc]
  [FilePath] -> IO [FilePath]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([FilePath] -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
forall a b. (a -> b) -> a -> b
$ FilePath -> [FilePath] -> [FilePath]
forall a. Show a => FilePath -> a -> a
dbg8 FilePath
"searching config dirs" [FilePath]
dirs3

-- | Get this directory and all of its parents up to /.
getDirsUpToRoot :: FilePath -> IO [FilePath]
getDirsUpToRoot :: FilePath -> IO [FilePath]
getDirsUpToRoot FilePath
dir = [FilePath] -> IO [FilePath]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([FilePath] -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
forall a b. (a -> b) -> a -> b
$ [FilePath] -> FilePath -> [FilePath]
go [] FilePath
dir
  where
    go :: [FilePath] -> FilePath -> [FilePath]
go [FilePath]
seen FilePath
d = if
      | FilePath
d FilePath -> [FilePath] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [FilePath]
seen Bool -> Bool -> Bool
|| [FilePath] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [FilePath]
seen Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
100 -> []  -- just in case
      | FilePath
dFilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
==FilePath
"/"    -> [FilePath
d]
      | Bool
otherwise -> FilePath
d FilePath -> [FilePath] -> [FilePath]
forall a. a -> [a] -> [a]
: [FilePath] -> FilePath -> [FilePath]
go (FilePath
dFilePath -> [FilePath] -> [FilePath]
forall a. a -> [a] -> [a]
:[FilePath]
seen) (ShowS
takeDirectory FilePath
d)

-- config file parsing

parseConf :: FilePath -> Text -> Either (ParseErrorBundle Text HledgerParseErrorData) [ConfSection]
parseConf :: FilePath
-> Text
-> Either
     (ParseErrorBundle Text HledgerParseErrorData) [ConfSection]
parseConf = Parsec HledgerParseErrorData Text [ConfSection]
-> FilePath
-> Text
-> Either
     (ParseErrorBundle Text HledgerParseErrorData) [ConfSection]
forall e s a.
Parsec e s a -> FilePath -> s -> Either (ParseErrorBundle s e) a
runParser Parsec HledgerParseErrorData Text [ConfSection]
confp

dp :: String -> TextParser m ()
dp :: forall (m :: * -> *). FilePath -> TextParser m ()
dp = TextParser m () -> FilePath -> TextParser m ()
forall a b. a -> b -> a
const (TextParser m () -> FilePath -> TextParser m ())
-> TextParser m () -> FilePath -> TextParser m ()
forall a b. (a -> b) -> a -> b
$ () -> TextParser m ()
forall a. a -> ParsecT HledgerParseErrorData Text m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- no-op
-- dp = dbgparse 0  -- trace parse state at this --debug level

whitespacep, commentlinesp, restoflinep :: TextParser Identity ()
whitespacep :: TextParser Identity ()
whitespacep   = ParsecT HledgerParseErrorData Text Identity FilePath
-> TextParser Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (ParsecT HledgerParseErrorData Text Identity FilePath
 -> TextParser Identity ())
-> ParsecT HledgerParseErrorData Text Identity FilePath
-> TextParser Identity ()
forall a b. (a -> b) -> a -> b
$ {- dp "whitespacep"   >> -} ParsecT HledgerParseErrorData Text Identity Char
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
many ParsecT HledgerParseErrorData Text Identity Char
forall s (m :: * -> *).
(Stream s, Char ~ Token s) =>
ParsecT HledgerParseErrorData s m Char
spacenonewline
commentlinesp :: TextParser Identity ()
commentlinesp = ParsecT HledgerParseErrorData Text Identity [()]
-> TextParser Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (ParsecT HledgerParseErrorData Text Identity [()]
 -> TextParser Identity ())
-> ParsecT HledgerParseErrorData Text Identity [()]
-> TextParser Identity ()
forall a b. (a -> b) -> a -> b
$ {- dp "commentlinesp" >> -} TextParser Identity ()
-> ParsecT HledgerParseErrorData Text Identity [()]
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
many (FilePath -> TextParser Identity ()
forall (m :: * -> *). FilePath -> TextParser m ()
emptyorcommentlinep2 FilePath
"#")
restoflinep :: TextParser Identity ()
restoflinep   = TextParser Identity () -> TextParser Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (TextParser Identity () -> TextParser Identity ())
-> TextParser Identity () -> TextParser Identity ()
forall a b. (a -> b) -> a -> b
$ {- dp "restoflinep"   >> -} TextParser Identity ()
whitespacep TextParser Identity ()
-> TextParser Identity () -> TextParser Identity ()
forall a b.
ParsecT HledgerParseErrorData Text Identity a
-> ParsecT HledgerParseErrorData Text Identity b
-> ParsecT HledgerParseErrorData Text Identity b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> FilePath -> TextParser Identity ()
forall (m :: * -> *). FilePath -> TextParser m ()
emptyorcommentlinep2 FilePath
"#"

confp :: TextParser Identity [ConfSection]  -- a monadic TextParser to allow reusing other hledger parsers
confp :: Parsec HledgerParseErrorData Text [ConfSection]
confp = do
  FilePath -> TextParser Identity ()
forall (m :: * -> *). FilePath -> TextParser m ()
dp FilePath
"confp"
  TextParser Identity ()
commentlinesp
  [FilePath]
genas <- ParsecT HledgerParseErrorData Text Identity FilePath
-> ParsecT HledgerParseErrorData Text Identity [FilePath]
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
many ParsecT HledgerParseErrorData Text Identity FilePath
arglinep
  let s :: ConfSection
s = FilePath -> [FilePath] -> ConfSection
ConfSection FilePath
"general" [FilePath]
genas
  [ConfSection]
ss <- ParsecT HledgerParseErrorData Text Identity ConfSection
-> Parsec HledgerParseErrorData Text [ConfSection]
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
many (ParsecT HledgerParseErrorData Text Identity ConfSection
 -> Parsec HledgerParseErrorData Text [ConfSection])
-> ParsecT HledgerParseErrorData Text Identity ConfSection
-> Parsec HledgerParseErrorData Text [ConfSection]
forall a b. (a -> b) -> a -> b
$ do
    (FilePath
n, Maybe FilePath
ma) <- TextParser Identity (FilePath, Maybe FilePath)
sectionstartp
    [FilePath]
as <- ParsecT HledgerParseErrorData Text Identity FilePath
-> ParsecT HledgerParseErrorData Text Identity [FilePath]
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
many ParsecT HledgerParseErrorData Text Identity FilePath
arglinep
    ConfSection
-> ParsecT HledgerParseErrorData Text Identity ConfSection
forall a. a -> ParsecT HledgerParseErrorData Text Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return (ConfSection
 -> ParsecT HledgerParseErrorData Text Identity ConfSection)
-> ConfSection
-> ParsecT HledgerParseErrorData Text Identity ConfSection
forall a b. (a -> b) -> a -> b
$ FilePath -> [FilePath] -> ConfSection
ConfSection FilePath
n ([FilePath]
-> (FilePath -> [FilePath]) -> Maybe FilePath -> [FilePath]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [FilePath]
as (FilePath -> [FilePath] -> [FilePath]
forall a. a -> [a] -> [a]
:[FilePath]
as) Maybe FilePath
ma)
  TextParser Identity ()
forall e s (m :: * -> *). MonadParsec e s m => m ()
eof
  [ConfSection] -> Parsec HledgerParseErrorData Text [ConfSection]
forall a. a -> ParsecT HledgerParseErrorData Text Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return ([ConfSection] -> Parsec HledgerParseErrorData Text [ConfSection])
-> [ConfSection] -> Parsec HledgerParseErrorData Text [ConfSection]
forall a b. (a -> b) -> a -> b
$ ConfSection
sConfSection -> [ConfSection] -> [ConfSection]
forall a. a -> [a] -> [a]
:[ConfSection]
ss

-- parse a section name and possibly arguments written on the same line
sectionstartp :: TextParser Identity (String, Maybe String)
sectionstartp :: TextParser Identity (FilePath, Maybe FilePath)
sectionstartp = do
  FilePath -> TextParser Identity ()
forall (m :: * -> *). FilePath -> TextParser m ()
dp FilePath
"sectionstartp"
  Token Text
-> ParsecT HledgerParseErrorData Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
'['
  FilePath
n <- ShowS
-> ParsecT HledgerParseErrorData Text Identity FilePath
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall a b.
(a -> b)
-> ParsecT HledgerParseErrorData Text Identity a
-> ParsecT HledgerParseErrorData Text Identity b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ShowS
strip (ParsecT HledgerParseErrorData Text Identity FilePath
 -> ParsecT HledgerParseErrorData Text Identity FilePath)
-> ParsecT HledgerParseErrorData Text Identity FilePath
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall a b. (a -> b) -> a -> b
$ ParsecT HledgerParseErrorData Text Identity Char
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
some (ParsecT HledgerParseErrorData Text Identity Char
 -> ParsecT HledgerParseErrorData Text Identity FilePath)
-> ParsecT HledgerParseErrorData Text Identity Char
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall a b. (a -> b) -> a -> b
$ [Token Text]
-> ParsecT HledgerParseErrorData Text Identity (Token Text)
forall (f :: * -> *) e s (m :: * -> *).
(Foldable f, MonadParsec e s m) =>
f (Token s) -> m (Token s)
noneOf FilePath
[Token Text]
"]#\n"
  Token Text
-> ParsecT HledgerParseErrorData Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
']'
  -- dp "sectionstartp2"
  TextParser Identity ()
whitespacep
  -- dp "sectionstartp3"
  Maybe FilePath
ma <- (Maybe FilePath -> Maybe FilePath)
-> ParsecT HledgerParseErrorData Text Identity (Maybe FilePath)
-> ParsecT HledgerParseErrorData Text Identity (Maybe FilePath)
forall a b.
(a -> b)
-> ParsecT HledgerParseErrorData Text Identity a
-> ParsecT HledgerParseErrorData Text Identity b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (ShowS -> Maybe FilePath -> Maybe FilePath
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ShowS
strip) (ParsecT HledgerParseErrorData Text Identity (Maybe FilePath)
 -> ParsecT HledgerParseErrorData Text Identity (Maybe FilePath))
-> ParsecT HledgerParseErrorData Text Identity (Maybe FilePath)
-> ParsecT HledgerParseErrorData Text Identity (Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ ParsecT HledgerParseErrorData Text Identity FilePath
-> ParsecT HledgerParseErrorData Text Identity (Maybe FilePath)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional (ParsecT HledgerParseErrorData Text Identity FilePath
 -> ParsecT HledgerParseErrorData Text Identity (Maybe FilePath))
-> ParsecT HledgerParseErrorData Text Identity FilePath
-> ParsecT HledgerParseErrorData Text Identity (Maybe FilePath)
forall a b. (a -> b) -> a -> b
$ ParsecT HledgerParseErrorData Text Identity Char
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
some (ParsecT HledgerParseErrorData Text Identity Char
 -> ParsecT HledgerParseErrorData Text Identity FilePath)
-> ParsecT HledgerParseErrorData Text Identity Char
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall a b. (a -> b) -> a -> b
$ [Token Text]
-> ParsecT HledgerParseErrorData Text Identity (Token Text)
forall (f :: * -> *) e s (m :: * -> *).
(Foldable f, MonadParsec e s m) =>
f (Token s) -> m (Token s)
noneOf FilePath
[Token Text]
"#\n"
  -- dp "sectionstartp4"
  TextParser Identity ()
restoflinep
  -- dp "sectionstartp5"
  TextParser Identity ()
commentlinesp
  -- dp "sectionstartp6"
  (FilePath, Maybe FilePath)
-> TextParser Identity (FilePath, Maybe FilePath)
forall a. a -> ParsecT HledgerParseErrorData Text Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return (FilePath
n, Maybe FilePath
ma)

arglinep :: TextParser Identity String
arglinep :: ParsecT HledgerParseErrorData Text Identity FilePath
arglinep = do
  FilePath -> TextParser Identity ()
forall (m :: * -> *). FilePath -> TextParser m ()
dp FilePath
"arglinep"
  ParsecT HledgerParseErrorData Text Identity Char
-> TextParser Identity ()
forall a.
ParsecT HledgerParseErrorData Text Identity a
-> TextParser Identity ()
forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m ()
notFollowedBy (ParsecT HledgerParseErrorData Text Identity Char
 -> TextParser Identity ())
-> ParsecT HledgerParseErrorData Text Identity Char
-> TextParser Identity ()
forall a b. (a -> b) -> a -> b
$ Token Text
-> ParsecT HledgerParseErrorData Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
'['
  -- dp "arglinep2"
  TextParser Identity ()
whitespacep
  -- dp "arglinep3"
  FilePath
a <- ParsecT HledgerParseErrorData Text Identity Char
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
some (ParsecT HledgerParseErrorData Text Identity Char
 -> ParsecT HledgerParseErrorData Text Identity FilePath)
-> ParsecT HledgerParseErrorData Text Identity Char
-> ParsecT HledgerParseErrorData Text Identity FilePath
forall a b. (a -> b) -> a -> b
$ [Token Text]
-> ParsecT HledgerParseErrorData Text Identity (Token Text)
forall (f :: * -> *) e s (m :: * -> *).
(Foldable f, MonadParsec e s m) =>
f (Token s) -> m (Token s)
noneOf FilePath
[Token Text]
"#\n"
  -- dp "arglinep4"
  TextParser Identity ()
restoflinep TextParser Identity ()
-> TextParser Identity () -> TextParser Identity ()
forall a.
ParsecT HledgerParseErrorData Text Identity a
-> ParsecT HledgerParseErrorData Text Identity a
-> ParsecT HledgerParseErrorData Text Identity a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> TextParser Identity ()
whitespacep  -- whitespace / same-line comment, possibly with no newline
  TextParser Identity ()
commentlinesp
  FilePath -> ParsecT HledgerParseErrorData Text Identity FilePath
forall a. a -> ParsecT HledgerParseErrorData Text Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return (FilePath -> ParsecT HledgerParseErrorData Text Identity FilePath)
-> FilePath -> ParsecT HledgerParseErrorData Text Identity FilePath
forall a b. (a -> b) -> a -> b
$ ShowS
strip FilePath
a


-- initialiseAndParseJournal :: ErroringJournalParser IO ParsedJournal -> InputOpts
--                           -> FilePath -> Text -> ExceptT String IO Journal
-- initialiseAndParseJournal parser iopts f txt =
--     prettyParseErrors $ runParserT (evalStateT parser initJournal) f txt
--   where
--     y = first3 . toGregorian $ _ioDay iopts
--     initJournal = nulljournal{jparsedefaultyear = Just y, jincludefilestack = [f]}
--     -- Flatten parse errors and final parse errors, and output each as a pretty String.
--     prettyParseErrors :: ExceptT FinalParseError IO (Either (ParseErrorBundle Text HledgerParseErrorData) a)
--                       -> ExceptT String IO a
--     prettyParseErrors = withExceptT customErrorBundlePretty . liftEither
--                     <=< withExceptT (finalErrorBundlePretty . attachSource f txt)