mirror of
https://github.com/simonmichael/hledger.git
synced 2024-09-19 18:29:36 +03:00
--output -> --output-file/-o & --output-format/-O, refactor
Use two options and less surprising behaviour. Notes: * a recognised suffix on the output file name can set the format * but --output-format always wins * -o - means stdout * currently these are command-specific options on balance, print, register * later, --output-file should become a global option * and --output-format should be available at least on all commands which support multiple formats. Each command may support a different set of output formats, which should be listed in its command line help.
This commit is contained in:
parent
827aaad08e
commit
9416d12d8a
@ -247,7 +247,6 @@ import Data.Maybe
|
|||||||
-- import System.Console.CmdArgs
|
-- import System.Console.CmdArgs
|
||||||
import System.Console.CmdArgs.Explicit as C
|
import System.Console.CmdArgs.Explicit as C
|
||||||
-- import System.Console.CmdArgs.Text
|
-- import System.Console.CmdArgs.Text
|
||||||
import System.FilePath
|
|
||||||
import Text.CSV
|
import Text.CSV
|
||||||
import Test.HUnit
|
import Test.HUnit
|
||||||
import Text.Printf (printf)
|
import Text.Printf (printf)
|
||||||
@ -259,6 +258,7 @@ import Prelude hiding (putStr)
|
|||||||
import Hledger.Utils.UTF8IOCompat (putStr)
|
import Hledger.Utils.UTF8IOCompat (putStr)
|
||||||
import Hledger.Data.OutputFormat
|
import Hledger.Data.OutputFormat
|
||||||
import Hledger.Cli.Options
|
import Hledger.Cli.Options
|
||||||
|
import Hledger.Cli.Utils
|
||||||
|
|
||||||
|
|
||||||
-- | Command line options for this command.
|
-- | Command line options for this command.
|
||||||
@ -274,8 +274,8 @@ balancemode = (defCommandMode $ ["balance"] ++ aliases) { -- also accept but don
|
|||||||
,flagNone ["no-total"] (\opts -> setboolopt "no-total" opts) "don't show the final total"
|
,flagNone ["no-total"] (\opts -> setboolopt "no-total" opts) "don't show the final total"
|
||||||
,flagNone ["cumulative"] (\opts -> setboolopt "cumulative" opts) "multicolumn mode: show accumulated ending balances"
|
,flagNone ["cumulative"] (\opts -> setboolopt "cumulative" opts) "multicolumn mode: show accumulated ending balances"
|
||||||
,flagNone ["historical","H"] (\opts -> setboolopt "historical" opts) "multicolumn mode: show historical ending balances"
|
,flagNone ["historical","H"] (\opts -> setboolopt "historical" opts) "multicolumn mode: show historical ending balances"
|
||||||
,flagReq ["output","o"] (\s opts -> Right $ setopt "output" s opts) "[FILE][.FMT]" "write output to FILE (- or nothing means stdout). With a recognised FMT suffix, write that format (txt, csv)."
|
|
||||||
]
|
]
|
||||||
|
++ outputflags
|
||||||
,groupHidden = []
|
,groupHidden = []
|
||||||
,groupNamed = [generalflagsgroup1]
|
,groupNamed = [generalflagsgroup1]
|
||||||
}
|
}
|
||||||
@ -289,28 +289,22 @@ balance opts@CliOpts{reportopts_=ropts} j = do
|
|||||||
case lineFormatFromOpts ropts of
|
case lineFormatFromOpts ropts of
|
||||||
Left err -> putStr $ unlines [err]
|
Left err -> putStr $ unlines [err]
|
||||||
Right _ -> do
|
Right _ -> do
|
||||||
(path, ext) <- outputFilePathAndExtensionFromOpts opts
|
let fmt = outputFormatFromOpts opts
|
||||||
let filename = fst $ splitExtension $ snd $ splitFileName path
|
|
||||||
case intervalFromOpts ropts of
|
case intervalFromOpts ropts of
|
||||||
|
|
||||||
NoInterval -> do
|
NoInterval -> do
|
||||||
let render | ext=="csv" = \_ r -> printCSV (balanceReportAsCsv ropts r) ++ "\n"
|
let render | fmt=="csv" = \_ r -> printCSV (balanceReportAsCsv ropts r) ++ "\n"
|
||||||
| otherwise = \ropts r -> unlines $ balanceReportAsText ropts r
|
| otherwise = \ropts r -> unlines $ balanceReportAsText ropts r
|
||||||
write | filename `elem` ["","-"] && ext `elem` ["","csv","txt"] = putStr
|
writeOutput opts $ render ropts $ balanceReport ropts (queryFromOpts d ropts) j
|
||||||
| otherwise = writeFile path
|
|
||||||
write $ render ropts $ balanceReport ropts (queryFromOpts d ropts) j
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
if ext=="csv"
|
if fmt=="csv"
|
||||||
then error' "Sorry, CSV output with a report period is not supported yet"
|
then error' "Sorry, CSV output with a report period is not supported yet"
|
||||||
else do
|
else do
|
||||||
let render = case balancetype_ ropts of
|
let render = case balancetype_ ropts of
|
||||||
PeriodBalance -> periodBalanceReportAsText
|
PeriodBalance -> periodBalanceReportAsText
|
||||||
CumulativeBalance -> cumulativeBalanceReportAsText
|
CumulativeBalance -> cumulativeBalanceReportAsText
|
||||||
HistoricalBalance -> historicalBalanceReportAsText
|
HistoricalBalance -> historicalBalanceReportAsText
|
||||||
write | filename `elem` ["","-"] && ext `elem` ["","txt"] = putStr . unlines
|
writeOutput opts $ unlines $ render ropts $ multiBalanceReport ropts (queryFromOpts d ropts) j
|
||||||
| otherwise = writeFile path . unlines
|
|
||||||
write $ render ropts $ multiBalanceReport ropts (queryFromOpts d ropts) j
|
|
||||||
|
|
||||||
-- | Render a single-column balance report as CSV.
|
-- | Render a single-column balance report as CSV.
|
||||||
balanceReportAsCsv :: ReportOpts -> BalanceReport -> CSV
|
balanceReportAsCsv :: ReportOpts -> BalanceReport -> CSV
|
||||||
|
@ -13,6 +13,7 @@ module Hledger.Cli.Options (
|
|||||||
detailedversionflag,
|
detailedversionflag,
|
||||||
inputflags,
|
inputflags,
|
||||||
reportflags,
|
reportflags,
|
||||||
|
outputflags,
|
||||||
generalflagsgroup1,
|
generalflagsgroup1,
|
||||||
generalflagsgroup2,
|
generalflagsgroup2,
|
||||||
generalflagsgroup3,
|
generalflagsgroup3,
|
||||||
@ -37,7 +38,8 @@ module Hledger.Cli.Options (
|
|||||||
aliasesFromOpts,
|
aliasesFromOpts,
|
||||||
journalFilePathFromOpts,
|
journalFilePathFromOpts,
|
||||||
rulesFilePathFromOpts,
|
rulesFilePathFromOpts,
|
||||||
outputFilePathAndExtensionFromOpts,
|
outputFileFromOpts,
|
||||||
|
outputFormatFromOpts,
|
||||||
-- | For register:
|
-- | For register:
|
||||||
OutputWidth(..),
|
OutputWidth(..),
|
||||||
Width(..),
|
Width(..),
|
||||||
@ -122,6 +124,12 @@ reportflags = [
|
|||||||
,flagNone ["cost","B"] (setboolopt "cost") "show amounts in their cost price's commodity"
|
,flagNone ["cost","B"] (setboolopt "cost") "show amounts in their cost price's commodity"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
-- | Common output-related flags: --output-file, --output-format...
|
||||||
|
outputflags = [
|
||||||
|
flagReq ["output-file","o"] (\s opts -> Right $ setopt "output-file" s opts) "FILE[.FMT]" "write output to FILE instead of stdout. A recognised FMT suffix influences the format."
|
||||||
|
,flagReq ["output-format","O"] (\s opts -> Right $ setopt "output-format" s opts) "FMT" "select the output format. Supported formats: txt, csv."
|
||||||
|
]
|
||||||
|
|
||||||
argsFlag :: FlagHelp -> Arg RawOpts
|
argsFlag :: FlagHelp -> Arg RawOpts
|
||||||
argsFlag desc = flagArg (\s opts -> Right $ setopt "args" s opts) desc
|
argsFlag desc = flagArg (\s opts -> Right $ setopt "args" s opts) desc
|
||||||
|
|
||||||
@ -237,7 +245,8 @@ data CliOpts = CliOpts {
|
|||||||
,command_ :: String
|
,command_ :: String
|
||||||
,file_ :: Maybe FilePath
|
,file_ :: Maybe FilePath
|
||||||
,rules_file_ :: Maybe FilePath
|
,rules_file_ :: Maybe FilePath
|
||||||
,output_ :: Maybe FilePath
|
,output_file_ :: Maybe FilePath
|
||||||
|
,output_format_ :: Maybe String
|
||||||
,alias_ :: [String]
|
,alias_ :: [String]
|
||||||
,ignore_assertions_ :: Bool
|
,ignore_assertions_ :: Bool
|
||||||
,debug_ :: Int -- ^ debug level, set by @--debug[=N]@. See also 'Hledger.Utils.debugLevel'.
|
,debug_ :: Int -- ^ debug level, set by @--debug[=N]@. See also 'Hledger.Utils.debugLevel'.
|
||||||
@ -261,6 +270,7 @@ defcliopts = CliOpts
|
|||||||
def
|
def
|
||||||
def
|
def
|
||||||
def
|
def
|
||||||
|
def
|
||||||
|
|
||||||
-- | Convert possibly encoded option values to regular unicode strings.
|
-- | Convert possibly encoded option values to regular unicode strings.
|
||||||
decodeRawOpts :: RawOpts -> RawOpts
|
decodeRawOpts :: RawOpts -> RawOpts
|
||||||
@ -277,7 +287,8 @@ rawOptsToCliOpts rawopts = do
|
|||||||
,command_ = stringopt "command" rawopts
|
,command_ = stringopt "command" rawopts
|
||||||
,file_ = maybestringopt "file" rawopts
|
,file_ = maybestringopt "file" rawopts
|
||||||
,rules_file_ = maybestringopt "rules-file" rawopts
|
,rules_file_ = maybestringopt "rules-file" rawopts
|
||||||
,output_ = maybestringopt "output" rawopts
|
,output_file_ = maybestringopt "output-file" rawopts
|
||||||
|
,output_format_ = maybestringopt "output-format" rawopts
|
||||||
,alias_ = map stripquotes $ listofstringopt "alias" rawopts
|
,alias_ = map stripquotes $ listofstringopt "alias" rawopts
|
||||||
,debug_ = intopt "debug" rawopts
|
,debug_ = intopt "debug" rawopts
|
||||||
,ignore_assertions_ = boolopt "ignore-assertions" rawopts
|
,ignore_assertions_ = boolopt "ignore-assertions" rawopts
|
||||||
@ -347,15 +358,33 @@ journalFilePathFromOpts opts = do
|
|||||||
expandPath d $ fromMaybe f $ file_ opts
|
expandPath d $ fromMaybe f $ file_ opts
|
||||||
|
|
||||||
|
|
||||||
-- | Get the (tilde-expanded, absolute) output file path and file
|
-- | Get the expanded, absolute output file path from options,
|
||||||
-- extension (without the dot) from options, or the defaults ("-","").
|
-- or the default (-, meaning stdout).
|
||||||
outputFilePathAndExtensionFromOpts :: CliOpts -> IO (String, String)
|
outputFileFromOpts :: CliOpts -> IO FilePath
|
||||||
outputFilePathAndExtensionFromOpts opts = do
|
outputFileFromOpts opts = do
|
||||||
d <- getCurrentDirectory
|
d <- getCurrentDirectory
|
||||||
p <- expandPath d <$> fromMaybe "-" $ output_ opts
|
case output_file_ opts of
|
||||||
let (_,ext) = splitExtension p
|
Just p -> expandPath d p
|
||||||
ext' = dropWhile (=='.') ext
|
Nothing -> return "-"
|
||||||
return (p,ext')
|
|
||||||
|
-- | Get the output format from the --output-format option,
|
||||||
|
-- otherwise from a recognised file extension in the --output-file option,
|
||||||
|
-- otherwise the default (txt).
|
||||||
|
outputFormatFromOpts :: CliOpts -> String
|
||||||
|
outputFormatFromOpts opts =
|
||||||
|
case output_format_ opts of
|
||||||
|
Just f -> f
|
||||||
|
Nothing ->
|
||||||
|
let mext = (snd . splitExtension . snd . splitFileName) <$> output_file_ opts
|
||||||
|
in case mext of
|
||||||
|
Just ext | ext `elem` formats -> ext
|
||||||
|
_ -> defaultformat
|
||||||
|
|
||||||
|
defaultformat = "txt"
|
||||||
|
formats =
|
||||||
|
[defaultformat] ++
|
||||||
|
["csv"
|
||||||
|
]
|
||||||
|
|
||||||
-- | Get the (tilde-expanded) rules file path from options, if any.
|
-- | Get the (tilde-expanded) rules file path from options, if any.
|
||||||
rulesFilePathFromOpts :: CliOpts -> IO (Maybe FilePath)
|
rulesFilePathFromOpts :: CliOpts -> IO (Maybe FilePath)
|
||||||
|
@ -13,22 +13,18 @@ where
|
|||||||
|
|
||||||
import Data.List
|
import Data.List
|
||||||
import System.Console.CmdArgs.Explicit
|
import System.Console.CmdArgs.Explicit
|
||||||
import System.FilePath
|
|
||||||
import Test.HUnit
|
import Test.HUnit
|
||||||
import Text.CSV
|
import Text.CSV
|
||||||
|
|
||||||
import Hledger
|
import Hledger
|
||||||
import Prelude hiding (putStr)
|
|
||||||
import Hledger.Utils.UTF8IOCompat (putStr)
|
|
||||||
import Hledger.Cli.Options
|
import Hledger.Cli.Options
|
||||||
|
import Hledger.Cli.Utils
|
||||||
|
|
||||||
|
|
||||||
printmode = (defCommandMode $ ["print"] ++ aliases) {
|
printmode = (defCommandMode $ ["print"] ++ aliases) {
|
||||||
modeHelp = "show transaction entries" `withAliases` aliases
|
modeHelp = "show transaction entries" `withAliases` aliases
|
||||||
,modeGroupFlags = Group {
|
,modeGroupFlags = Group {
|
||||||
groupUnnamed = [
|
groupUnnamed = outputflags
|
||||||
flagReq ["output","o"] (\s opts -> Right $ setopt "output" s opts) "[FILE][.FMT]" "write output to FILE (- or nothing means stdout). With a recognised FMT suffix, write that format (txt, csv)."
|
|
||||||
]
|
|
||||||
,groupHidden = []
|
,groupHidden = []
|
||||||
,groupNamed = [generalflagsgroup1]
|
,groupNamed = [generalflagsgroup1]
|
||||||
}
|
}
|
||||||
@ -40,14 +36,11 @@ print' :: CliOpts -> Journal -> IO ()
|
|||||||
print' opts@CliOpts{reportopts_=ropts} j = do
|
print' opts@CliOpts{reportopts_=ropts} j = do
|
||||||
d <- getCurrentDay
|
d <- getCurrentDay
|
||||||
let q = queryFromOpts d ropts
|
let q = queryFromOpts d ropts
|
||||||
(path, ext) <- outputFilePathAndExtensionFromOpts opts
|
fmt = outputFormatFromOpts opts
|
||||||
let filename = fst $ splitExtension $ snd $ splitFileName path
|
(render, ropts') = case fmt of
|
||||||
write | filename `elem` ["","-"] && ext `elem` ["","csv","txt"] = putStr
|
"csv" -> ((++"\n") . printCSV . entriesReportAsCsv, ropts{flat_=True})
|
||||||
| otherwise = writeFile path
|
_ -> (entriesReportAsText, ropts)
|
||||||
(render,ropts') | ext=="csv" = ((++"\n") . printCSV . entriesReportAsCsv, ropts{flat_=True})
|
writeOutput opts $ render $ entriesReport ropts' q j
|
||||||
| otherwise = (entriesReportAsText, ropts)
|
|
||||||
|
|
||||||
write $ render $ entriesReport ropts' q j
|
|
||||||
|
|
||||||
entriesReportAsText :: EntriesReport -> String
|
entriesReportAsText :: EntriesReport -> String
|
||||||
entriesReportAsText items = concatMap showTransactionUnelided items
|
entriesReportAsText items = concatMap showTransactionUnelided items
|
||||||
|
@ -15,15 +15,13 @@ module Hledger.Cli.Register (
|
|||||||
import Data.List
|
import Data.List
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import System.Console.CmdArgs.Explicit
|
import System.Console.CmdArgs.Explicit
|
||||||
import System.FilePath
|
|
||||||
import Text.CSV
|
import Text.CSV
|
||||||
import Test.HUnit
|
import Test.HUnit
|
||||||
import Text.Printf
|
import Text.Printf
|
||||||
|
|
||||||
import Hledger
|
import Hledger
|
||||||
import Prelude hiding (putStr)
|
|
||||||
import Hledger.Utils.UTF8IOCompat (putStr)
|
|
||||||
import Hledger.Cli.Options
|
import Hledger.Cli.Options
|
||||||
|
import Hledger.Cli.Utils
|
||||||
|
|
||||||
|
|
||||||
registermode = (defCommandMode $ ["register"] ++ aliases) {
|
registermode = (defCommandMode $ ["register"] ++ aliases) {
|
||||||
@ -34,8 +32,8 @@ registermode = (defCommandMode $ ["register"] ++ aliases) {
|
|||||||
,flagNone ["average","A"] (\opts -> setboolopt "average" opts) "show a running average instead of the running total (implies --empty)"
|
,flagNone ["average","A"] (\opts -> setboolopt "average" opts) "show a running average instead of the running total (implies --empty)"
|
||||||
,flagNone ["related","r"] (\opts -> setboolopt "related" opts) "show postings' siblings instead"
|
,flagNone ["related","r"] (\opts -> setboolopt "related" opts) "show postings' siblings instead"
|
||||||
,flagReq ["width","w"] (\s opts -> Right $ setopt "width" s opts) "N" "set output width (default: 80)"
|
,flagReq ["width","w"] (\s opts -> Right $ setopt "width" s opts) "N" "set output width (default: 80)"
|
||||||
,flagReq ["output","o"] (\s opts -> Right $ setopt "output" s opts) "[FILE][.FMT]" "write output to FILE (- or nothing means stdout). With a recognised FMT suffix, write that format (txt, csv)."
|
|
||||||
]
|
]
|
||||||
|
++ outputflags
|
||||||
,groupHidden = []
|
,groupHidden = []
|
||||||
,groupNamed = [generalflagsgroup1]
|
,groupNamed = [generalflagsgroup1]
|
||||||
}
|
}
|
||||||
@ -46,13 +44,10 @@ registermode = (defCommandMode $ ["register"] ++ aliases) {
|
|||||||
register :: CliOpts -> Journal -> IO ()
|
register :: CliOpts -> Journal -> IO ()
|
||||||
register opts@CliOpts{reportopts_=ropts} j = do
|
register opts@CliOpts{reportopts_=ropts} j = do
|
||||||
d <- getCurrentDay
|
d <- getCurrentDay
|
||||||
(path, ext) <- outputFilePathAndExtensionFromOpts opts
|
let fmt = outputFormatFromOpts opts
|
||||||
let filename = fst $ splitExtension $ snd $ splitFileName path
|
render | fmt=="csv" = const ((++"\n") . printCSV . postingsReportAsCsv)
|
||||||
write | filename `elem` ["","-"] && ext `elem` ["","csv","txt"] = putStr
|
|
||||||
| otherwise = writeFile path
|
|
||||||
render | ext=="csv" = const ((++"\n") . printCSV . postingsReportAsCsv)
|
|
||||||
| otherwise = postingsReportAsText
|
| otherwise = postingsReportAsText
|
||||||
write $ render opts $ postingsReport ropts (queryFromOpts d ropts) j
|
writeOutput opts $ render opts $ postingsReport ropts (queryFromOpts d ropts) j
|
||||||
|
|
||||||
postingsReportAsCsv :: PostingsReport -> CSV
|
postingsReportAsCsv :: PostingsReport -> CSV
|
||||||
postingsReportAsCsv (_,is) =
|
postingsReportAsCsv (_,is) =
|
||||||
|
@ -9,6 +9,7 @@ Hledger.Utils.
|
|||||||
module Hledger.Cli.Utils
|
module Hledger.Cli.Utils
|
||||||
(
|
(
|
||||||
withJournalDo,
|
withJournalDo,
|
||||||
|
writeOutput,
|
||||||
journalReload,
|
journalReload,
|
||||||
journalReloadIfChanged,
|
journalReloadIfChanged,
|
||||||
journalFileIsNewer,
|
journalFileIsNewer,
|
||||||
@ -70,6 +71,12 @@ withJournalDo opts cmd = do
|
|||||||
ej <- readJournalFile Nothing rulespath (not $ ignore_assertions_ opts) journalpath
|
ej <- readJournalFile Nothing rulespath (not $ ignore_assertions_ opts) journalpath
|
||||||
either error' (cmd opts . journalApplyAliases (aliasesFromOpts opts)) ej
|
either error' (cmd opts . journalApplyAliases (aliasesFromOpts opts)) ej
|
||||||
|
|
||||||
|
-- | Write some output to stdout or to a file selected by --output-file.
|
||||||
|
writeOutput :: CliOpts -> String -> IO ()
|
||||||
|
writeOutput opts s = do
|
||||||
|
f <- outputFileFromOpts opts
|
||||||
|
(if f == "-" then putStr else writeFile f) s
|
||||||
|
|
||||||
-- -- | Get a journal from the given string and options, or throw an error.
|
-- -- | Get a journal from the given string and options, or throw an error.
|
||||||
-- readJournalWithOpts :: CliOpts -> String -> IO Journal
|
-- readJournalWithOpts :: CliOpts -> String -> IO Journal
|
||||||
-- readJournalWithOpts opts s = readJournal Nothing Nothing Nothing s >>= either error' return
|
-- readJournalWithOpts opts s = readJournal Nothing Nothing Nothing s >>= either error' return
|
||||||
|
Loading…
Reference in New Issue
Block a user