Coverage for hledger_lots/cli.py: 47%

118 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-05-05 13:34 -0300

1from typing import Tuple, TypedDict 

2import os 

3import rich_click as click 

4 

5from .avg_info import AllAvgInfo, AvgInfo 

6from .fifo_info import AllFifoInfo, FifoInfo 

7from .info import AllInfo 

8from .lib import get_files_comm, get_default_file, get_file_from_stdin 

9from .options import Options, get_options 

10from .prices_yahoo import YahooPrices 

11from .prompt import get_append_file 

12from .prompt_buy import PromptBuy 

13from .prompt_sell import PromptSell 

14 

15 

16class Obj(TypedDict): 

17 file: Tuple[str, ...] 

18 opt: Options 

19 

20 

21CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) 

22 

23click.rich_click.USE_MARKDOWN = True 

24click.rich_click.SHOW_ARGUMENTS = True 

25click.rich_click.GROUP_ARGUMENTS_OPTIONS = True 

26click.rich_click.MAX_WIDTH = 80 

27click.rich_click.SHOW_METAVARS_COLUMN = False 

28click.rich_click.APPEND_METAVARS_HELP = True 

29click.rich_click.STYLE_ERRORS_SUGGESTION = "magenta italic" 

30click.rich_click.ERRORS_SUGGESTION = ( 

31 "Try running the '--help' flag for more information." 

32) 

33click.rich_click.ERRORS_EPILOGUE = "To find out more, visit [link=https://github.com/edkedk99/hledger-lots]https://github.com/edkedk99/hledger-lots[/link]" 

34click.rich_click.STYLE_OPTIONS_TABLE_LEADING = 1 

35click.rich_click.STYLE_OPTIONS_TABLE_BOX = "SIMPLE" 

36click.rich_click.STYLE_OPTIONS_PANEL_BORDER = "dim" # Possibly conceal 

37 

38 

39@click.group(context_settings=CONTEXT_SETTINGS) 

40@click.option( 

41 "-f", 

42 "--file", 

43 required=True, 

44 default=lambda: get_default_file(), 

45 multiple=True, 

46 help="Inform the journal file path. If \"-\", read from stdin. Without this flag read from $LEDGER_FILE or ~/.hledger.journal in this order or '-f-'.", 

47) 

48@click.pass_obj 

49@click.version_option() 

50def cli(obj, file: Tuple[str, ...]): 

51 """ 

52 Commands to apply FIFO(first-in-first-out) or AVERAGE COST accounting principles without manual management of lots. Useful for transactions involving buying and selling foreign currencies or stocks. 

53 

54 To find out more, visit [https://github.com/edkedk99/hledger-lots](https://github.com/edkedk99/hledger-lots) 

55 """ 

56 

57 

58 

59 if file[0] == "-": 

60 stdin_file = (get_file_from_stdin(),) 

61 opt = get_options(stdin_file) 

62 obj["file"] = stdin_file 

63 else: 

64 opt = get_options(file) 

65 obj["file"] = file 

66 

67 obj["opt"] = opt 

68 

69 

70@click.command() 

71@click.pass_obj 

72def buy(obj: Obj): 

73 """ 

74 Create a purchase transaction for a commodity by answering some prompts that tries to avoid errors with validation and using current journal data to filter possible answers and give informations that guides the user thru the process.\r 

75 

76 ### Transaction postings 

77 

78 - First posting: Negative amount on the cash account where the money was used to pay for the commodity 

79 

80 - Second Posting: Positive amount of the commodity being bought with its cost using \"@\" symbol 

81 """ 

82 

83 file = obj["file"] 

84 opt = obj["opt"] 

85 prompt_buy = PromptBuy(file, opt.avg_cost, opt.check, opt.no_desc) 

86 txn_print = prompt_buy.get_hl_txn() 

87 click.echo("\n" + txn_print) 

88 

89 append_file = get_append_file(file[0]) 

90 if append_file: 

91 with open(append_file, "a") as f: 

92 f.write("\n" + txn_print) 

93 else: 

94 click.echo("\n" + "Transaction not saved.") 

95 

96 commodity = prompt_buy.info["comm"] 

97 if opt.avg_cost: 

98 info = AvgInfo(file, commodity, opt.check) 

99 else: 

100 info = FifoInfo(file, commodity, opt.check) 

101 

102 click.echo(info.table) 

103 click.echo(info.info_txt) 

104 

105 

106@click.command() 

107@click.pass_obj 

108def sell(obj: Obj): 

109 """ 

110 Create a transaction with automatic FIFO or AVERAGE COST for a commodity by answering some prompts that tries to avoid errors with validation and using current journal data to filter possible answers give informations that guides the user thru the process.\r 

111 

112 > This command also add transaction's comment with some indicators. See an example on "Output examples" page of the docs. 

113 

114 ### Transaction postings 

115 

116 - First posting: Positive amount on the 'base-currency' in the account that receives the product of the sale. 

117 

118 - Multiple lot postings: Each posting represents a lot you are selling for the cost price on purchasing date, according to FIFO accounting principles or one postings in case of AVERAGE COST method. 

119 

120 - Revenue posting: posting that represent Capital Gain or Loss as the difference between the total cost and the amount received on the base-currency. 

121 """ 

122 file = obj["file"] 

123 opt = obj["opt"] 

124 prompt_sell = PromptSell(file, opt.avg_cost, opt.check, opt.no_desc) 

125 

126 txn_print = prompt_sell.get_hl_txn() 

127 click.echo("\n" + txn_print) 

128 

129 append_file = get_append_file(file[0]) 

130 if append_file: 

131 with open(append_file, "a") as f: 

132 f.write("\n" + txn_print) 

133 else: 

134 click.echo("\n" + "Transaction not saved.") 

135 

136 commodity = prompt_sell.info["comm"] 

137 if opt.avg_cost: 

138 info = AvgInfo(file, commodity, opt.check) 

139 else: 

140 info = FifoInfo(file, commodity, opt.check) 

141 

142 click.echo(info.table) 

143 click.echo(info.info_txt) 

144 

145 if commodity.startswith("y."): 

146 files_comm_str = " ".join(get_files_comm(file)) 

147 click.echo( 

148 f"To update prices from Yahoo finance, run:\n\n hledger-lots {files_comm_str} view -c {commodity} -p {file[0]}" 

149 ) 

150 

151 

152@click.command() 

153@click.argument("commodity", type=click.STRING, required=True) 

154@click.pass_obj 

155def view(obj: Obj, commodity: str): 

156 """ 

157 Report lots for a commodity.\r 

158 

159 Show a report with lots for a commodity considering eventual past sale using FIFO or AVERAGE COST accounting principles. 

160 

161 Also show some indicators about the lots and performance if there is prices in the journal after the last purchase. See the docs for details 

162 """ 

163 file = obj["file"] 

164 opt = obj["opt"] 

165 

166 if opt.avg_cost: 

167 info = AvgInfo(file, commodity, opt.check, opt.no_desc) 

168 else: 

169 info = FifoInfo(file, commodity, opt.check, opt.no_desc) 

170 

171 click.echo(info.table) 

172 click.echo(info.info_txt) 

173 

174 

175@click.command(name="list") 

176@click.option( 

177 "-o", 

178 "--output-format", 

179 type=click.Choice(["plain", "pretty", "csv"]), 

180 default="plain", 

181 help="Format to output the report", 

182) 

183@click.pass_obj 

184def list_commodities(obj: Obj, output_format: str): 

185 """ 

186 List indicators for all your commodities in a tabular format sorted from higher to lower **XIRR**. It is advised to use full-screen of the terminal. See the docs for a list of indicators and output examples. 

187 

188 It can output in three formats: *plain, pretty and csv*. 

189 """ 

190 

191 file = obj["file"] 

192 opt = obj["opt"] 

193 lots_info = AllInfo(file, opt.no_desc) 

194 

195 lots_info = ( 

196 AllAvgInfo(file, opt.no_desc, opt.check) 

197 if opt.avg_cost 

198 else AllFifoInfo(file, opt.no_desc, opt.check) 

199 ) 

200 

201 if output_format == "pretty": 

202 table = lots_info.infos_table("mixed_grid") 

203 elif output_format == "csv": 

204 infos_io = lots_info.infos_csv() 

205 table = infos_io.read() 

206 else: 

207 table = lots_info.infos_table("plain") 

208 

209 click.echo(table) 

210 

211 

212@click.command() 

213@click.pass_obj 

214def prices(obj: Obj): 

215 """ 

216 Download market prices from Yahoo Finance and print as **price directives**. Use *BASH* redirection to append to the journal or copy/paste the data. 

217 

218 ### Setup 

219 Add a \"yahoo_ticker\" tag to the *commodity directive* with the value of the ticker in Yahoo Finance to download prices 

220 

221 ### Example 

222 

223 ```text 

224 commodity AAPL ; yahoo_ticker:AAPL 

225 commodity \"PETR4\" ; yahoo_ticker:PETR4.SA 

226 commodity BTC ; yahoo_ticker:BTC-USD 

227 ``` 

228 """ 

229 

230 file = obj["file"] 

231 

232 yahoo_prices = YahooPrices(file) 

233 yahoo_prices.print_prices() 

234 

235 

236cli.add_command(buy) 

237cli.add_command(sell) 

238cli.add_command(view) 

239cli.add_command(list_commodities) 

240cli.add_command(prices)