lib: Remove non-law-abiding Monoid instance for Journal.
This commit is contained in:
parent
702c958487
commit
e0dde6fe57
@ -90,6 +90,7 @@ import Control.Monad.Extra
|
||||
import Control.Monad.Reader as R
|
||||
import Control.Monad.ST
|
||||
import Data.Array.ST
|
||||
import Data.Default (Default(..))
|
||||
import Data.Function ((&))
|
||||
import qualified Data.HashTable.ST.Cuckoo as H
|
||||
import Data.List
|
||||
@ -97,9 +98,8 @@ import Data.List.Extra (groupSort, nubSort)
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe
|
||||
#if !(MIN_VERSION_base(4,11,0))
|
||||
import Data.Monoid
|
||||
import Data.Semigroup (Semigroup(..))
|
||||
#endif
|
||||
import qualified Data.Semigroup as Sem
|
||||
import qualified Data.Set as S
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
@ -157,7 +157,7 @@ instance Show Journal where
|
||||
-- ,show $ map fst $ jfiles j
|
||||
-- ]
|
||||
|
||||
-- The monoid instance for Journal is useful for two situations.
|
||||
-- The semigroup instance for Journal is useful for two situations.
|
||||
--
|
||||
-- 1. concatenating finalised journals, eg with multiple -f options:
|
||||
-- FIRST <> SECOND. The second's list fields are appended to the
|
||||
@ -168,7 +168,9 @@ instance Show Journal where
|
||||
-- CHILD <> PARENT. A parsed journal's data is in reverse order, so
|
||||
-- this gives what we want.
|
||||
--
|
||||
instance Sem.Semigroup Journal where
|
||||
-- Note that (<>) is right-biased, so nulljournal is only a left identity.
|
||||
-- In particular, this prevents Journal from being a monoid.
|
||||
instance Semigroup Journal where
|
||||
j1 <> j2 = Journal {
|
||||
jparsedefaultyear = jparsedefaultyear j2
|
||||
,jparsedefaultcommodity = jparsedefaultcommodity j2
|
||||
@ -190,12 +192,8 @@ instance Sem.Semigroup Journal where
|
||||
,jlastreadtime = max (jlastreadtime j1) (jlastreadtime j2)
|
||||
}
|
||||
|
||||
instance Monoid Journal where
|
||||
mempty = nulljournal
|
||||
#if !(MIN_VERSION_base(4,11,0))
|
||||
-- This is redundant starting with base-4.11 / GHC 8.4.
|
||||
mappend = (Sem.<>)
|
||||
#endif
|
||||
instance Default Journal where
|
||||
def = nulljournal
|
||||
|
||||
nulljournal :: Journal
|
||||
nulljournal = Journal {
|
||||
|
||||
@ -48,22 +48,24 @@ import Control.Arrow (right)
|
||||
import qualified Control.Exception as C
|
||||
import Control.Monad (when)
|
||||
import "mtl" Control.Monad.Except (runExceptT)
|
||||
import Data.Default
|
||||
import Data.Default (def)
|
||||
import Data.Foldable (asum)
|
||||
import Data.List
|
||||
import Data.Maybe
|
||||
import Data.Ord
|
||||
import Data.List (group, sort, sortBy)
|
||||
import Data.List.NonEmpty (nonEmpty)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Ord (comparing)
|
||||
import Data.Semigroup (sconcat)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Time (Day)
|
||||
import Safe
|
||||
import Safe (headDef)
|
||||
import System.Directory (doesFileExist, getHomeDirectory)
|
||||
import System.Environment (getEnv)
|
||||
import System.Exit (exitFailure)
|
||||
import System.FilePath
|
||||
import System.FilePath ((<.>), (</>), splitDirectories, splitFileName)
|
||||
import System.Info (os)
|
||||
import System.IO
|
||||
import Text.Printf
|
||||
import System.IO (stderr, writeFile)
|
||||
import Text.Printf (hPrintf, printf)
|
||||
|
||||
import Hledger.Data.Dates (getCurrentDay, parsedate, showDate)
|
||||
import Hledger.Data.Types
|
||||
@ -150,11 +152,7 @@ type PrefixedFilePath = FilePath
|
||||
-- Also the final parse state saved in the Journal does span all files.
|
||||
readJournalFiles :: InputOpts -> [PrefixedFilePath] -> IO (Either String Journal)
|
||||
readJournalFiles iopts =
|
||||
(right mconcat1 . sequence <$>) . mapM (readJournalFile iopts)
|
||||
where
|
||||
mconcat1 :: Monoid t => [t] -> t
|
||||
mconcat1 [] = mempty
|
||||
mconcat1 x = foldr1 mappend x
|
||||
fmap (right (maybe def sconcat . nonEmpty) . sequence) . mapM (readJournalFile iopts)
|
||||
|
||||
-- | Read a Journal from this file, or from stdin if the file path is -,
|
||||
-- or return an error message. The file path can have a READER: prefix.
|
||||
|
||||
@ -222,7 +222,7 @@ rtp = runTextParser
|
||||
runJournalParser, rjp
|
||||
:: Monad m
|
||||
=> JournalParser m a -> Text -> m (Either (ParseErrorBundle Text CustomErr) a)
|
||||
runJournalParser p t = runParserT (evalStateT p mempty) "" t
|
||||
runJournalParser p t = runParserT (evalStateT p nulljournal) "" t
|
||||
rjp = runJournalParser
|
||||
|
||||
-- | Run an erroring journal parser in some monad. See also: parseWithState.
|
||||
@ -232,7 +232,7 @@ runErroringJournalParser, rejp
|
||||
-> Text
|
||||
-> m (Either FinalParseError (Either (ParseErrorBundle Text CustomErr) a))
|
||||
runErroringJournalParser p t =
|
||||
runExceptT $ runParserT (evalStateT p mempty) "" t
|
||||
runExceptT $ runParserT (evalStateT p nulljournal) "" t
|
||||
rejp = runErroringJournalParser
|
||||
|
||||
genericSourcePos :: SourcePos -> GenericSourcePos
|
||||
@ -680,7 +680,7 @@ amountwithoutpricep = do
|
||||
-- | Parse an amount from a string, or get an error.
|
||||
amountp' :: String -> Amount
|
||||
amountp' s =
|
||||
case runParser (evalStateT (amountp <* eof) mempty) "" (T.pack s) of
|
||||
case runParser (evalStateT (amountp <* eof) nulljournal) "" (T.pack s) of
|
||||
Right amt -> amt
|
||||
Left err -> error' $ show err -- XXX should throwError
|
||||
|
||||
|
||||
@ -963,7 +963,7 @@ mkPosting rules record number accountFld amountFld amountInFld amountOutFld bala
|
||||
| all isSpace str = Nothing
|
||||
| otherwise = Just
|
||||
(either (balanceerror n str) id $
|
||||
runParser (evalStateT (amountp <* eof) mempty) "" $
|
||||
runParser (evalStateT (amountp <* eof) nulljournal) "" $
|
||||
T.pack $ (currency++) $ simplifySign str
|
||||
,nullsourcepos) -- XXX parse position to show when assertion fails,
|
||||
-- the csv record's line number would be good
|
||||
@ -1039,7 +1039,7 @@ chooseAmount rules record currency amountFld amountInFld amountOutFld =
|
||||
|
||||
parseAmount currency amountstr =
|
||||
either (amounterror amountstr) (Mixed . (:[]))
|
||||
<$> runParser (evalStateT (amountp <* eof) mempty) ""
|
||||
<$> runParser (evalStateT (amountp <* eof) nulljournal) ""
|
||||
<$> T.pack
|
||||
<$> (currency++)
|
||||
<$> simplifySign
|
||||
|
||||
@ -85,7 +85,7 @@ import Control.Monad.Trans.Class (lift)
|
||||
import Data.Either (isRight)
|
||||
import qualified Data.Map.Strict as M
|
||||
#if !(MIN_VERSION_base(4,11,0))
|
||||
import Data.Monoid ((<>))
|
||||
import Data.Semigroup ((<>))
|
||||
#endif
|
||||
import Data.Text (Text)
|
||||
import Data.String
|
||||
@ -298,7 +298,7 @@ includedirectivep = do
|
||||
put $ updatedChildj <> parentj
|
||||
|
||||
newJournalWithParseStateFrom :: FilePath -> Journal -> Journal
|
||||
newJournalWithParseStateFrom filepath j = mempty{
|
||||
newJournalWithParseStateFrom filepath j = nulljournal{
|
||||
jparsedefaultyear = jparsedefaultyear j
|
||||
,jparsedefaultcommodity = jparsedefaultcommodity j
|
||||
,jparseparentaccounts = jparseparentaccounts j
|
||||
@ -747,7 +747,7 @@ tests_JournalReader = tests "JournalReader" [
|
||||
,test "yearless date with no default year" $ assertParseError datep "1/1" "current year is unknown"
|
||||
,test "yearless date with default year" $ do
|
||||
let s = "1/1"
|
||||
ep <- parseWithState mempty{jparsedefaultyear=Just 2018} datep s
|
||||
ep <- parseWithState nulljournal{jparsedefaultyear=Just 2018} datep s
|
||||
either (assertFailure . ("parse error at "++) . customErrorBundlePretty) (const $ return ()) ep
|
||||
,test "no leading zero" $ assertParse datep "2018/1/1"
|
||||
]
|
||||
|
||||
@ -25,8 +25,9 @@ where
|
||||
|
||||
import Control.Monad.Except (ExceptT, runExceptT)
|
||||
import Control.Monad.State.Strict (StateT, evalStateT, execStateT)
|
||||
import Data.Default (Default(..))
|
||||
#if !(MIN_VERSION_base(4,11,0))
|
||||
import Data.Monoid ((<>))
|
||||
import Data.Semigroup ((<>))
|
||||
#endif
|
||||
-- import Data.CallStack
|
||||
import Data.List (isInfixOf)
|
||||
@ -73,35 +74,35 @@ assertRight (Left a) = assertFailure $ "expected Right, got (Left " ++ show a +
|
||||
-- | Assert that this stateful parser runnable in IO successfully parses
|
||||
-- all of the given input text, showing the parse error if it fails.
|
||||
-- Suitable for hledger's JournalParser parsers.
|
||||
assertParse :: (HasCallStack, Eq a, Show a, Monoid st) =>
|
||||
assertParse :: (HasCallStack, Eq a, Show a, Default st) =>
|
||||
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> Assertion
|
||||
assertParse parser input = do
|
||||
ep <- runParserT (evalStateT (parser <* eof) mempty) "" input
|
||||
ep <- runParserT (evalStateT (parser <* eof) def) "" input
|
||||
either (assertFailure.(++"\n").("\nparse error at "++).customErrorBundlePretty)
|
||||
(const $ return ())
|
||||
ep
|
||||
|
||||
-- | Assert a parser produces an expected value.
|
||||
assertParseEq :: (HasCallStack, Eq a, Show a, Monoid st) =>
|
||||
assertParseEq :: (HasCallStack, Eq a, Show a, Default st) =>
|
||||
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> a -> Assertion
|
||||
assertParseEq parser input expected = assertParseEqOn parser input id expected
|
||||
|
||||
-- | Like assertParseEq, but transform the parse result with the given function
|
||||
-- before comparing it.
|
||||
assertParseEqOn :: (HasCallStack, Eq b, Show b, Monoid st) =>
|
||||
assertParseEqOn :: (HasCallStack, Eq b, Show b, Default st) =>
|
||||
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> (a -> b) -> b -> Assertion
|
||||
assertParseEqOn parser input f expected = do
|
||||
ep <- runParserT (evalStateT (parser <* eof) mempty) "" input
|
||||
ep <- runParserT (evalStateT (parser <* eof) def) "" input
|
||||
either (assertFailure . (++"\n") . ("\nparse error at "++) . customErrorBundlePretty)
|
||||
(assertEqual "" expected . f)
|
||||
ep
|
||||
|
||||
-- | Assert that this stateful parser runnable in IO fails to parse
|
||||
-- the given input text, with a parse error containing the given string.
|
||||
assertParseError :: (HasCallStack, Eq a, Show a, Monoid st) =>
|
||||
assertParseError :: (HasCallStack, Eq a, Show a, Default st) =>
|
||||
StateT st (ParsecT CustomErr T.Text IO) a -> String -> String -> Assertion
|
||||
assertParseError parser input errstr = do
|
||||
ep <- runParserT (evalStateT parser mempty) "" (T.pack input)
|
||||
ep <- runParserT (evalStateT parser def) "" (T.pack input)
|
||||
case ep of
|
||||
Right v -> assertFailure $ "\nparse succeeded unexpectedly, producing:\n" ++ pshow v ++ "\n"
|
||||
Left e -> do
|
||||
@ -113,28 +114,28 @@ assertParseError parser input errstr = do
|
||||
-- | Run a stateful parser in IO like assertParse, then assert that the
|
||||
-- final state (the wrapped state, not megaparsec's internal state),
|
||||
-- transformed by the given function, matches the given expected value.
|
||||
assertParseStateOn :: (HasCallStack, Eq b, Show b, Monoid st) =>
|
||||
assertParseStateOn :: (HasCallStack, Eq b, Show b, Default st) =>
|
||||
StateT st (ParsecT CustomErr T.Text IO) a
|
||||
-> T.Text
|
||||
-> (st -> b)
|
||||
-> b
|
||||
-> Assertion
|
||||
assertParseStateOn parser input f expected = do
|
||||
es <- runParserT (execStateT (parser <* eof) mempty) "" input
|
||||
es <- runParserT (execStateT (parser <* eof) def) "" input
|
||||
case es of
|
||||
Left err -> assertFailure $ (++"\n") $ ("\nparse error at "++) $ customErrorBundlePretty err
|
||||
Right s -> assertEqual "" expected $ f s
|
||||
|
||||
-- | These "E" variants of the above are suitable for hledger's ErroringJournalParser parsers.
|
||||
assertParseE
|
||||
:: (HasCallStack, Eq a, Show a, Monoid st)
|
||||
:: (HasCallStack, Eq a, Show a, Default st)
|
||||
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
|
||||
-> T.Text
|
||||
-> Assertion
|
||||
assertParseE parser input = do
|
||||
let filepath = ""
|
||||
eep <- runExceptT $
|
||||
runParserT (evalStateT (parser <* eof) mempty) filepath input
|
||||
runParserT (evalStateT (parser <* eof) def) filepath input
|
||||
case eep of
|
||||
Left finalErr ->
|
||||
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
|
||||
@ -145,7 +146,7 @@ assertParseE parser input = do
|
||||
ep
|
||||
|
||||
assertParseEqE
|
||||
:: (Monoid st, Eq a, Show a, HasCallStack)
|
||||
:: (Default st, Eq a, Show a, HasCallStack)
|
||||
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
|
||||
-> T.Text
|
||||
-> a
|
||||
@ -153,7 +154,7 @@ assertParseEqE
|
||||
assertParseEqE parser input expected = assertParseEqOnE parser input id expected
|
||||
|
||||
assertParseEqOnE
|
||||
:: (HasCallStack, Eq b, Show b, Monoid st)
|
||||
:: (HasCallStack, Eq b, Show b, Default st)
|
||||
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
|
||||
-> T.Text
|
||||
-> (a -> b)
|
||||
@ -161,7 +162,7 @@ assertParseEqOnE
|
||||
-> Assertion
|
||||
assertParseEqOnE parser input f expected = do
|
||||
let filepath = ""
|
||||
eep <- runExceptT $ runParserT (evalStateT (parser <* eof) mempty) filepath input
|
||||
eep <- runExceptT $ runParserT (evalStateT (parser <* eof) def) filepath input
|
||||
case eep of
|
||||
Left finalErr ->
|
||||
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
|
||||
@ -172,14 +173,14 @@ assertParseEqOnE parser input f expected = do
|
||||
ep
|
||||
|
||||
assertParseErrorE
|
||||
:: (Monoid st, Eq a, Show a, HasCallStack)
|
||||
:: (Default st, Eq a, Show a, HasCallStack)
|
||||
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
|
||||
-> T.Text
|
||||
-> String
|
||||
-> Assertion
|
||||
assertParseErrorE parser input errstr = do
|
||||
let filepath = ""
|
||||
eep <- runExceptT $ runParserT (evalStateT parser mempty) filepath input
|
||||
eep <- runExceptT $ runParserT (evalStateT parser def) filepath input
|
||||
case eep of
|
||||
Left finalErr -> do
|
||||
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
|
||||
|
||||
@ -166,7 +166,7 @@ validatePostings acctRes amtRes = let
|
||||
foldl (\s a -> s <> parseErrorTextPretty a) "" .
|
||||
bundleErrors)
|
||||
checkAccount = errorToFormMsg . runParser (accountnamep <* eof) "" . T.strip
|
||||
checkAmount = errorToFormMsg . runParser (evalStateT (amountp <* eof) mempty) "" . T.strip
|
||||
checkAmount = errorToFormMsg . runParser (evalStateT (amountp <* eof) nulljournal) "" . T.strip
|
||||
|
||||
-- Add errors to forms with zero or one rows if the form is not a FormMissing
|
||||
result :: [(Text, Text, Either (Maybe Text, Maybe Text) Posting)]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user