coinpare 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/exe/coinpare +4 -4
- data/lib/coinpare/cli.rb +73 -64
- data/lib/coinpare/command.rb +20 -17
- data/lib/coinpare/commands/coins.rb +44 -40
- data/lib/coinpare/commands/holdings.rb +94 -95
- data/lib/coinpare/commands/markets.rb +27 -27
- data/lib/coinpare/fetcher.rb +11 -9
- data/lib/coinpare/version.rb +1 -1
- metadata +47 -57
- data/Rakefile +0 -9
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/coinpare.gemspec +0 -45
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
|
12
|
-
require_relative
|
13
|
-
require_relative
|
3
|
+
require "toml"
|
4
|
+
require "pastel"
|
5
|
+
require "tty-config"
|
6
|
+
require "tty-prompt"
|
7
|
+
require "tty-spinner"
|
8
|
+
require "tty-table"
|
9
|
+
require "tty-pie"
|
10
|
+
require "timers"
|
11
|
+
|
12
|
+
require_relative "../command"
|
13
|
+
require_relative "../fetcher"
|
14
14
|
|
15
15
|
module Coinpare
|
16
16
|
module Commands
|
@@ -23,16 +23,16 @@ module Coinpare
|
|
23
23
|
@timers = Timers::Group.new
|
24
24
|
@spinner = TTY::Spinner.new(":spinner Fetching data...",
|
25
25
|
format: :dots, clear: true)
|
26
|
-
@interval = @options.fetch(
|
27
|
-
config.set(
|
26
|
+
@interval = @options.fetch("watch", DEFAULT_INTERVAL).to_f
|
27
|
+
config.set("settings", "color", value: !@options["no-color"])
|
28
28
|
end
|
29
29
|
|
30
30
|
def execute(input: $stdin, output: $stdout)
|
31
31
|
config_saved = config.exist?
|
32
|
-
if config_saved && @options[
|
32
|
+
if config_saved && @options["edit"]
|
33
33
|
editor.open(config.source_file)
|
34
34
|
return
|
35
|
-
elsif @options[
|
35
|
+
elsif @options["edit"]
|
36
36
|
output.puts "Sorry, no holdings configuration found."
|
37
37
|
output.print "Run \""
|
38
38
|
output.print "$ #{add_color('coinpare holdings', :yellow)}\" "
|
@@ -42,29 +42,29 @@ module Coinpare
|
|
42
42
|
|
43
43
|
config.read if config_saved
|
44
44
|
|
45
|
-
holdings = config.fetch(
|
45
|
+
holdings = config.fetch("holdings")
|
46
46
|
if holdings.nil? || (holdings && holdings.empty?)
|
47
47
|
info = setup_portfolio(input, output)
|
48
48
|
config.merge(info)
|
49
|
-
elsif @options[
|
49
|
+
elsif @options["add"]
|
50
50
|
coin_info = add_coin(input, output)
|
51
|
-
config.append(coin_info, to: [
|
52
|
-
elsif @options[
|
51
|
+
config.append(coin_info, to: ["holdings"])
|
52
|
+
elsif @options["remove"]
|
53
53
|
coin_info = remove_coin(input, output)
|
54
|
-
config.remove(*coin_info, from: [
|
55
|
-
elsif @options[
|
54
|
+
config.remove(*coin_info, from: ["holdings"])
|
55
|
+
elsif @options["clear"]
|
56
56
|
prompt = create_prompt(input, output)
|
57
|
-
answer = prompt.yes?(
|
57
|
+
answer = prompt.yes?("Do you want to remove all holdings?")
|
58
58
|
if answer
|
59
|
-
config.delete(
|
59
|
+
config.delete("holdings")
|
60
60
|
output.puts add_color("All holdings removed", :red)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
holdings = config.fetch(
|
64
|
+
holdings = config.fetch("holdings")
|
65
65
|
no_holdings_left = holdings.nil? || (holdings && holdings.empty?)
|
66
66
|
if no_holdings_left
|
67
|
-
config.delete(
|
67
|
+
config.delete("holdings")
|
68
68
|
end
|
69
69
|
|
70
70
|
# Persist current configuration
|
@@ -77,15 +77,15 @@ module Coinpare
|
|
77
77
|
end
|
78
78
|
|
79
79
|
@spinner.auto_spin
|
80
|
-
settings = config.fetch(
|
80
|
+
settings = config.fetch("settings")
|
81
81
|
# command options take precedence over config settings
|
82
82
|
overridden_settings = {}
|
83
|
-
overridden_settings[
|
84
|
-
overridden_settings[
|
85
|
-
holdings = config.fetch(
|
86
|
-
names = holdings.map { |c| c[
|
83
|
+
overridden_settings["exchange"] = @options.fetch("exchange", settings.fetch("exchange"))
|
84
|
+
overridden_settings["base"] = @options.fetch("base", settings.fetch("base"))
|
85
|
+
holdings = config.fetch("holdings") { [] }
|
86
|
+
names = holdings.map { |c| c["name"] }
|
87
87
|
|
88
|
-
if @options[
|
88
|
+
if @options["watch"]
|
89
89
|
output.print cursor.hide
|
90
90
|
@timers.now_and_every(@interval) do
|
91
91
|
display_coins(output, names, overridden_settings)
|
@@ -96,7 +96,7 @@ module Coinpare
|
|
96
96
|
end
|
97
97
|
ensure
|
98
98
|
@spinner.stop
|
99
|
-
if @options[
|
99
|
+
if @options["watch"]
|
100
100
|
@timers.cancel
|
101
101
|
output.print cursor.clear_screen_down
|
102
102
|
output.print cursor.show
|
@@ -104,14 +104,14 @@ module Coinpare
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def display_coins(output, names, overridden_settings)
|
107
|
-
response = Fetcher.fetch_prices(names.join(
|
108
|
-
overridden_settings[
|
107
|
+
response = Fetcher.fetch_prices(names.join(","),
|
108
|
+
overridden_settings["base"].upcase,
|
109
109
|
overridden_settings)
|
110
110
|
return unless response
|
111
|
-
table = if @options[
|
112
|
-
setup_table_with_pies(response[
|
111
|
+
table = if @options["pie"]
|
112
|
+
setup_table_with_pies(response["RAW"], response["DISPLAY"])
|
113
113
|
else
|
114
|
-
setup_table(response[
|
114
|
+
setup_table(response["RAW"], response["DISPLAY"])
|
115
115
|
end
|
116
116
|
|
117
117
|
@spinner.stop
|
@@ -119,7 +119,7 @@ module Coinpare
|
|
119
119
|
lines = banner(overridden_settings).lines.size + 1 + (table.rows_size + 3)
|
120
120
|
clear_output(output, lines) do
|
121
121
|
output.puts banner(overridden_settings)
|
122
|
-
if @options[
|
122
|
+
if @options["pie"]
|
123
123
|
output.puts table.render(:unicode, padding: [0, 2])
|
124
124
|
else
|
125
125
|
output.puts table.render(:unicode, padding: [0, 1], alignment: :right)
|
@@ -128,9 +128,9 @@ module Coinpare
|
|
128
128
|
end
|
129
129
|
|
130
130
|
def clear_output(output, lines)
|
131
|
-
output.print cursor.clear_screen_down if @options[
|
131
|
+
output.print cursor.clear_screen_down if @options["watch"]
|
132
132
|
yield if block_given?
|
133
|
-
output.print cursor.up(lines) if @options[
|
133
|
+
output.print cursor.up(lines) if @options["watch"]
|
134
134
|
end
|
135
135
|
|
136
136
|
def create_prompt(input, output)
|
@@ -138,32 +138,31 @@ module Coinpare
|
|
138
138
|
prefix: "[#{add_color('c', :yellow)}] ",
|
139
139
|
input: input, output: output,
|
140
140
|
interrupt: -> { puts; exit 1 },
|
141
|
-
enable_color: !@options[
|
142
|
-
clear: true
|
141
|
+
enable_color: !@options["no-color"]
|
143
142
|
)
|
144
143
|
prompt.on(:keypress) { |event|
|
145
|
-
prompt.trigger(:keydown) if event.value ==
|
146
|
-
prompt.trigger(:keyup) if event.value ==
|
144
|
+
prompt.trigger(:keydown) if event.value == "j"
|
145
|
+
prompt.trigger(:keyup) if event.value == "k"
|
147
146
|
}
|
148
147
|
prompt
|
149
148
|
end
|
150
149
|
|
151
150
|
def ask_coin
|
152
151
|
-> (prompt) do
|
153
|
-
key(
|
154
|
-
q.default
|
155
|
-
q.required(true,
|
156
|
-
q.validate(/\w{2,}/,
|
152
|
+
key("name").ask("What coin do you own?") do |q|
|
153
|
+
q.default "BTC"
|
154
|
+
q.required(true, "You need to provide a coin")
|
155
|
+
q.validate(/\w{2,}/, "Currency can only be chars.")
|
157
156
|
q.convert ->(coin) { coin.upcase }
|
158
157
|
end
|
159
|
-
key(
|
160
|
-
q.required(true,
|
161
|
-
q.validate(/[\d.]+/,
|
158
|
+
key("amount").ask("What amount?") do |q|
|
159
|
+
q.required(true, "You need to provide an amount")
|
160
|
+
q.validate(/[\d.]+/, "Invalid amount provided")
|
162
161
|
q.convert ->(am) { am.to_f }
|
163
162
|
end
|
164
|
-
key(
|
165
|
-
q.required(true,
|
166
|
-
q.validate(/[\d.]+/,
|
163
|
+
key("price").ask("At what price per coin?") do |q|
|
164
|
+
q.required(true, "You need to provide a price")
|
165
|
+
q.validate(/[\d.]+/, "Invalid prince provided")
|
167
166
|
q.convert ->(p) { p.to_f }
|
168
167
|
end
|
169
168
|
end
|
@@ -180,8 +179,8 @@ module Coinpare
|
|
180
179
|
|
181
180
|
def remove_coin(input, output)
|
182
181
|
prompt = create_prompt(input, output)
|
183
|
-
holdings = config.fetch(
|
184
|
-
data = prompt.multi_select(
|
182
|
+
holdings = config.fetch("holdings")
|
183
|
+
data = prompt.multi_select("Which hodlings to remove?") do |menu|
|
185
184
|
holdings.each do |holding|
|
186
185
|
menu.choice "#{holding['name']} (#{holding['amount']})", holding
|
187
186
|
end
|
@@ -198,26 +197,26 @@ module Coinpare
|
|
198
197
|
prompt = create_prompt(input, output)
|
199
198
|
context = self
|
200
199
|
data = prompt.collect do
|
201
|
-
key(
|
202
|
-
key(
|
200
|
+
key("settings") do
|
201
|
+
key("base").ask("What base currency to convert holdings to?") do |q|
|
203
202
|
q.default "USD"
|
204
203
|
q.convert ->(b) { b.upcase }
|
205
|
-
q.validate(/\w{3}/,
|
204
|
+
q.validate(/\w{3}/, "Currency code needs to be 3 chars long")
|
206
205
|
end
|
207
|
-
key(
|
206
|
+
key("exchange").ask("What exchange would you like to use?") do |q|
|
208
207
|
q.default "CCCAGG"
|
209
208
|
q.required true
|
210
209
|
end
|
211
210
|
end
|
212
211
|
|
213
212
|
while prompt.yes?("Do you want to add coin to your altfolio?")
|
214
|
-
key(
|
213
|
+
key("holdings").values(&context.ask_coin)
|
215
214
|
end
|
216
215
|
end
|
217
216
|
|
218
217
|
lines = 4 + # intro
|
219
218
|
2 + # base + exchange
|
220
|
-
data[
|
219
|
+
data["holdings"].size * 4 + 1
|
221
220
|
output.print cursor.up(lines)
|
222
221
|
output.print cursor.clear_screen_down
|
223
222
|
|
@@ -226,24 +225,24 @@ module Coinpare
|
|
226
225
|
|
227
226
|
def create_pie_charts(raw_data, display_data)
|
228
227
|
colors = %i[yellow blue green cyan magenta red]
|
229
|
-
radius = @options[
|
230
|
-
base = @options.fetch(
|
228
|
+
radius = @options["pie"].to_i > 0 ? @options["pie"].to_i : DEFAULT_PIE_RADIUS
|
229
|
+
base = @options.fetch("base", config.fetch("settings", "base")).upcase
|
231
230
|
to_symbol = nil
|
232
231
|
past_data = []
|
233
232
|
curr_data = []
|
234
233
|
|
235
|
-
config.fetch(
|
236
|
-
coin_data = raw_data[coin[
|
237
|
-
to_symbol = display_data[coin[
|
238
|
-
past_price = coin[
|
239
|
-
curr_price = coin[
|
234
|
+
config.fetch("holdings").each do |coin|
|
235
|
+
coin_data = raw_data[coin["name"]][base]
|
236
|
+
to_symbol = display_data[coin["name"]][base]["TOSYMBOL"]
|
237
|
+
past_price = coin["amount"] * coin["price"]
|
238
|
+
curr_price = coin["amount"] * coin_data["PRICE"]
|
240
239
|
|
241
|
-
past_data << { name: coin[
|
242
|
-
curr_data << { name: coin[
|
240
|
+
past_data << { name: coin["name"], value: past_price }
|
241
|
+
curr_data << { name: coin["name"], value: curr_price }
|
243
242
|
end
|
244
243
|
|
245
244
|
options = {
|
246
|
-
colors: !@options[
|
245
|
+
colors: !@options["no-color"] && colors,
|
247
246
|
radius: radius,
|
248
247
|
legend: {
|
249
248
|
left: 2,
|
@@ -253,8 +252,8 @@ module Coinpare
|
|
253
252
|
}
|
254
253
|
|
255
254
|
[
|
256
|
-
TTY::Pie.new(options.merge(data: past_data)),
|
257
|
-
TTY::Pie.new(options.merge(data: curr_data)),
|
255
|
+
TTY::Pie.new(**options.merge(data: past_data)),
|
256
|
+
TTY::Pie.new(**options.merge(data: curr_data)),
|
258
257
|
to_symbol
|
259
258
|
]
|
260
259
|
end
|
@@ -273,8 +272,8 @@ module Coinpare
|
|
273
272
|
|
274
273
|
table = TTY::Table.new(
|
275
274
|
header: [
|
276
|
-
{value: header_past, alignment: :center},
|
277
|
-
{value: header_curr, alignment: :center}
|
275
|
+
{ value: header_past, alignment: :center },
|
276
|
+
{ value: header_curr, alignment: :center }
|
278
277
|
]
|
279
278
|
)
|
280
279
|
past_pie.to_s.split("\n").zip(curr_pie.to_s.split("\n")).each do |past_part, curr_part|
|
@@ -284,36 +283,36 @@ module Coinpare
|
|
284
283
|
end
|
285
284
|
|
286
285
|
def setup_table(raw_data, display_data)
|
287
|
-
base = @options.fetch(
|
286
|
+
base = @options.fetch("base", config.fetch("settings", "base")).upcase
|
288
287
|
total_buy = 0
|
289
288
|
total = 0
|
290
289
|
to_symbol = nil
|
291
290
|
|
292
291
|
table = TTY::Table.new(header: [
|
293
|
-
{ value:
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
292
|
+
{ value: "Coin", alignment: :left },
|
293
|
+
"Amount",
|
294
|
+
"Price",
|
295
|
+
"Total Price",
|
296
|
+
"Cur. Price",
|
297
|
+
"Total Cur. Price",
|
298
|
+
"Change",
|
299
|
+
"Change%"
|
301
300
|
])
|
302
301
|
|
303
|
-
config.fetch(
|
304
|
-
coin_data = raw_data[coin[
|
305
|
-
coin_display_data = display_data[coin[
|
306
|
-
past_price = coin[
|
307
|
-
curr_price = coin[
|
308
|
-
to_symbol = coin_display_data[
|
302
|
+
config.fetch("holdings").each do |coin|
|
303
|
+
coin_data = raw_data[coin["name"]][base]
|
304
|
+
coin_display_data = display_data[coin["name"]][base]
|
305
|
+
past_price = coin["amount"] * coin["price"]
|
306
|
+
curr_price = coin["amount"] * coin_data["PRICE"]
|
307
|
+
to_symbol = coin_display_data["TOSYMBOL"]
|
309
308
|
change = curr_price - past_price
|
310
309
|
arrow = pick_arrow(change)
|
311
310
|
total_buy += past_price
|
312
311
|
total += curr_price
|
313
312
|
|
314
313
|
coin_details = [
|
315
|
-
{ value: add_color(coin[
|
316
|
-
coin[
|
314
|
+
{ value: add_color(coin["name"], :yellow), alignment: :left },
|
315
|
+
coin["amount"],
|
317
316
|
"#{to_symbol} #{number_to_currency(round_to(coin['price']))}",
|
318
317
|
"#{to_symbol} #{number_to_currency(round_to(past_price))}",
|
319
318
|
add_color("#{to_symbol} #{number_to_currency(round_to(coin_data['PRICE']))}", pick_color(change)),
|
@@ -328,8 +327,8 @@ module Coinpare
|
|
328
327
|
arrow = pick_arrow(total_change)
|
329
328
|
|
330
329
|
table << [
|
331
|
-
{ value: add_color(
|
332
|
-
"#{to_symbol} #{number_to_currency(round_to(total_buy))}",
|
330
|
+
{ value: add_color("ALL", :cyan), alignment: :left }, "-", "-",
|
331
|
+
"#{to_symbol} #{number_to_currency(round_to(total_buy))}", "-",
|
333
332
|
add_color("#{to_symbol} #{number_to_currency(round_to(total))}", pick_color(total_change)),
|
334
333
|
add_color("#{arrow} #{to_symbol} #{number_to_currency(round_to(total_change))}", pick_color(total_change)),
|
335
334
|
add_color("#{arrow} #{round_to(percent_change(total_buy, total))}%", pick_color(total_change))
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require "pastel"
|
4
|
+
require "tty-pager"
|
5
|
+
require "tty-spinner"
|
6
|
+
require "tty-table"
|
7
|
+
require "timers"
|
8
8
|
|
9
|
-
require_relative
|
10
|
-
require_relative
|
9
|
+
require_relative "../command"
|
10
|
+
require_relative "../fetcher"
|
11
11
|
|
12
12
|
module Coinpare
|
13
13
|
module Commands
|
@@ -17,7 +17,7 @@ module Coinpare
|
|
17
17
|
@options = options
|
18
18
|
@pastel = Pastel.new
|
19
19
|
@timers = Timers::Group.new
|
20
|
-
@spinner = TTY::Spinner.new(
|
20
|
+
@spinner = TTY::Spinner.new(":spinner Fetching data...",
|
21
21
|
format: :dots, clear: true)
|
22
22
|
end
|
23
23
|
|
@@ -25,9 +25,9 @@ module Coinpare
|
|
25
25
|
pager = TTY::Pager.new(output: output)
|
26
26
|
@spinner.auto_spin
|
27
27
|
|
28
|
-
if @options[
|
28
|
+
if @options["watch"]
|
29
29
|
output.print cursor.hide
|
30
|
-
interval = @options[
|
30
|
+
interval = @options["watch"].to_f > 0 ? @options["watch"].to_f : DEFAULT_INTERVAL
|
31
31
|
@timers.now_and_every(interval) { display_markets(output, pager) }
|
32
32
|
loop { @timers.wait }
|
33
33
|
else
|
@@ -35,7 +35,7 @@ module Coinpare
|
|
35
35
|
end
|
36
36
|
ensure
|
37
37
|
@spinner.stop
|
38
|
-
if @options[
|
38
|
+
if @options["watch"]
|
39
39
|
@timers.cancel
|
40
40
|
output.print cursor.clear_screen_down
|
41
41
|
output.print cursor.show
|
@@ -45,7 +45,7 @@ module Coinpare
|
|
45
45
|
def display_markets(output, pager)
|
46
46
|
to_symbol = fetch_symbol
|
47
47
|
response = Fetcher.fetch_top_exchanges_by_pair(
|
48
|
-
@name.upcase, @options[
|
48
|
+
@name.upcase, @options["base"].upcase, @options)
|
49
49
|
return unless response
|
50
50
|
table = setup_table(response["Data"]["Exchanges"], to_symbol)
|
51
51
|
|
@@ -55,9 +55,9 @@ module Coinpare
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def clear_output(output, lines)
|
58
|
-
output.print cursor.clear_screen_down if @options[
|
58
|
+
output.print cursor.clear_screen_down if @options["watch"]
|
59
59
|
yield if block_given?
|
60
|
-
output.print cursor.up(lines) if @options[
|
60
|
+
output.print cursor.up(lines) if @options["watch"]
|
61
61
|
end
|
62
62
|
|
63
63
|
def print_results(table, output, pager)
|
@@ -73,10 +73,10 @@ module Coinpare
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def fetch_symbol
|
76
|
-
prices = Fetcher.fetch_prices(
|
77
|
-
@name.upcase, @options['base'].upcase, @options)
|
76
|
+
prices = Fetcher.fetch_prices(@name.upcase, @options["base"].upcase, @options)
|
78
77
|
return unless prices
|
79
|
-
|
78
|
+
|
79
|
+
prices["DISPLAY"][@name.upcase][@options["base"].upcase]["TOSYMBOL"]
|
80
80
|
end
|
81
81
|
|
82
82
|
def banner
|
@@ -87,20 +87,20 @@ module Coinpare
|
|
87
87
|
|
88
88
|
def setup_table(data, to_symbol)
|
89
89
|
table = TTY::Table.new(header: [
|
90
|
-
{ value:
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
90
|
+
{ value: "Market", alignment: :left },
|
91
|
+
"Price",
|
92
|
+
"Chg. 24H",
|
93
|
+
"Chg.% 24H",
|
94
|
+
"Open 24H",
|
95
|
+
"High 24H",
|
96
|
+
"Low 24H",
|
97
|
+
"Direct Vol. 24H"
|
98
98
|
])
|
99
99
|
|
100
100
|
data.each do |market|
|
101
|
-
change24h = market[
|
101
|
+
change24h = market["CHANGE24HOUR"]
|
102
102
|
market_details = [
|
103
|
-
{ value: add_color(market[
|
103
|
+
{ value: add_color(market["MARKET"], :yellow), alignment: :left },
|
104
104
|
add_color("#{to_symbol} #{number_to_currency(round_to(market['PRICE']))}", pick_color(change24h)),
|
105
105
|
add_color("#{pick_arrow(change24h)} #{to_symbol} #{number_to_currency(round_to(change24h))}", pick_color(change24h)),
|
106
106
|
add_color("#{pick_arrow(change24h)} #{round_to(market['CHANGEPCT24HOUR'] * 100)}%", pick_color(change24h)),
|