{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
module Hledger.Cli.Commands.Stats (
statsmode
,stats
)
where
import Control.Monad (when)
import Data.Default (def)
import Data.List (intercalate, nub, sortOn)
import Data.List.Extra (nubSort)
import Data.Map qualified as Map
import Data.Maybe (fromMaybe)
import Data.HashSet (size, fromList)
import Data.Text qualified as T
import Data.Text.Lazy qualified as TL
import Data.Time.Calendar (Day, addDays, diffDays)
import Data.Time.Clock.POSIX (getPOSIXTime)
import GHC.Stats
import GitHash (tGitInfoCwdTry)
import System.Console.CmdArgs.Explicit hiding (Group)
import System.FilePath (takeFileName)
import System.Mem (performMajorGC)
import Text.Printf (printf)
import Text.Tabular.AsciiWide
import Hledger
import Hledger.Cli.CliOptions
import Hledger.Cli.Utils (writeOutput)
import Hledger.Cli.Version (packageversion, versionStringWith)
statsmode :: Mode RawOpts
statsmode = String
-> [Flag RawOpts]
-> [(String, [Flag RawOpts])]
-> [Flag RawOpts]
-> ([Arg RawOpts], Maybe (Arg RawOpts))
-> Mode RawOpts
hledgerCommandMode
$(embedFileRelative "Hledger/Cli/Commands/Stats.txt")
[ [String] -> (RawOpts -> RawOpts) -> String -> Flag RawOpts
forall a. [String] -> (a -> a) -> String -> Flag a
flagNone [String
"1"] (String -> RawOpts -> RawOpts
setboolopt String
"") String
"show a single line of output"
, [String] -> (RawOpts -> RawOpts) -> String -> Flag RawOpts
forall a. [String] -> (a -> a) -> String -> Flag a
flagNone [String
"verbose",String
"v"] (String -> RawOpts -> RawOpts
setboolopt String
"verbose") String
"show more detailed output"
,[String] -> Update RawOpts -> String -> String -> Flag RawOpts
forall a. [String] -> Update a -> String -> String -> Flag a
flagReq [String
"output-file",String
"o"] (\String
s RawOpts
opts -> RawOpts -> Either String RawOpts
forall a b. b -> Either a b
Right (RawOpts -> Either String RawOpts)
-> RawOpts -> Either String RawOpts
forall a b. (a -> b) -> a -> b
$ String -> String -> RawOpts -> RawOpts
setopt String
"output-file" String
s RawOpts
opts) String
"FILE" String
"write output to FILE."
]
[(String, [Flag RawOpts])]
cligeneralflagsgroups1
[Flag RawOpts]
hiddenflags
([], Arg RawOpts -> Maybe (Arg RawOpts)
forall a. a -> Maybe a
Just (Arg RawOpts -> Maybe (Arg RawOpts))
-> Arg RawOpts -> Maybe (Arg RawOpts)
forall a b. (a -> b) -> a -> b
$ String -> Arg RawOpts
argsFlag String
"[QUERY]")
stats :: CliOpts -> Journal -> IO ()
stats :: CliOpts -> Journal -> IO ()
stats opts :: CliOpts
opts@CliOpts{rawopts_ :: CliOpts -> RawOpts
rawopts_=RawOpts
rawopts, reportspec_ :: CliOpts -> ReportSpec
reportspec_=ReportSpec
rspec, POSIXTime
progstarttime_ :: POSIXTime
progstarttime_ :: CliOpts -> POSIXTime
progstarttime_} Journal
j = do
t <- IO POSIXTime
getPOSIXTime
let
today = ReportSpec -> Day
_rsDay ReportSpec
rspec
oneline = String -> RawOpts -> Int
intopt String
"depth" RawOpts
rawopts Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1
verbose = String -> RawOpts -> Bool
boolopt String
"verbose" RawOpts
rawopts
q = ReportSpec -> Query
_rsQuery ReportSpec
rspec
l = Query -> Journal -> Ledger
ledgerFromJournal Query
q Journal
j
intervalspans = (DateSpan, Maybe DayPartition) -> Maybe DayPartition
forall a b. (a, b) -> b
snd ((DateSpan, Maybe DayPartition) -> Maybe DayPartition)
-> (DateSpan, Maybe DayPartition) -> Maybe DayPartition
forall a b. (a -> b) -> a -> b
$ Journal -> ReportSpec -> (DateSpan, Maybe DayPartition)
reportSpanBothDates Journal
j ReportSpec
rspec
ismultiperiod = Maybe DayPartition -> Int
forall a. Maybe a -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length Maybe DayPartition
intervalspans Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
1
(txts, tnums) = unzip . map (showLedgerStats verbose l today) $ maybeDayPartitionToDateSpans intervalspans
out1 = (if Bool
ismultiperiod then String -> String
forall a. a -> a
id else String -> String
forall a. HasCallStack => [a] -> [a]
init) (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines [String]
txts
rtsstats <- getRTSStatsEnabled
(maxlivemb, maxinusemb) <- if rtsstats
then do
performMajorGC
RTSStats{..} <- getRTSStats
return (toMegabytes max_live_bytes, toMegabytes max_mem_in_use_bytes)
else
return (0,0)
let
(label, sep)
| oneline = (lstrip $ versionStringWith $$tGitInfoCwdTry False "" packageversion <> "\t", "\t")
| otherwise = ("Runtime stats : ", ", ")
dt = POSIXTime
t POSIXTime -> POSIXTime -> POSIXTime
forall a. Num a => a -> a -> a
- POSIXTime
progstarttime_
tnum = [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Int]
tnums
ss =
[ String -> String
takeFileName (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ Journal -> String
journalFilePath Journal
j | Bool
oneline ]
[String] -> [String] -> [String]
forall a. Semigroup a => a -> a -> a
<> [
String -> Float -> String
forall r. PrintfType r => String -> r
printf String
"%.2f s elapsed" (POSIXTime -> Float
forall a b. (Real a, Fractional b) => a -> b
realToFrac POSIXTime
dt :: Float)
,String -> Float -> String
forall r. PrintfType r => String -> r
printf String
"%.0f txns/s" (Int -> Float
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
tnum Float -> Float -> Float
forall a. Fractional a => a -> a -> a
/ POSIXTime -> Float
forall a b. (Real a, Fractional b) => a -> b
realToFrac POSIXTime
dt :: Float)
]
[String] -> [String] -> [String]
forall a. Semigroup a => a -> a -> a
<> if Bool
rtsstats then [
String -> Float -> String
forall r. PrintfType r => String -> r
printf String
"%0.0f MB live" Float
maxlivemb
,String -> Float -> String
forall r. PrintfType r => String -> r
printf String
"%0.0f MB alloc" Float
maxinusemb
]
else [
String
"(add +RTS -T -RTS for more)"
]
out2 = String
label String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
sep [String]
ss String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\n"
when (not oneline) $ writeOutput opts out1
when (oneline && debugLevel>0) $ do
let tabstops = String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate (Int -> Char -> String
forall a. Int -> a -> [a]
replicate Int
7 Char
' ') (Int -> String -> [String]
forall a. Int -> a -> [a]
replicate Int
21 String
".") String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\n"
writeOutput opts tabstops
writeOutput opts $ (if ismultiperiod then "\n" else "") <> out2
toMegabytes :: a -> Float
toMegabytes a
n = a -> Float
forall a b. (Real a, Fractional b) => a -> b
realToFrac a
n Float -> Float -> Float
forall a. Fractional a => a -> a -> a
/ Float
1000000 ::Float
showLedgerStats :: Bool -> Ledger -> Day -> DateSpan -> (String, Int)
showLedgerStats :: Bool -> Ledger -> Day -> DateSpan -> (String, Int)
showLedgerStats Bool
verbose Ledger
l Day
today DateSpan
spn =
([String] -> String
unlines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ ((Text, String) -> String) -> [(Text, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> String
TL.unpack (Text -> String)
-> ((Text, String) -> Text) -> (Text, String) -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TableOpts -> Header Cell -> Text
renderRow TableOpts
forall a. Default a => a
def{tableBorders=False, borderSpaces=False} (Header Cell -> Text)
-> ((Text, String) -> Header Cell) -> (Text, String) -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text, String) -> Header Cell
showRow) [(Text, String)]
stts
,Int
tnum)
where
showRow :: (Text, String) -> Header Cell
showRow (Text
label, String
val) = Properties -> [Header Cell] -> Header Cell
forall h. Properties -> [Header h] -> Header h
Group Properties
NoLine ([Header Cell] -> Header Cell) -> [Header Cell] -> Header Cell
forall a b. (a -> b) -> a -> b
$ (Text -> Header Cell) -> [Text] -> [Header Cell]
forall a b. (a -> b) -> [a] -> [b]
map (Cell -> Header Cell
forall h. h -> Header h
Header (Cell -> Header Cell) -> (Text -> Cell) -> Text -> Header Cell
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Align -> Text -> Cell
textCell Align
TopLeft)
[Maybe Int -> Maybe Int -> Bool -> Bool -> Text -> Text
fitText (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
w) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
w) Bool
False Bool
True Text
label Text -> Text -> Text
`T.append` Text
": ", String -> Text
T.pack String
val]
w :: Int
w = Int
20
([(Text, String)]
stts, Int
tnum) = ([
(Text
"Main file", String
path' :: String)
,(Text
"Included files", if Bool
verbose then [String] -> String
unlines [String]
includedpaths else Int -> String
forall a. Show a => a -> String
show ([String] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
includedpaths))
,(Text
"Txns span", String -> String -> String -> Integer -> String
forall r. PrintfType r => String -> r
printf String
"%s to %s (%d days)" (DateSpan -> String
showstart DateSpan
spn) (DateSpan -> String
showend DateSpan
spn) Integer
days)
,(Text
"Last txn", String -> (Day -> String) -> Maybe Day -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"none" Day -> String
forall a. Show a => a -> String
show Maybe Day
lastdate String -> String -> String
forall a. [a] -> [a] -> [a]
++ Maybe Integer -> String
forall {a} {t}.
(IsString a, PrintfArg t, PrintfType a, Ord t, Num t) =>
Maybe t -> a
showelapsed Maybe Integer
lastelapsed)
,(Text
"Txns", String -> Int -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%d (%0.1f per day)" Int
tnum Double
txnrate)
,(Text
"Txns last 30 days", String -> Int -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%d (%0.1f per day)" Int
tnum30 Double
txnrate30)
,(Text
"Txns last 7 days", String -> Int -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%d (%0.1f per day)" Int
tnum7 Double
txnrate7)
,(Text
"Payees/descriptions", Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ HashSet Text -> Int
forall a. HashSet a -> Int
size (HashSet Text -> Int) -> HashSet Text -> Int
forall a b. (a -> b) -> a -> b
$ [Text] -> HashSet Text
forall a. (Eq a, Hashable a) => [a] -> HashSet a
fromList ([Text] -> HashSet Text) -> [Text] -> HashSet Text
forall a b. (a -> b) -> a -> b
$ (Transaction -> Text) -> [Transaction] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Transaction -> Text
tdescription) [Transaction]
ts)
,(Text
"Accounts", String -> Int -> Int -> String
forall r. PrintfType r => String -> r
printf String
"%d (depth %d)" Int
acctnum Int
acctdepth)
,(Text
"Commodities", String -> String -> Text -> String
forall r. PrintfType r => String -> r
printf String
"%s%s" (Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
cs) (if Bool
verbose then Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
", " [Text]
cs Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")" else Text
""))
,(Text
"Market prices", String -> String -> Text -> String
forall r. PrintfType r => String -> r
printf String
"%s%s" (Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ [PriceDirective] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [PriceDirective]
mktprices) (if Bool
verbose then Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
", " [Text]
mktpricecommodities Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")" else Text
""))
]
,Int
tnum1)
where
j :: Journal
j = Ledger -> Journal
ljournal Ledger
l
path' :: String
path' = if Bool
verbose then String
path else String
".../" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String
takeFileName String
path where path :: String
path = Journal -> String
journalFilePath Journal
j
includedpaths :: [String]
includedpaths = Int -> [String] -> [String]
forall a. Int -> [a] -> [a]
drop Int
1 ([String] -> [String]) -> [String] -> [String]
forall a b. (a -> b) -> a -> b
$ Journal -> [String]
journalFilePaths Journal
j
ts :: [Transaction]
ts = (Transaction -> Day) -> [Transaction] -> [Transaction]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn Transaction -> Day
tdate ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$ (Transaction -> Bool) -> [Transaction] -> [Transaction]
forall a. (a -> Bool) -> [a] -> [a]
filter (DateSpan -> Day -> Bool
spanContainsDate DateSpan
spn (Day -> Bool) -> (Transaction -> Day) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Day
tdate) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j
as :: [Text]
as = [Text] -> [Text]
forall a. Eq a => [a] -> [a]
nub ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ (Posting -> Text) -> [Posting] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Posting -> Text
paccount ([Posting] -> [Text]) -> [Posting] -> [Text]
forall a b. (a -> b) -> a -> b
$ (Transaction -> [Posting]) -> [Transaction] -> [Posting]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Posting]
tpostings [Transaction]
ts
cs :: [Text]
cs = (String -> [Text])
-> (Map Text AmountStyle -> [Text])
-> Either String (Map Text AmountStyle)
-> [Text]
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> [Text]
forall a. String -> a
error' Map Text AmountStyle -> [Text]
forall k a. Map k a -> [k]
Map.keys (Either String (Map Text AmountStyle) -> [Text])
-> Either String (Map Text AmountStyle) -> [Text]
forall a b. (a -> b) -> a -> b
$ [Amount] -> Either String (Map Text AmountStyle)
commodityStylesFromAmounts ([Amount] -> Either String (Map Text AmountStyle))
-> [Amount] -> Either String (Map Text AmountStyle)
forall a b. (a -> b) -> a -> b
$ (Posting -> [Amount]) -> [Posting] -> [Amount]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (MixedAmount -> [Amount]
amountsRaw (MixedAmount -> [Amount])
-> (Posting -> MixedAmount) -> Posting -> [Amount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> MixedAmount
pamount) ([Posting] -> [Amount]) -> [Posting] -> [Amount]
forall a b. (a -> b) -> a -> b
$ (Transaction -> [Posting]) -> [Transaction] -> [Posting]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Posting]
tpostings [Transaction]
ts
lastdate :: Maybe Day
lastdate | [Transaction] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Transaction]
ts = Maybe Day
forall a. Maybe a
Nothing
| Bool
otherwise = Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Transaction -> Day
tdate (Transaction -> Day) -> Transaction -> Day
forall a b. (a -> b) -> a -> b
$ [Transaction] -> Transaction
forall a. HasCallStack => [a] -> a
last [Transaction]
ts
lastelapsed :: Maybe Integer
lastelapsed = (Day -> Integer) -> Maybe Day -> Maybe Integer
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Day -> Day -> Integer
diffDays Day
today) Maybe Day
lastdate
showelapsed :: Maybe t -> a
showelapsed Maybe t
Nothing = a
""
showelapsed (Just t
dys) = String -> t -> String -> a
forall r. PrintfType r => String -> r
printf String
" (%d %s)" t
dys' String
direction
where dys' :: t
dys' = t -> t
forall a. Num a => a -> a
abs t
dys
direction :: String
direction | t
dys t -> t -> Bool
forall a. Ord a => a -> a -> Bool
>= t
0 = String
"days ago" :: String
| Bool
otherwise = String
"days from now"
tnum1 :: Int
tnum1 = [Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Transaction]
ts
showstart :: DateSpan -> String
showstart (DateSpan (Just EFDay
efd) Maybe EFDay
_) = Day -> String
forall a. Show a => a -> String
show (Day -> String) -> Day -> String
forall a b. (a -> b) -> a -> b
$ EFDay -> Day
fromEFDay EFDay
efd
showstart DateSpan
_ = String
""
showend :: DateSpan -> String
showend (DateSpan Maybe EFDay
_ (Just EFDay
efd)) = Day -> String
forall a. Show a => a -> String
show (Day -> String) -> Day -> String
forall a b. (a -> b) -> a -> b
$ EFDay -> Day
fromEFDay EFDay
efd
showend DateSpan
_ = String
""
days :: Integer
days = Integer -> Maybe Integer -> Integer
forall a. a -> Maybe a -> a
fromMaybe Integer
0 (Maybe Integer -> Integer) -> Maybe Integer -> Integer
forall a b. (a -> b) -> a -> b
$ DateSpan -> Maybe Integer
daysInSpan DateSpan
spn
txnrate :: Double
txnrate | Integer
daysInteger -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
==Integer
0 = Double
0
| Bool
otherwise = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
tnum1 Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Integer -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
days :: Double
tnum30 :: Int
tnum30 = [Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Transaction] -> Int) -> [Transaction] -> Int
forall a b. (a -> b) -> a -> b
$ (Transaction -> Bool) -> [Transaction] -> [Transaction]
forall a. (a -> Bool) -> [a] -> [a]
filter Transaction -> Bool
withinlast30 [Transaction]
ts
withinlast30 :: Transaction -> Bool
withinlast30 Transaction
t = Day
d Day -> Day -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer -> Day -> Day
addDays (-Integer
30) Day
today Bool -> Bool -> Bool
&& (Day
dDay -> Day -> Bool
forall a. Ord a => a -> a -> Bool
<=Day
today) where d :: Day
d = Transaction -> Day
tdate Transaction
t
txnrate30 :: Double
txnrate30 = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
tnum30 Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
30 :: Double
tnum7 :: Int
tnum7 = [Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Transaction] -> Int) -> [Transaction] -> Int
forall a b. (a -> b) -> a -> b
$ (Transaction -> Bool) -> [Transaction] -> [Transaction]
forall a. (a -> Bool) -> [a] -> [a]
filter Transaction -> Bool
withinlast7 [Transaction]
ts
withinlast7 :: Transaction -> Bool
withinlast7 Transaction
t = Day
d Day -> Day -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer -> Day -> Day
addDays (-Integer
7) Day
today Bool -> Bool -> Bool
&& (Day
dDay -> Day -> Bool
forall a. Ord a => a -> a -> Bool
<=Day
today) where d :: Day
d = Transaction -> Day
tdate Transaction
t
txnrate7 :: Double
txnrate7 = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
tnum7 Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
7 :: Double
acctnum :: Int
acctnum = [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
as
acctdepth :: Int
acctdepth | [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
as = Int
0
| Bool
otherwise = [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (Text -> Int) -> [Text] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Int
accountNameLevel [Text]
as
mktprices :: [PriceDirective]
mktprices = Journal -> [PriceDirective]
jpricedirectives Journal
j
mktpricecommodities :: [Text]
mktpricecommodities = [Text] -> [Text]
forall a. Ord a => [a] -> [a]
nubSort ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ (PriceDirective -> Text) -> [PriceDirective] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map PriceDirective -> Text
pdcommodity [PriceDirective]
mktprices