dev: lens: Use TemplateHaskell for ReportOpts and ReportSpec.
Also rename overWithReport/setWithReport to overEither/setEither.
This commit is contained in:
		
							parent
							
								
									435ec992f9
								
							
						
					
					
						commit
						f3eacebc1d
					
				| @ -11,15 +11,17 @@ Options common to most hledger reports. | ||||
| {-# LANGUAGE OverloadedStrings     #-} | ||||
| {-# LANGUAGE RankNTypes            #-} | ||||
| {-# LANGUAGE RecordWildCards       #-} | ||||
| {-# LANGUAGE TemplateHaskell       #-} | ||||
| {-# LANGUAGE TypeFamilies          #-} | ||||
| 
 | ||||
| module Hledger.Reports.ReportOptions ( | ||||
|   ReportOpts(..), | ||||
|   HasReportOptsNoUpdate(..), | ||||
|   HasReportOpts(..), | ||||
|   ReportSpec(..), | ||||
|   HasReportSpec(..), | ||||
|   overWithReport, | ||||
|   setWithReport, | ||||
|   overEither, | ||||
|   setEither, | ||||
|   BalanceCalculation(..), | ||||
|   BalanceAccumulation(..), | ||||
|   AccountListMode(..), | ||||
| @ -67,7 +69,6 @@ import Data.Maybe (fromMaybe, mapMaybe) | ||||
| import qualified Data.Text as T | ||||
| import Data.Time.Calendar (Day, addDays) | ||||
| import Data.Default (Default(..)) | ||||
| import Lens.Micro (Lens') | ||||
| import Safe (headMay, lastDef, lastMay, maximumMay) | ||||
| 
 | ||||
| import Text.Megaparsec.Custom | ||||
| @ -274,35 +275,6 @@ defreportspec = ReportSpec | ||||
|     , _rsQueryOpts  = [] | ||||
|     } | ||||
| 
 | ||||
| -- | Generate a ReportSpec from a set of ReportOpts on a given day. | ||||
| reportOptsToSpec :: Day -> ReportOpts -> Either String ReportSpec | ||||
| reportOptsToSpec day ropts = do | ||||
|     (argsquery, queryopts) <- parseQueryList day $ querystring_ ropts | ||||
|     return ReportSpec | ||||
|       { _rsReportOpts = ropts | ||||
|       , _rsDay        = day | ||||
|       , _rsQuery      = simplifyQuery $ And [queryFromFlags ropts, argsquery] | ||||
|       , _rsQueryOpts  = queryopts | ||||
|       } | ||||
| 
 | ||||
| -- | Update the ReportOpts and the fields derived from it in a ReportSpec, | ||||
| -- or return an error message if there is a problem such as missing or | ||||
| -- unparseable options data. This is the safe way to change a ReportSpec, | ||||
| -- ensuring that all fields (_rsQuery, _rsReportOpts, querystring_, etc.) are in sync. | ||||
| updateReportSpec :: ReportOpts -> ReportSpec -> Either String ReportSpec | ||||
| updateReportSpec = setWithReport reportOpts | ||||
| 
 | ||||
| -- | Like updateReportSpec, but takes a ReportOpts-modifying function. | ||||
| updateReportSpecWith :: (ReportOpts -> ReportOpts) -> ReportSpec -> Either String ReportSpec | ||||
| updateReportSpecWith = overWithReport reportOpts | ||||
| 
 | ||||
| -- | Generate a ReportSpec from RawOpts and the current date. | ||||
| rawOptsToReportSpec :: RawOpts -> IO ReportSpec | ||||
| rawOptsToReportSpec rawopts = do | ||||
|     d <- getCurrentDay | ||||
|     let ropts = rawOptsToReportOpts d rawopts | ||||
|     either fail return $ reportOptsToSpec d ropts | ||||
| 
 | ||||
| accountlistmodeopt :: RawOpts -> AccountListMode | ||||
| accountlistmodeopt = | ||||
|   fromMaybe ALFlat . choiceopt parse where | ||||
| @ -709,237 +681,105 @@ instance (e ~ a) => Reportable (Either a) e where | ||||
|     report _ = join | ||||
| 
 | ||||
| -- | Apply a function over a lens, but report on failure. | ||||
| overWithReport :: ((a -> Either e b) -> s -> Either e t) -> (a -> b) -> s -> Either e t | ||||
| overWithReport l f = l (pure . f) | ||||
| overEither :: ((a -> Either e b) -> s -> Either e t) -> (a -> b) -> s -> Either e t | ||||
| overEither l f = l (pure . f) | ||||
| 
 | ||||
| -- | Set a field using a lens, but report on failure. | ||||
| setWithReport :: ((a -> Either e b) -> s -> Either e t) -> b -> s -> Either e t | ||||
| setWithReport l = overWithReport l . const | ||||
| setEither :: ((a -> Either e b) -> s -> Either e t) -> b -> s -> Either e t | ||||
| setEither l = overEither l . const | ||||
| 
 | ||||
| type ReportableLens' s a = forall f. Reportable f String => (a -> f a) -> s -> f s | ||||
| 
 | ||||
| -- | Lenses for ReportOpts. | ||||
| -- Note that some of these are not true lenses, as they have a further restriction on | ||||
| 
 | ||||
| -- Implement HasReportOptsNoUpdate, the basic lenses for ReportOpts. | ||||
| makeHledgerClassyLenses ''ReportOpts | ||||
| makeHledgerClassyLenses ''ReportSpec | ||||
| 
 | ||||
| -- | Special lenses for ReportOpts which also update the Query and QueryOpts in ReportSpec. | ||||
| -- Note that these are not true lenses, as they have a further restriction on | ||||
| -- the functor. This will work as a normal lens for all common uses, but since they | ||||
| -- don't obey the lens laws for some fancy cases, they may fail in some exotic circumstances. | ||||
| -- The special type is so that updating querystring_, real_, depth_, date2_, period_, or | ||||
| -- statuses_ can automatically update the Query and QueryOpts in ReportSpec. | ||||
| class HasReportOpts a where | ||||
| -- | ||||
| -- Note that setEither/overEither should only be necessary with | ||||
| -- querystring and reportOpts: the other lenses should never fail. | ||||
| -- | ||||
| -- === Examples: | ||||
| -- >>> import Lens.Micro (set) | ||||
| -- >>> _rsQuery <$> setEither querystring ["assets"] defreportspec | ||||
| -- Right (Acct (RegexpCI "assets")) | ||||
| -- >>> _rsQuery <$> setEither querystring ["(assets"] defreportspec | ||||
| -- Left "this regular expression could not be compiled: (assets" | ||||
| -- >>> _rsQuery $ set querystring ["assets"] defreportspec | ||||
| -- Acct (RegexpCI "assets") | ||||
| -- >>> _rsQuery $ set querystring ["(assets"] defreportspec | ||||
| -- *** Exception: Updating ReportSpec failed: try using overEither instead of over or setEither instead of set | ||||
| -- >>> _rsQuery $ set period (MonthPeriod 2021 08) defreportspec | ||||
| -- Date DateSpan 2021-08 | ||||
| class HasReportOptsNoUpdate a => HasReportOpts a where | ||||
|     reportOpts :: ReportableLens' a ReportOpts | ||||
|     reportOptsNoUpdate :: Lens' a ReportOpts | ||||
|     reportOpts = reportOptsNoUpdate | ||||
|     {-# INLINE reportOpts #-} | ||||
| 
 | ||||
|     period :: ReportableLens' a Period | ||||
|     period = reportOpts.period | ||||
|     period = reportOpts.periodNoUpdate | ||||
|     {-# INLINE period #-} | ||||
| 
 | ||||
|     statuses :: ReportableLens' a [Status] | ||||
|     statuses = reportOpts.statuses | ||||
|     statuses = reportOpts.statusesNoUpdate | ||||
|     {-# INLINE statuses #-} | ||||
| 
 | ||||
|     depth :: ReportableLens' a (Maybe Int) | ||||
|     depth = reportOpts.depth | ||||
|     depth = reportOpts.depthNoUpdate | ||||
|     {-# INLINE depth #-} | ||||
| 
 | ||||
|     date2 :: ReportableLens' a Bool | ||||
|     date2 = reportOpts.date2 | ||||
|     date2 = reportOpts.date2NoUpdate | ||||
|     {-# INLINE date2 #-} | ||||
| 
 | ||||
|     real :: ReportableLens' a Bool | ||||
|     real = reportOpts.real | ||||
|     real = reportOpts.realNoUpdate | ||||
|     {-# INLINE real #-} | ||||
| 
 | ||||
|     querystring :: ReportableLens' a [T.Text] | ||||
|     querystring = reportOpts.querystring | ||||
|     querystring = reportOpts.querystringNoUpdate | ||||
|     {-# INLINE querystring #-} | ||||
| 
 | ||||
|     interval :: Lens' a Interval | ||||
|     interval = reportOptsNoUpdate.interval | ||||
|     {-# INLINE interval #-} | ||||
| instance HasReportOpts ReportOpts | ||||
| 
 | ||||
|     cost :: Lens' a Costing | ||||
|     cost = reportOptsNoUpdate.cost | ||||
|     {-# INLINE cost #-} | ||||
| 
 | ||||
|     value :: Lens' a (Maybe ValuationType) | ||||
|     value = reportOptsNoUpdate.value | ||||
|     {-# INLINE value #-} | ||||
| 
 | ||||
|     infer_value :: Lens' a Bool | ||||
|     infer_value = reportOptsNoUpdate.infer_value | ||||
|     {-# INLINE infer_value #-} | ||||
| 
 | ||||
|     empty__ :: Lens' a Bool | ||||
|     empty__ = reportOptsNoUpdate.empty__ | ||||
|     {-# INLINE empty__ #-} | ||||
| 
 | ||||
|     no_elide :: Lens' a Bool | ||||
|     no_elide = reportOptsNoUpdate.no_elide | ||||
|     {-# INLINE no_elide #-} | ||||
| 
 | ||||
|     format :: Lens' a StringFormat | ||||
|     format = reportOptsNoUpdate.format | ||||
|     {-# INLINE format #-} | ||||
| 
 | ||||
|     average :: Lens' a Bool | ||||
|     average = reportOptsNoUpdate.average | ||||
|     {-# INLINE average #-} | ||||
| 
 | ||||
|     related :: Lens' a Bool | ||||
|     related = reportOptsNoUpdate.related | ||||
|     {-# INLINE related #-} | ||||
| 
 | ||||
|     txn_dates :: Lens' a Bool | ||||
|     txn_dates = reportOptsNoUpdate.txn_dates | ||||
|     {-# INLINE txn_dates #-} | ||||
| 
 | ||||
|     balancecalc :: Lens' a BalanceCalculation | ||||
|     balancecalc = reportOptsNoUpdate.balancecalc | ||||
|     {-# INLINE balancecalc #-} | ||||
| 
 | ||||
|     balanceaccum :: Lens' a BalanceAccumulation | ||||
|     balanceaccum = reportOptsNoUpdate.balanceaccum | ||||
|     {-# INLINE balanceaccum #-} | ||||
| 
 | ||||
|     accountlistmode :: Lens' a AccountListMode | ||||
|     accountlistmode = reportOptsNoUpdate.accountlistmode | ||||
|     {-# INLINE accountlistmode #-} | ||||
| 
 | ||||
|     drop__ :: Lens' a Int | ||||
|     drop__ = reportOptsNoUpdate.drop__ | ||||
|     {-# INLINE drop__ #-} | ||||
| 
 | ||||
|     row_total :: Lens' a Bool | ||||
|     row_total = reportOptsNoUpdate.row_total | ||||
|     {-# INLINE row_total #-} | ||||
| 
 | ||||
|     no_total :: Lens' a Bool | ||||
|     no_total = reportOptsNoUpdate.no_total | ||||
|     {-# INLINE no_total #-} | ||||
| 
 | ||||
|     show_costs :: Lens' a Bool | ||||
|     show_costs = reportOptsNoUpdate.show_costs | ||||
|     {-# INLINE show_costs #-} | ||||
| 
 | ||||
|     pretty_tables :: Lens' a Bool | ||||
|     pretty_tables = reportOptsNoUpdate.pretty_tables | ||||
|     {-# INLINE pretty_tables #-} | ||||
| 
 | ||||
|     sort_amount :: Lens' a Bool | ||||
|     sort_amount = reportOptsNoUpdate.sort_amount | ||||
|     {-# INLINE sort_amount #-} | ||||
| 
 | ||||
|     percent :: Lens' a Bool | ||||
|     percent = reportOptsNoUpdate.percent | ||||
|     {-# INLINE percent #-} | ||||
| 
 | ||||
|     invert :: Lens' a Bool | ||||
|     invert = reportOptsNoUpdate.invert | ||||
|     {-# INLINE invert #-} | ||||
| 
 | ||||
|     normalbalance :: Lens' a (Maybe NormalSign) | ||||
|     normalbalance = reportOptsNoUpdate.normalbalance | ||||
|     {-# INLINE normalbalance #-} | ||||
| 
 | ||||
|     color__ :: Lens' a Bool | ||||
|     color__ = reportOptsNoUpdate.color__ | ||||
|     {-# INLINE color__ #-} | ||||
| 
 | ||||
|     transpose__ :: Lens' a Bool | ||||
|     transpose__ = reportOptsNoUpdate.transpose__ | ||||
|     {-# INLINE transpose__ #-} | ||||
| 
 | ||||
| instance HasReportOpts ReportOpts where | ||||
|     reportOpts = id | ||||
|     reportOptsNoUpdate = id | ||||
|     period f ropts = (\x -> ropts{period_=x}) <$> f (period_ ropts) | ||||
|     {-# INLINE period #-} | ||||
|     interval f ropts = (\x -> ropts{interval_=x}) <$> f (interval_ ropts) | ||||
|     {-# INLINE interval #-} | ||||
|     statuses f ropts = (\x -> ropts{statuses_=x}) <$> f (statuses_ ropts) | ||||
|     {-# INLINE statuses #-} | ||||
|     cost f ropts = (\x -> ropts{cost_=x}) <$> f (cost_ ropts) | ||||
|     {-# INLINE cost #-} | ||||
|     value f ropts = (\x -> ropts{value_=x}) <$> f (value_ ropts) | ||||
|     {-# INLINE value #-} | ||||
|     infer_value f ropts = (\x -> ropts{infer_value_=x}) <$> f (infer_value_ ropts) | ||||
|     {-# INLINE infer_value #-} | ||||
|     depth f ropts = (\x -> ropts{depth_=x}) <$> f (depth_ ropts) | ||||
|     {-# INLINE depth #-} | ||||
|     date2 f ropts = (\x -> ropts{date2_=x}) <$> f (date2_ ropts) | ||||
|     {-# INLINE date2 #-} | ||||
|     empty__ f ropts = (\x -> ropts{empty_=x}) <$> f (empty_ ropts) | ||||
|     {-# INLINE empty__ #-} | ||||
|     no_elide f ropts = (\x -> ropts{no_elide_=x}) <$> f (no_elide_ ropts) | ||||
|     {-# INLINE no_elide #-} | ||||
|     real f ropts = (\x -> ropts{real_=x}) <$> f (real_ ropts) | ||||
|     {-# INLINE real #-} | ||||
|     format f ropts = (\x -> ropts{format_=x}) <$> f (format_ ropts) | ||||
|     {-# INLINE format #-} | ||||
|     querystring f ropts = (\x -> ropts{querystring_=x}) <$> f (querystring_ ropts) | ||||
|     {-# INLINE querystring #-} | ||||
|     average f ropts = (\x -> ropts{average_=x}) <$> f (average_ ropts) | ||||
|     {-# INLINE average #-} | ||||
|     related f ropts = (\x -> ropts{related_=x}) <$> f (related_ ropts) | ||||
|     {-# INLINE related #-} | ||||
|     txn_dates f ropts = (\x -> ropts{txn_dates_=x}) <$> f (txn_dates_ ropts) | ||||
|     {-# INLINE txn_dates #-} | ||||
|     balancecalc f ropts = (\x -> ropts{balancecalc_=x}) <$> f (balancecalc_ ropts) | ||||
|     {-# INLINE balancecalc #-} | ||||
|     balanceaccum f ropts = (\x -> ropts{balanceaccum_=x}) <$> f (balanceaccum_ ropts) | ||||
|     {-# INLINE balanceaccum #-} | ||||
|     accountlistmode f ropts = (\x -> ropts{accountlistmode_=x}) <$> f (accountlistmode_ ropts) | ||||
|     {-# INLINE accountlistmode #-} | ||||
|     drop__ f ropts = (\x -> ropts{drop_=x}) <$> f (drop_ ropts) | ||||
|     {-# INLINE drop__ #-} | ||||
|     row_total f ropts = (\x -> ropts{row_total_=x}) <$> f (row_total_ ropts) | ||||
|     {-# INLINE row_total #-} | ||||
|     no_total f ropts = (\x -> ropts{no_total_=x}) <$> f (no_total_ ropts) | ||||
|     {-# INLINE no_total #-} | ||||
|     show_costs f ropts = (\x -> ropts{show_costs_=x}) <$> f (show_costs_ ropts) | ||||
|     {-# INLINE show_costs #-} | ||||
|     pretty_tables f ropts = (\x -> ropts{pretty_tables_=x}) <$> f (pretty_tables_ ropts) | ||||
|     {-# INLINE pretty_tables #-} | ||||
|     sort_amount f ropts = (\x -> ropts{sort_amount_=x}) <$> f (sort_amount_ ropts) | ||||
|     {-# INLINE sort_amount #-} | ||||
|     percent f ropts = (\x -> ropts{percent_=x}) <$> f (percent_ ropts) | ||||
|     {-# INLINE percent #-} | ||||
|     invert f ropts = (\x -> ropts{invert_=x}) <$> f (invert_ ropts) | ||||
|     {-# INLINE invert #-} | ||||
|     normalbalance f ropts = (\x -> ropts{normalbalance_=x}) <$> f (normalbalance_ ropts) | ||||
|     {-# INLINE normalbalance #-} | ||||
|     color__ f ropts = (\x -> ropts{color_=x}) <$> f (color_ ropts) | ||||
|     {-# INLINE color__ #-} | ||||
|     transpose__ f ropts = (\x -> ropts{transpose_=x}) <$> f (transpose_ ropts) | ||||
|     {-# INLINE transpose__ #-} | ||||
| 
 | ||||
| class HasReportSpec a where | ||||
|     reportSpec :: Lens' a ReportSpec | ||||
| 
 | ||||
|     rsDay :: Lens' a Day | ||||
|     rsDay = reportSpec.rsDay | ||||
|     {-# INLINE rsDay #-} | ||||
| 
 | ||||
|     rsQuery :: Lens' a Query | ||||
|     rsQuery = reportSpec.rsQuery | ||||
|     {-# INLINE rsQuery #-} | ||||
| 
 | ||||
|     rsQueryOpts :: Lens' a [QueryOpt] | ||||
|     rsQueryOpts = reportSpec.rsQueryOpts | ||||
|     {-# INLINE rsQueryOpts #-} | ||||
| 
 | ||||
| instance HasReportSpec ReportSpec where | ||||
|     reportSpec = id | ||||
|     rsDay f rspec = (\d -> either (error "Updating _rsDay should never fail") id .  -- PARTIAL: | ||||
|       reportOptsToSpec d $ _rsReportOpts rspec) <$> f (_rsDay rspec) | ||||
|     {-# INLINE rsDay #-} | ||||
|     rsQuery f rspec = (\x -> rspec{_rsQuery=x}) <$> f (_rsQuery rspec) | ||||
|     {-# INLINE rsQuery #-} | ||||
|     rsQueryOpts f rspec = (\x -> rspec{_rsQueryOpts=x}) <$> f (_rsQueryOpts rspec) | ||||
|     {-# INLINE rsQueryOpts #-} | ||||
| instance HasReportOptsNoUpdate ReportSpec where | ||||
|     reportOptsNoUpdate = rsReportOpts | ||||
| 
 | ||||
| instance HasReportOpts ReportSpec where | ||||
|     reportOpts f rspec = report (error "Updating ReportSpec failed: try using overWithReport instead of over") $  -- PARTIAL: | ||||
|     reportOpts f rspec = report (error' "Updating ReportSpec failed: try using overEither instead of over or setEither instead of set") $  -- PARTIAL: | ||||
|       reportOptsToSpec (_rsDay rspec) <$> f (_rsReportOpts rspec) | ||||
|     {-# INLINE reportOpts #-} | ||||
|     reportOptsNoUpdate f rspec = (\x -> rspec{_rsReportOpts=x}) <$> f (_rsReportOpts rspec) | ||||
|     {-# INLINE reportOptsNoUpdate #-} | ||||
| 
 | ||||
| -- | Generate a ReportSpec from a set of ReportOpts on a given day. | ||||
| reportOptsToSpec :: Day -> ReportOpts -> Either String ReportSpec | ||||
| reportOptsToSpec day ropts = do | ||||
|     (argsquery, queryopts) <- parseQueryList day $ querystring_ ropts | ||||
|     return ReportSpec | ||||
|       { _rsReportOpts = ropts | ||||
|       , _rsDay        = day | ||||
|       , _rsQuery      = simplifyQuery $ And [queryFromFlags ropts, argsquery] | ||||
|       , _rsQueryOpts  = queryopts | ||||
|       } | ||||
| 
 | ||||
| -- | Update the ReportOpts and the fields derived from it in a ReportSpec, | ||||
| -- or return an error message if there is a problem such as missing or | ||||
| -- unparseable options data. This is the safe way to change a ReportSpec, | ||||
| -- ensuring that all fields (_rsQuery, _rsReportOpts, querystring_, etc.) are in sync. | ||||
| updateReportSpec :: ReportOpts -> ReportSpec -> Either String ReportSpec | ||||
| updateReportSpec = setEither reportOpts | ||||
| 
 | ||||
| -- | Like updateReportSpec, but takes a ReportOpts-modifying function. | ||||
| updateReportSpecWith :: (ReportOpts -> ReportOpts) -> ReportSpec -> Either String ReportSpec | ||||
| updateReportSpecWith = overEither reportOpts | ||||
| 
 | ||||
| -- | Generate a ReportSpec from RawOpts and the current date. | ||||
| rawOptsToReportSpec :: RawOpts -> IO ReportSpec | ||||
| rawOptsToReportSpec rawopts = do | ||||
|     d <- getCurrentDay | ||||
|     let ropts = rawOptsToReportOpts d rawopts | ||||
|     either fail return $ reportOptsToSpec d ropts | ||||
|  | ||||
| @ -747,4 +747,3 @@ getDirectoryContentsSafe d = | ||||
| --     putStrLn $ "processed opts:\n" ++ show opts | ||||
| --     d <- getCurrentDay | ||||
| --     putStrLn $ "search query: " ++ (show $ queryFromOpts d $ reportopts_ opts) | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user