command line options API updates, possibly fixing hledger-web build
This commit is contained in:
		
							parent
							
								
									13f8c0f938
								
							
						
					
					
						commit
						a66a715eeb
					
				| @ -11,14 +11,12 @@ import Data.List | |||||||
| import Data.Ord | import Data.Ord | ||||||
| import Hledger | import Hledger | ||||||
| import Hledger.Cli | import Hledger.Cli | ||||||
| import Hledger.Cli.Print (print') |  | ||||||
| 
 | 
 | ||||||
| main = do | main = do | ||||||
|   opts <- getHledgerCliOpts [] |   opts <- getCliOpts (defCommandMode ["hledger-print-unique"]) | ||||||
|   withJournalDo opts $ |   withJournalDo opts $ | ||||||
|     \opts j@Journal{jtxns=ts} -> print' opts j{jtxns=uniquify ts} |     \opts j@Journal{jtxns=ts} -> print' opts j{jtxns=uniquify ts} | ||||||
|     where  |     where  | ||||||
|       uniquify = nubBy (\t1 t2 -> thingToCompare t1 == thingToCompare t2) . sortBy (comparing thingToCompare) |       uniquify = nubBy (\t1 t2 -> thingToCompare t1 == thingToCompare t2) . sortBy (comparing thingToCompare) | ||||||
| 
 |       thingToCompare = tdescription | ||||||
| thingToCompare = tdescription |       -- thingToCompare = tdate | ||||||
| -- thingToCompare = tdate |  | ||||||
|  | |||||||
| @ -40,9 +40,9 @@ main = do | |||||||
| 
 | 
 | ||||||
| runWith :: WebOpts -> IO () | runWith :: WebOpts -> IO () | ||||||
| runWith opts | runWith opts | ||||||
|   | "help" `in_` (rawopts_ $ cliopts_ opts)            = putStr (showModeHelp webmode) >> exitSuccess |   | "help" `inRawOpts` (rawopts_ $ cliopts_ opts)            = putStr (showModeHelp webmode) >> exitSuccess | ||||||
|   | "version" `in_` (rawopts_ $ cliopts_ opts)         = putStrLn prognameandversion >> exitSuccess |   | "version" `inRawOpts` (rawopts_ $ cliopts_ opts)         = putStrLn prognameandversion >> exitSuccess | ||||||
|   | "binary-filename" `in_` (rawopts_ $ cliopts_ opts) = putStrLn (binaryfilename progname) |   | "binary-filename" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStrLn (binaryfilename progname) | ||||||
|   | otherwise = do |   | otherwise = do | ||||||
|     requireJournalFileExists =<< journalFilePathFromOpts (cliopts_ opts) |     requireJournalFileExists =<< journalFilePathFromOpts (cliopts_ opts) | ||||||
|     withJournalDo' opts web |     withJournalDo' opts web | ||||||
|  | |||||||
| @ -31,11 +31,11 @@ webflags = [ | |||||||
| webmode :: Mode [([Char], [Char])] | webmode :: Mode [([Char], [Char])] | ||||||
| webmode =  (mode "hledger-web" [("command","web")] | webmode =  (mode "hledger-web" [("command","web")] | ||||||
|             "start serving the hledger web interface" |             "start serving the hledger web interface" | ||||||
|             mainargsflag []){ |             (argsFlag "[PATTERNS]") []){ | ||||||
|               modeGroupFlags = Group { |               modeGroupFlags = Group { | ||||||
|                                 groupUnnamed = webflags |                                 groupUnnamed = webflags | ||||||
|                                ,groupHidden = [flagNone ["binary-filename"] (setboolopt "binary-filename") "show the download filename for this executable, and exit"] |                                ,groupHidden = [flagNone ["binary-filename"] (setboolopt "binary-filename") "show the download filename for this executable, and exit"] | ||||||
|                                ,groupNamed = [(generalflagstitle, generalflags1)] |                                ,groupNamed = [generalflagsgroup1] | ||||||
|                                } |                                } | ||||||
|              ,modeHelpSuffix=[ |              ,modeHelpSuffix=[ | ||||||
|                   -- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window curses ui." |                   -- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window curses ui." | ||||||
| @ -61,7 +61,7 @@ defwebopts = WebOpts | |||||||
| 
 | 
 | ||||||
| toWebOpts :: RawOpts -> IO WebOpts | toWebOpts :: RawOpts -> IO WebOpts | ||||||
| toWebOpts rawopts = do | toWebOpts rawopts = do | ||||||
|   cliopts <- toCliOpts rawopts |   cliopts <- rawOptsToCliOpts rawopts | ||||||
|   let p = fromMaybe defport $ maybeintopt "port" rawopts |   let p = fromMaybe defport $ maybeintopt "port" rawopts | ||||||
|   return defwebopts { |   return defwebopts { | ||||||
|               port_ = p |               port_ = p | ||||||
|  | |||||||
| @ -99,7 +99,7 @@ main = do | |||||||
|   addons <- getHledgerAddonCommands |   addons <- getHledgerAddonCommands | ||||||
| 
 | 
 | ||||||
|   -- parse arguments with cmdargs |   -- parse arguments with cmdargs | ||||||
|   opts <- getHledgerCliOpts addons |   opts <- argsToCliOpts args addons | ||||||
| 
 | 
 | ||||||
|   -- select an action and run it. |   -- select an action and run it. | ||||||
|   let |   let | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ Command-line options for the hledger program, and related utilities. | |||||||
| 
 | 
 | ||||||
| module Hledger.Cli.Options ( | module Hledger.Cli.Options ( | ||||||
| 
 | 
 | ||||||
|   -- * cmdargs modes |   -- * cmdargs modes & flags | ||||||
|   -- | These tell cmdargs how to parse the command line arguments. |   -- | These tell cmdargs how to parse the command line arguments. | ||||||
|   -- There's one mode for each internal subcommand, plus a main mode. |   -- There's one mode for each internal subcommand, plus a main mode. | ||||||
|   mainmode, |   mainmode, | ||||||
| @ -22,7 +22,15 @@ module Hledger.Cli.Options ( | |||||||
|   statsmode, |   statsmode, | ||||||
|   testmode, |   testmode, | ||||||
|   convertmode, |   convertmode, | ||||||
| 
 |   defCommandMode, | ||||||
|  |   argsFlag, | ||||||
|  |   helpflags, | ||||||
|  |   inputflags, | ||||||
|  |   reportflags, | ||||||
|  |   generalflagsgroup1, | ||||||
|  |   generalflagsgroup2, | ||||||
|  |   generalflagsgroup3, | ||||||
|  |    | ||||||
|   -- * raw options |   -- * raw options | ||||||
|   -- | To allow the cmdargs modes to be reused and extended by other |   -- | To allow the cmdargs modes to be reused and extended by other | ||||||
|   -- packages (eg, add-ons which want to mimic the standard hledger |   -- packages (eg, add-ons which want to mimic the standard hledger | ||||||
| @ -30,6 +38,14 @@ module Hledger.Cli.Options ( | |||||||
|   -- association list, not a fixed ADT. |   -- association list, not a fixed ADT. | ||||||
|   RawOpts, |   RawOpts, | ||||||
|   inRawOpts, |   inRawOpts, | ||||||
|  |   boolopt, | ||||||
|  |   intopt, | ||||||
|  |   maybeintopt, | ||||||
|  |   stringopt, | ||||||
|  |   maybestringopt, | ||||||
|  |   listofstringopt, | ||||||
|  |   setopt, | ||||||
|  |   setboolopt, | ||||||
| 
 | 
 | ||||||
|   -- * CLI options |   -- * CLI options | ||||||
|   -- | Raw options are converted to a more convenient, |   -- | Raw options are converted to a more convenient, | ||||||
| @ -37,10 +53,9 @@ module Hledger.Cli.Options ( | |||||||
|   -- throughout hledger CLI code. |   -- throughout hledger CLI code. | ||||||
|   CliOpts(..), |   CliOpts(..), | ||||||
|   defcliopts, |   defcliopts, | ||||||
|   toCliOpts, |  | ||||||
| 
 | 
 | ||||||
|   -- * CLI option accessors |   -- * CLI option accessors | ||||||
|   -- | Some options require more processing. Possibly these should be merged into toCliOpts. |   -- | Some options require more processing. Possibly these should be merged into argsToCliOpts. | ||||||
|   aliasesFromOpts, |   aliasesFromOpts, | ||||||
|   formatFromOpts, |   formatFromOpts, | ||||||
|   journalFilePathFromOpts, |   journalFilePathFromOpts, | ||||||
| @ -53,10 +68,15 @@ module Hledger.Cli.Options ( | |||||||
| 
 | 
 | ||||||
|   -- * utilities |   -- * utilities | ||||||
|   getHledgerAddonCommands, |   getHledgerAddonCommands, | ||||||
|   getHledgerCliOpts, |   argsToCliOpts, | ||||||
|   moveFlagsAfterCommand, |   moveFlagsAfterCommand, | ||||||
|  |   decodeRawOpts, | ||||||
|  |   checkCliOpts, | ||||||
|  |   rawOptsToCliOpts, | ||||||
|   optserror, |   optserror, | ||||||
|   showModeHelp, |   showModeHelp, | ||||||
|  |   debugArgs, | ||||||
|  |   getCliOpts, | ||||||
| 
 | 
 | ||||||
|   -- * tests |   -- * tests | ||||||
|   tests_Hledger_Cli_Options |   tests_Hledger_Cli_Options | ||||||
| @ -66,9 +86,11 @@ where | |||||||
|    |    | ||||||
| import qualified Control.Exception as C | import qualified Control.Exception as C | ||||||
| -- import Control.Monad (filterM) | -- import Control.Monad (filterM) | ||||||
|  | import Control.Monad (when) | ||||||
| import Data.List | import Data.List | ||||||
| import Data.List.Split | import Data.List.Split | ||||||
| import Data.Maybe | import Data.Maybe | ||||||
|  | import Data.PPrint (pprint) | ||||||
| import Data.Time.Calendar | import Data.Time.Calendar | ||||||
| import Safe | import Safe | ||||||
| import System.Console.CmdArgs | import System.Console.CmdArgs | ||||||
| @ -76,6 +98,7 @@ import System.Console.CmdArgs.Explicit | |||||||
| import System.Console.CmdArgs.Text | import System.Console.CmdArgs.Text | ||||||
| import System.Directory | import System.Directory | ||||||
| import System.Environment | import System.Environment | ||||||
|  | import System.Exit | ||||||
| import Test.HUnit | import Test.HUnit | ||||||
| import Text.ParserCombinators.Parsec as P | import Text.ParserCombinators.Parsec as P | ||||||
| import Text.Printf | import Text.Printf | ||||||
| @ -107,7 +130,7 @@ helpflags = [ | |||||||
| inputflags = [ | inputflags = [ | ||||||
|   flagReq ["file","f"]  (\s opts -> Right $ setopt "file" s opts) "FILE" "use a different journal file; - means stdin" |   flagReq ["file","f"]  (\s opts -> Right $ setopt "file" s opts) "FILE" "use a different journal file; - means stdin" | ||||||
|  ,flagReq ["rules-file"]  (\s opts -> Right $ setopt "rules-file" s opts) "RULESFILE" "conversion rules for CSV (default: FILE.rules)" |  ,flagReq ["rules-file"]  (\s opts -> Right $ setopt "rules-file" s opts) "RULESFILE" "conversion rules for CSV (default: FILE.rules)" | ||||||
|  ,flagReq ["alias"]  (\s opts -> Right $ setopt "alias" s opts)  "ACCT=ALIAS" "display ACCT's name as ALIAS in reports" |  ,flagReq ["alias"]  (\s opts -> Right $ setopt "alias" s opts)  "ACCT=ALIAS" "convert ACCT's name to ALIAS" | ||||||
|  ] |  ] | ||||||
| 
 | 
 | ||||||
| -- | Common report-related flags: --period, --cost, --display etc. | -- | Common report-related flags: --period, --cost, --display etc. | ||||||
| @ -137,7 +160,9 @@ generalflagsgroup1 = (generalflagstitle, inputflags ++ reportflags ++ helpflags) | |||||||
| generalflagsgroup2 = (generalflagstitle, inputflags ++ helpflags) | generalflagsgroup2 = (generalflagstitle, inputflags ++ helpflags) | ||||||
| generalflagsgroup3 = (generalflagstitle, helpflags) | generalflagsgroup3 = (generalflagstitle, helpflags) | ||||||
| 
 | 
 | ||||||
| -- | Template for creating our modes. | -- cmdargs modes | ||||||
|  | 
 | ||||||
|  | -- | A basic mode template. | ||||||
| defMode :: Mode RawOpts | defMode :: Mode RawOpts | ||||||
| defMode =   Mode { | defMode =   Mode { | ||||||
|   modeNames = [] |   modeNames = [] | ||||||
| @ -147,11 +172,44 @@ defMode =   Mode { | |||||||
|  ,modeCheck = Right |  ,modeCheck = Right | ||||||
|  ,modeReform = const Nothing |  ,modeReform = const Nothing | ||||||
|  ,modeExpandAt = True |  ,modeExpandAt = True | ||||||
|  ,modeGroupFlags = toGroup [] |  ,modeGroupFlags = Group { | ||||||
|  |      groupNamed = [] | ||||||
|  |     ,groupUnnamed = [ | ||||||
|  |         flagNone ["help","h","?"] (setboolopt "help") "Display command help." | ||||||
|  |         ] | ||||||
|  |     ,groupHidden = [] | ||||||
|  |     } | ||||||
|  ,modeArgs = ([], Nothing) |  ,modeArgs = ([], Nothing) | ||||||
|  ,modeGroupModes = toGroup [] |  ,modeGroupModes = toGroup [] | ||||||
|  } |  } | ||||||
| 
 | 
 | ||||||
|  | -- | A basic subcommand mode with the given command name(s). | ||||||
|  | defCommandMode names = defMode { | ||||||
|  |    modeNames=names | ||||||
|  |   ,modeValue=[("command", headDef "" names)] | ||||||
|  |   ,modeArgs = ([], Just $ argsFlag "[PATTERNS]") | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | -- | A basic subcommand mode suitable for an add-on command. | ||||||
|  | defAddonCommandMode addon = defMode { | ||||||
|  |    modeNames = [addon] | ||||||
|  |   ,modeHelp = printf "run %s-%s" progname addon | ||||||
|  |   ,modeValue=[("command",addon)] | ||||||
|  |   ,modeGroupFlags = Group { | ||||||
|  |       groupUnnamed = [] | ||||||
|  |      ,groupHidden = [] | ||||||
|  |      ,groupNamed = [generalflagsgroup1] | ||||||
|  |      } | ||||||
|  |   ,modeArgs = ([], Just $ argsFlag "[ARGS]") | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | -- | Add command aliases to the command's help string. | ||||||
|  | withAliases :: String -> [String] -> String | ||||||
|  | s `withAliases` []     = s | ||||||
|  | s `withAliases` (a:[]) = s ++ " (alias: " ++ a ++ ")" | ||||||
|  | s `withAliases` as     = s ++ " (aliases: " ++ intercalate ", " as ++ ")" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| -- | The top-level cmdargs mode for hledger. | -- | The top-level cmdargs mode for hledger. | ||||||
| mainmode addons = defMode { | mainmode addons = defMode { | ||||||
|   modeNames = [progname] |   modeNames = [progname] | ||||||
| @ -211,49 +269,7 @@ mainmode addons = defMode { | |||||||
| --   -- ,"When using both, not: comes last." | --   -- ,"When using both, not: comes last." | ||||||
| --  ] | --  ] | ||||||
| 
 | 
 | ||||||
| -- | -- visible subcommand modes | ||||||
| -- cmdargs modes for subcommands |  | ||||||
| -- |  | ||||||
| 
 |  | ||||||
| -- | Make a basic command mode given the command's name and any aliases. |  | ||||||
| defCommandMode names = defMode { |  | ||||||
|    modeNames=names |  | ||||||
|   ,modeValue=[("command", headDef "" names)] |  | ||||||
|   ,modeArgs = ([], Just $ argsFlag "[PATTERNS]") |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| -- | Make a basic command mode suitable for an add-on command. |  | ||||||
| defAddonCommandMode addon = defMode { |  | ||||||
|    modeNames = [addon] |  | ||||||
|   ,modeHelp = printf "run %s-%s" progname addon |  | ||||||
|   ,modeValue=[("command",addon)] |  | ||||||
|   ,modeGroupFlags = Group { |  | ||||||
|       groupUnnamed = [] |  | ||||||
|      ,groupHidden = [] |  | ||||||
|      ,groupNamed = [generalflagsgroup1] |  | ||||||
|      } |  | ||||||
|   ,modeArgs = ([], Just $ argsFlag "[ARGS]") |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| withAliases :: String -> [String] -> String |  | ||||||
| s `withAliases` []     = s |  | ||||||
| s `withAliases` (a:[]) = s ++ " (alias: " ++ a ++ ")" |  | ||||||
| s `withAliases` as     = s ++ " (aliases: " ++ intercalate ", " as ++ ")" |  | ||||||
| 
 |  | ||||||
| -- hidden commands |  | ||||||
| 
 |  | ||||||
| convertmode = (defCommandMode ["convert"]) { |  | ||||||
|   modeValue = [("command","convert")] |  | ||||||
|  ,modeHelp = "convert is no longer needed, just use -f FILE.csv" |  | ||||||
|  ,modeArgs = ([], Just $ argsFlag "[CSVFILE]") |  | ||||||
|  ,modeGroupFlags = Group { |  | ||||||
|      groupUnnamed = [] |  | ||||||
|     ,groupHidden = helpflags |  | ||||||
|     ,groupNamed = [] |  | ||||||
|     } |  | ||||||
|  } |  | ||||||
| 
 |  | ||||||
| -- visible commands |  | ||||||
| 
 | 
 | ||||||
| addmode = (defCommandMode ["add"]) { | addmode = (defCommandMode ["add"]) { | ||||||
|   modeHelp = "prompt for new transaction entries and add them to the journal" |   modeHelp = "prompt for new transaction entries and add them to the journal" | ||||||
| @ -375,6 +391,19 @@ testmode = (defCommandMode ["test"]) { | |||||||
|     } |     } | ||||||
|  } |  } | ||||||
| 
 | 
 | ||||||
|  | -- hidden commands | ||||||
|  | 
 | ||||||
|  | convertmode = (defCommandMode ["convert"]) { | ||||||
|  |   modeValue = [("command","convert")] | ||||||
|  |  ,modeHelp = "convert is no longer needed, just use -f FILE.csv" | ||||||
|  |  ,modeArgs = ([], Just $ argsFlag "[CSVFILE]") | ||||||
|  |  ,modeGroupFlags = Group { | ||||||
|  |      groupUnnamed = [] | ||||||
|  |     ,groupHidden = helpflags | ||||||
|  |     ,groupNamed = [] | ||||||
|  |     } | ||||||
|  |  } | ||||||
|  | 
 | ||||||
| -- | -- | ||||||
| -- 2. A package-specific data structure holding options used in this | -- 2. A package-specific data structure holding options used in this | ||||||
| -- package and above, parsed from RawOpts.  This represents the | -- package and above, parsed from RawOpts.  This represents the | ||||||
| @ -411,8 +440,8 @@ instance Default CliOpts where def = defcliopts | |||||||
| -- | Parse raw option string values to the desired final data types. | -- | Parse raw option string values to the desired final data types. | ||||||
| -- Any relative smart dates will be converted to fixed dates based on | -- Any relative smart dates will be converted to fixed dates based on | ||||||
| -- today's date. Parsing failures will raise an error. | -- today's date. Parsing failures will raise an error. | ||||||
| toCliOpts :: RawOpts -> IO CliOpts | rawOptsToCliOpts :: RawOpts -> IO CliOpts | ||||||
| toCliOpts rawopts = do | rawOptsToCliOpts rawopts = do | ||||||
|   d <- getCurrentDay |   d <- getCurrentDay | ||||||
|   return defcliopts { |   return defcliopts { | ||||||
|               rawopts_         = rawopts |               rawopts_         = rawopts | ||||||
| @ -451,16 +480,15 @@ toCliOpts rawopts = do | |||||||
|                             } |                             } | ||||||
|              } |              } | ||||||
| 
 | 
 | ||||||
| -- | Parse hledger CLI options from the command line arguments and | -- | Parse hledger CLI options from these command line arguments and | ||||||
| -- specified add-on command names, or raise any error. | -- add-on command names, or raise any error. | ||||||
| getHledgerCliOpts :: [String] -> IO CliOpts | argsToCliOpts :: [String] -> [String] -> IO CliOpts | ||||||
| getHledgerCliOpts addons = do | argsToCliOpts args addons = do | ||||||
|   args <- getArgs |  | ||||||
|   let |   let | ||||||
|     args'        = moveFlagsAfterCommand args |     args'        = moveFlagsAfterCommand args | ||||||
|     cmdargsopts  = System.Console.CmdArgs.Explicit.processValue (mainmode addons) args' |     cmdargsopts  = System.Console.CmdArgs.Explicit.processValue (mainmode addons) args' | ||||||
|     cmdargsopts' = decodeRawOpts cmdargsopts |     cmdargsopts' = decodeRawOpts cmdargsopts | ||||||
|   toCliOpts cmdargsopts' >>= checkCliOpts |   rawOptsToCliOpts cmdargsopts' >>= checkCliOpts | ||||||
| 
 | 
 | ||||||
| -- | A hacky workaround for cmdargs not accepting flags before the | -- | A hacky workaround for cmdargs not accepting flags before the | ||||||
| -- subcommand name: try to detect and move such flags after the | -- subcommand name: try to detect and move such flags after the | ||||||
| @ -505,7 +533,9 @@ checkCliOpts opts@CliOpts{reportopts_=ropts} = do | |||||||
|     Right _ -> return () |     Right _ -> return () | ||||||
|   return opts |   return opts | ||||||
| 
 | 
 | ||||||
|  | -- | ||||||
| -- utils | -- utils | ||||||
|  | -- | ||||||
| 
 | 
 | ||||||
| -- | Get the unique suffixes (without hledger-) of hledger-* executables | -- | Get the unique suffixes (without hledger-) of hledger-* executables | ||||||
| -- found in the current user's PATH, or the empty list if there is any | -- found in the current user's PATH, or the empty list if there is any | ||||||
| @ -677,6 +707,31 @@ showModeHelp = | |||||||
|   . |   . | ||||||
|   (helpText [] HelpFormatDefault :: Mode a -> [Text]) |   (helpText [] HelpFormatDefault :: Mode a -> [Text]) | ||||||
| 
 | 
 | ||||||
|  | -- | Print debug info about arguments and options if --debug is present. | ||||||
|  | debugArgs :: [String] -> CliOpts -> IO () | ||||||
|  | debugArgs args opts = | ||||||
|  |   when ("--debug" `elem` args) $ do | ||||||
|  |     progname <- getProgName | ||||||
|  |     putStrLn $ "running: " ++ progname | ||||||
|  |     putStrLn $ "raw args: " ++ show args | ||||||
|  |     putStrLn $ "processed opts:\n" ++ show opts | ||||||
|  |     putStrLn . show =<< pprint opts | ||||||
|  |     d <- getCurrentDay | ||||||
|  |     putStrLn $ "search query: " ++ (show $ queryFromOpts d $ reportopts_ opts) | ||||||
|  | 
 | ||||||
|  | -- | Parse hledger CLI options from the command line using the given | ||||||
|  | -- cmdargs mode, and either return them or, if a help flag is present, | ||||||
|  | -- print the mode help and exit the program. | ||||||
|  | getCliOpts :: Mode RawOpts -> IO CliOpts | ||||||
|  | getCliOpts mode = do | ||||||
|  |   args <- getArgs | ||||||
|  |   let rawopts = decodeRawOpts $ processValue mode args | ||||||
|  |   opts <- rawOptsToCliOpts rawopts >>= checkCliOpts | ||||||
|  |   debugArgs args opts | ||||||
|  |   -- if any (`elem` args) ["--help","-h","-?"] | ||||||
|  |   when ("help" `inRawOpts` rawopts_ opts) $ | ||||||
|  |     putStr (showModeHelp mode) >> exitSuccess | ||||||
|  |   return opts | ||||||
| 
 | 
 | ||||||
| tests_Hledger_Cli_Options = TestList | tests_Hledger_Cli_Options = TestList | ||||||
|  [ |  [ | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user