class Coinpare::Commands::Holdings
Constants
- DEFAULT_PIE_RADIUS
Public Class Methods
new(options)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 20 def initialize(options) @options = options @pastel = Pastel.new @timers = Timers::Group.new @spinner = TTY::Spinner.new(":spinner Fetching data...", format: :dots, clear: true) @interval = @options.fetch("watch", DEFAULT_INTERVAL).to_f config.set("settings", "color", value: !@options["no-color"]) end
Public Instance Methods
add_coin(input, output)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 171 def add_coin(input, output) prompt = create_prompt(input, output) context = self data = prompt.collect(&context.ask_coin) output.print cursor.up(3) output.print cursor.clear_screen_down data end
ask_coin()
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 150 def ask_coin -> (prompt) do key("name").ask("What coin do you own?") do |q| q.default "BTC" q.required(true, "You need to provide a coin") q.validate(/\w{2,}/, "Currency can only be chars.") q.convert ->(coin) { coin.upcase } end key("amount").ask("What amount?") do |q| q.required(true, "You need to provide an amount") q.validate(/[\d.]+/, "Invalid amount provided") q.convert ->(am) { am.to_f } end key("price").ask("At what price per coin?") do |q| q.required(true, "You need to provide a price") q.validate(/[\d.]+/, "Invalid prince provided") q.convert ->(p) { p.to_f } end end end
clear_output(output, lines) { || ... }
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 130 def clear_output(output, lines) output.print cursor.clear_screen_down if @options["watch"] yield if block_given? output.print cursor.up(lines) if @options["watch"] end
create_pie_charts(raw_data, display_data)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 226 def create_pie_charts(raw_data, display_data) colors = %i[yellow blue green cyan magenta red] radius = @options["pie"].to_i > 0 ? @options["pie"].to_i : DEFAULT_PIE_RADIUS base = @options.fetch("base", config.fetch("settings", "base")).upcase to_symbol = nil past_data = [] curr_data = [] config.fetch("holdings").each do |coin| coin_data = raw_data[coin["name"]][base] to_symbol = display_data[coin["name"]][base]["TOSYMBOL"] past_price = coin["amount"] * coin["price"] curr_price = coin["amount"] * coin_data["PRICE"] past_data << { name: coin["name"], value: past_price } curr_data << { name: coin["name"], value: curr_price } end options = { colors: !@options["no-color"] && colors, radius: radius, legend: { left: 2, format: "%<label>s %<name>s #{to_symbol} %<currency>s (%<percent>.0f%%)", precision: 2 } } [ TTY::Pie.new(**options.merge(data: past_data)), TTY::Pie.new(**options.merge(data: curr_data)), to_symbol ] end
create_prompt(input, output)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 136 def create_prompt(input, output) prompt = TTY::Prompt.new( prefix: "[#{add_color('c', :yellow)}] ", input: input, output: output, interrupt: -> { puts; exit 1 }, enable_color: !@options["no-color"] ) prompt.on(:keypress) { |event| prompt.trigger(:keydown) if event.value == "j" prompt.trigger(:keyup) if event.value == "k" } prompt end
display_coins(output, names, overridden_settings)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 106 def display_coins(output, names, overridden_settings) response = Fetcher.fetch_prices(names.join(","), overridden_settings["base"].upcase, overridden_settings) return unless response table = if @options["pie"] setup_table_with_pies(response["RAW"], response["DISPLAY"]) else setup_table(response["RAW"], response["DISPLAY"]) end @spinner.stop lines = banner(overridden_settings).lines.size + 1 + (table.rows_size + 3) clear_output(output, lines) do output.puts banner(overridden_settings) if @options["pie"] output.puts table.render(:unicode, padding: [0, 2]) else output.puts table.render(:unicode, padding: [0, 1], alignment: :right) end end end
execute(input: $stdin, output: $stdout)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 30 def execute(input: $stdin, output: $stdout) config_saved = config.exist? if config_saved && @options["edit"] editor.open(config.source_file) return elsif @options["edit"] output.puts "Sorry, no holdings configuration found." output.print "Run \"" output.print "$ #{add_color('coinpare holdings', :yellow)}\" " output.puts "to setup new altfolio." return end config.read if config_saved holdings = config.fetch("holdings") if holdings.nil? || (holdings && holdings.empty?) info = setup_portfolio(input, output) config.merge(info) elsif @options["add"] coin_info = add_coin(input, output) config.append(coin_info, to: ["holdings"]) elsif @options["remove"] coin_info = remove_coin(input, output) config.remove(*coin_info, from: ["holdings"]) elsif @options["clear"] prompt = create_prompt(input, output) answer = prompt.yes?("Do you want to remove all holdings?") if answer config.delete("holdings") output.puts add_color("All holdings removed", :red) end end holdings = config.fetch("holdings") no_holdings_left = holdings.nil? || (holdings && holdings.empty?) if no_holdings_left config.delete("holdings") end # Persist current configuration home_file = ::File.join(Dir.home, "#{config.filename}#{config.extname}") file = config.source_file config.write(file.nil? ? home_file : file, force: true) if no_holdings_left output.puts add_color("Please add holdings to your altfolio!", :green) exit end @spinner.auto_spin settings = config.fetch("settings") # command options take precedence over config settings overridden_settings = {} overridden_settings["exchange"] = @options.fetch("exchange", settings.fetch("exchange")) overridden_settings["base"] = @options.fetch("base", settings.fetch("base")) holdings = config.fetch("holdings") { [] } names = holdings.map { |c| c["name"] } if @options["watch"] output.print cursor.hide @timers.now_and_every(@interval) do display_coins(output, names, overridden_settings) end loop { @timers.wait } else display_coins(output, names, overridden_settings) end ensure @spinner.stop if @options["watch"] @timers.cancel output.print cursor.clear_screen_down output.print cursor.show end end
remove_coin(input, output)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 180 def remove_coin(input, output) prompt = create_prompt(input, output) holdings = config.fetch("holdings") data = prompt.multi_select("Which hodlings to remove?") do |menu| holdings.each do |holding| menu.choice "#{holding['name']} (#{holding['amount']})", holding end end output.print cursor.up(1) output.print cursor.clear_line data end
setup_portfolio(input, output)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 193 def setup_portfolio(input, output) output.print "\nCurrently you have no investments setup...\n" \ "Let's change that and create your altfolio!\n\n" prompt = create_prompt(input, output) context = self data = prompt.collect do key("settings") do key("base").ask("What base currency to convert holdings to?") do |q| q.default "USD" q.convert ->(b) { b.upcase } q.validate(/\w{3}/, "Currency code needs to be 3 chars long") end key("exchange").ask("What exchange would you like to use?") do |q| q.default "CCCAGG" q.required true end end while prompt.yes?("Do you want to add coin to your altfolio?") key("holdings").values(&context.ask_coin) end end lines = 4 + # intro 2 + # base + exchange data["holdings"].size * 4 + 1 output.print cursor.up(lines) output.print cursor.clear_screen_down data end
setup_table(raw_data, display_data)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 285 def setup_table(raw_data, display_data) base = @options.fetch("base", config.fetch("settings", "base")).upcase total_buy = 0 total = 0 to_symbol = nil table = TTY::Table.new(header: [ { value: "Coin", alignment: :left }, "Amount", "Price", "Total Price", "Cur. Price", "Total Cur. Price", "Change", "Change%" ]) config.fetch("holdings").each do |coin| coin_data = raw_data[coin["name"]][base] coin_display_data = display_data[coin["name"]][base] past_price = coin["amount"] * coin["price"] curr_price = coin["amount"] * coin_data["PRICE"] to_symbol = coin_display_data["TOSYMBOL"] change = curr_price - past_price arrow = pick_arrow(change) total_buy += past_price total += curr_price coin_details = [ { value: add_color(coin["name"], :yellow), alignment: :left }, coin["amount"], "#{to_symbol} #{number_to_currency(round_to(coin['price']))}", "#{to_symbol} #{number_to_currency(round_to(past_price))}", add_color("#{to_symbol} #{number_to_currency(round_to(coin_data['PRICE']))}", pick_color(change)), add_color("#{to_symbol} #{number_to_currency(round_to(curr_price))}", pick_color(change)), add_color("#{arrow} #{to_symbol} #{number_to_currency(round_to(change))}", pick_color(change)), add_color("#{arrow} #{round_to(percent_change(past_price, curr_price))}%", pick_color(change)) ] table << coin_details end total_change = total - total_buy arrow = pick_arrow(total_change) table << [ { value: add_color("ALL", :cyan), alignment: :left }, "-", "-", "#{to_symbol} #{number_to_currency(round_to(total_buy))}", "-", add_color("#{to_symbol} #{number_to_currency(round_to(total))}", pick_color(total_change)), add_color("#{arrow} #{to_symbol} #{number_to_currency(round_to(total_change))}", pick_color(total_change)), add_color("#{arrow} #{round_to(percent_change(total_buy, total))}%", pick_color(total_change)) ] table end
setup_table_with_pies(raw_data, display_data)
click to toggle source
# File lib/coinpare/commands/holdings.rb, line 261 def setup_table_with_pies(raw_data, display_data) past_pie, curr_pie, to_symbol = *create_pie_charts(raw_data, display_data) total_change = curr_pie.total - past_pie.total arrow = pick_arrow(total_change) header_past = "Total Price (#{to_symbol} #{number_to_currency(round_to(past_pie.total))})" header_curr = [ "Total Current Price (#{to_symbol} #{number_to_currency(round_to(curr_pie.total))}) ", add_color("#{arrow} #{to_symbol} #{number_to_currency(round_to(total_change))}", pick_color(total_change)), ].join table = TTY::Table.new( header: [ { value: header_past, alignment: :center }, { value: header_curr, alignment: :center } ] ) past_pie.to_s.split("\n").zip(curr_pie.to_s.split("\n")).each do |past_part, curr_part| table << [past_part, curr_part] end table end