From 07140bb7026eee3a32fb6f24ad3854d35f0089af Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 9 Feb 2022 15:49:36 +0100 Subject: [PATCH 01/26] create dev branch --- .gitignore | 8 ++-- .rspec_status | 12 ----- bin/ctfc | 3 +- lib/ctfc/api.rb | 18 +++++++ lib/ctfc/api/cryptocompare.rb | 45 ++++++++++++++++++ lib/ctfc/{base.rb => client.rb} | 84 +++++++++------------------------ lib/ctfc/config.rb | 18 ------- lib/ctfc/request.rb | 43 +++++++++++++++++ 8 files changed, 133 insertions(+), 98 deletions(-) delete mode 100644 .rspec_status create mode 100644 lib/ctfc/api.rb create mode 100644 lib/ctfc/api/cryptocompare.rb rename lib/ctfc/{base.rb => client.rb} (61%) delete mode 100644 lib/ctfc/config.rb create mode 100644 lib/ctfc/request.rb diff --git a/.gitignore b/.gitignore index 20312e8..fcffe9a 100644 --- a/.gitignore +++ b/.gitignore @@ -45,9 +45,9 @@ build-iPhoneSimulator/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# Gemfile.lock -# .ruby-version -# .ruby-gemset +Gemfile.lock +.ruby-version +.ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc @@ -55,6 +55,4 @@ build-iPhoneSimulator/ # Used by RuboCop. Remote config files pulled in from inherit_from directive. # .rubocop-https?--* -# Ruby Gemfile.lock -*.lock diff --git a/.rspec_status b/.rspec_status deleted file mode 100644 index 661d71d..0000000 --- a/.rspec_status +++ /dev/null @@ -1,12 +0,0 @@ -example_id | status | run_time | ----------------------------- | ------ | --------------- | -./spec/config_spec.rb[1:1:1] | passed | 0.00112 seconds | -./spec/config_spec.rb[1:1:2] | passed | 0.00012 seconds | -./spec/config_spec.rb[1:1:3] | passed | 0.00009 seconds | -./spec/data_spec.rb[1:1:1] | passed | 0.00115 seconds | -./spec/data_spec.rb[1:1:2] | passed | 0.00012 seconds | -./spec/data_spec.rb[1:1:3] | passed | 0.00009 seconds | -./spec/data_spec.rb[1:1:4] | passed | 0.00009 seconds | -./spec/data_spec.rb[2:1:1] | passed | 0.2051 seconds | -./spec/data_spec.rb[2:1:2] | passed | 0.00039 seconds | -./spec/data_spec.rb[2:1:3] | passed | 0.0003 seconds | diff --git a/bin/ctfc b/bin/ctfc index 39ac318..4eef8c4 100755 --- a/bin/ctfc +++ b/bin/ctfc @@ -38,6 +38,7 @@ else opts[:loop].times do ARGV.each do |fiat| next if opts.include?(fiat.downcase) + # in Ruby 3.1: @crypto.get(fiat, save:, print:, coins:) @crypto.get(fiat, save: save, @@ -52,7 +53,7 @@ else # pause between loops sleep opts[:wait] - # clear screan - depending on OS + # clear screen - depending on OS system 'clear' or system 'cls' end end diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb new file mode 100644 index 0000000..dbc501a --- /dev/null +++ b/lib/ctfc/api.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rest-client' +require 'json' + +require_relative 'api/cryptocompare' + +module CTFC + module API + def self.list + sources = [] + Dir.entries("#{File.expand_path(__FILE__)}"/api).select do |file| + sources << file.gsub('.rb', '').to_sym unless %w[. ..].include? file + end + sources + end + end +end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb new file mode 100644 index 0000000..4d9adc0 --- /dev/null +++ b/lib/ctfc/api/cryptocompare.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module API + class Cryptocompare + BASE_URL = 'https://min-api.cryptocompare.com/data/pricemultifull?' + MAX_RETRY = 3 + + def initialize(fiat, coins) + @response = { counter: 0 } + call_cryptocompare fiat, coins + end + + private + + def call_cryptocompare(fiat, coins) + price_hash, coin_uri = {}, '' + coins.collect do |coin| + coin_uri += "fsyms=#{coin}&" + end + request = RestClient.get(BASE_URL + "#{coin_uri}tsyms=#{fiat}") + data = JSON.parse request + process_json_data fiat, coins, data + end + + def process_json_data(fiat, coins, data) + @response[:data] = data + @response[:data_row] = [Time.now.to_s] + coins.each do |coin| + value = data['RAW'][coin.upcase][fiat.name.upcase]['PRICE'].round(2) + price_hash[coin] = value + @response[:data_row] << value + end + @response[:prices] = price_hash + @response + rescue StandardError + if (@response[:counter] += 1) > MAX_RETRY + puts @response[:data].to_s.split(',') + false + else + process_json_data fiat, coins, url + end + end + + end +end diff --git a/lib/ctfc/base.rb b/lib/ctfc/client.rb similarity index 61% rename from lib/ctfc/base.rb rename to lib/ctfc/client.rb index 97c51da..c8e3d3b 100644 --- a/lib/ctfc/base.rb +++ b/lib/ctfc/client.rb @@ -9,31 +9,26 @@ require 'rest-client' ## -# Module **CTFC** keep everything together. **CTFC::CONFIG** module for default setup, -# and **CTFC::Data** class for actual request execution. For instance methods look -# at **CTFC::Data**, for class methods look at **Ctfc**. -# -# @see CTFC::Data +# @see CTFC::Request +# @see CTFC::API # @see Ctfc # module CTFC ## # Data class keep all the logic to send request, receive response, - # and everything between. Class Ctfc extend CTFC::Data, for easier work. + # and everything between. Class Ctfc extend CTFC::Client, for easier work. # - # @note Instead of using CTFC::Data.new, you can also call Ctfc.new + # @note Instead of using CTFC::Client.new, you can also call Ctfc.new # - class Data - include CONFIG - - attr_reader :response, :data, :url, :table, :count, :prices + class Client + attr_reader :response, :data, :url, :table, :prices attr_accessor :fiat, :coins alias currency fiat ## # @example Initialization example - # @data = CTFC::Data.new :eur, save: true + # @data = CTFC::Client.new :eur, save: true # # @param [Symbol] currency **Optional**. Define fiat currency. # @param [Hash] opts **Optional**. Additional options hash. @@ -42,13 +37,14 @@ class Data # @option opts [Boolean] save **Optional**. Save `.csv` output. # @option opts [Array] coins **Optional**. Define coins to scrap. # - # @return [Data] Data object to work with + # @return [Client] Client instance # - def initialize(currency = :eur, opts = {}) - @fiat = currency.to_s.upcase - @save = opts[:save].nil? ? true : opts[:save] - @print = opts[:print].nil? ? true : opts[:print] - @coins = opts[:coins].nil? ? COINS : Array(opts[:coins]) + def initialize(curr = :eur, opts = {}) + @fiat = curr.name.upcase + @save = opts[:save].nil? ? true : opts[:save] + @print = opts[:print].nil? ? true : opts[:print] + @coins = opts[:coins].nil? ? COINS : Array(opts[:coins]) + @source = opts[:source].nil? ? :cryptocompare : opts[:source] end ## @@ -65,14 +61,14 @@ def initialize(currency = :eur, opts = {}) # # @return [Hash || false] Hash of coins and fiat values, or false if all requests fail # - def get(currency = nil, opts = {}) - @fiat = currency.to_s.upcase unless currency.nil? - @coins = opts[:coins] unless opts[:coins].nil? - @save = opts[:save] unless opts[:save].nil? - @print = opts[:print] unless opts[:print].nil? - @count = 0 - @table = "ctfc_#{@fiat}.csv".downcase - do_rest_request + def get(curr = nil, opts = {}) + @fiat = curr.to_s.upcase unless curr.nil? + @coins = opts[:coins] unless opts[:coins].nil? + @save = opts[:save] unless opts[:save].nil? + @print = opts[:print] unless opts[:print].nil? + @source = opts[:source] unless opts[:source].nil? + request = Request.new @fiat, @coins, @source + @response = request.response end ## @@ -138,42 +134,6 @@ def success? private - def do_rest_request - prepare_uri - process_data - @prices - rescue StandardError - if (@count += 1) >= MAX_RETRY - puts @response.to_s.split(',') - false - else - do_rest_request - end - end - - def process_data - @response = RestClient.get @url - @data = JSON.parse @response - - @data_array << Time.now.to_s - @coins.each do |coin| - value = @data['RAW'][coin.to_s.upcase][@fiat.to_s.upcase]['PRICE'].round(2) - @prices[coin] = value - @data_array << value - end - - print_fiat_values - save_csv_data - end - - def prepare_uri - @prices = {} - @data_array = [] - coin_uri = String.new '' - @coins.collect { |coin| coin_uri << "fsyms=#{coin}&" } - @url = URL + "#{coin_uri}tsyms=#{@fiat}" - end - def print_fiat_values return unless print? diff --git a/lib/ctfc/config.rb b/lib/ctfc/config.rb deleted file mode 100644 index e68188d..0000000 --- a/lib/ctfc/config.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module CTFC - ## - # Keep default configuration data, like coins to scrap, max number - # of retries and cryptocompare API url. - # - module CONFIG - # default coins to use - COINS = %w[BTC LTC XMR ETH BCH ZEC].freeze - - # max number of retries if request fail - MAX_RETRY = 3 - - # Cryptocompare API - base url for requests - URL = 'https://min-api.cryptocompare.com/data/pricemultifull?' - end -end diff --git a/lib/ctfc/request.rb b/lib/ctfc/request.rb new file mode 100644 index 0000000..9a0a064 --- /dev/null +++ b/lib/ctfc/request.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative 'api' + +module CTFC + class Request + attr_reader :response + + def initialize(fiat: :eur, coins: [], source: :cryptocompare) + unless fiat.is_a?(Symbol) && source.is_a?(Symbol) && coins.is_a?(Array) + raise TypeError, 'Use symbols, and array of strings for coins' + end + @response = { fiat: fiat, coins: coins, source: source, prices: {} } + process_source source + end + + def process_source(source = response[:source]) + case source + when :cryptocompare + Cryptocompare.get response[:fiat], response[:coins] + when :binance + raise NoMethodError, 'Working on Binance implementation' + else + raise NoMethodError, 'Not implemented, yet! Feel free to contribute!' + end + end + + private + + + def save_csv_data + return unless save? + + create_csv_headers unless File.exist?(@table) + CSV.open(@table, 'ab') { |column| column << @data_array } + end + + def create_csv_headers + header_array = ['TIME'] + @coins.each { |coin| header_array << coin } + CSV.open(@table, 'w') { |header| header << header_array } + end + From 21c003e2f1559b299b2d2b14715a9667bb63aac4 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 9 Feb 2022 22:05:26 +0100 Subject: [PATCH 02/26] create api template class with attr_steroids --- lib/ctfc/api.rb | 19 +++++++++++---- lib/ctfc/api/apitemplate.rb | 45 +++++++++++++++++++++++++++++++++++ lib/ctfc/api/cryptocompare.rb | 21 +++++----------- lib/ctfc/client.rb | 3 +-- lib/ctfc/request.rb | 3 ++- 5 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 lib/ctfc/api/apitemplate.rb diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index dbc501a..ab5d367 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -7,12 +7,21 @@ module CTFC module API - def self.list - sources = [] - Dir.entries("#{File.expand_path(__FILE__)}"/api).select do |file| - sources << file.gsub('.rb', '').to_sym unless %w[. ..].include? file + class << self + ## + # List available sources by *:symbolizing* filenames without + # **.rb** extension in **API** directory. + # + # @return [Array] Array of symbols as available sources + # + def list + sources, skip = [], %w[. ..] + # use select instead of map to avoid nil in array + Dir.entries("#{File.expand_path(__FILE__)}/api").select do |source| + sources << source.gsub('.rb', '').to_sym unless skip.include? source + end + sources end - sources end end end diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb new file mode 100644 index 0000000..340d014 --- /dev/null +++ b/lib/ctfc/api/apitemplate.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module CTFC + module API + class ApiTemplate + include Steroids + + MAX_RETRY = 3 + BASE_URL = { + cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?' + }.freeze + + at_reader :response, as:{counter: 0, prices: {}, + uri: BASE_URL[self.class.name.downcase.to_sym]} + + def initialize(fiat, coins) + process fiat, coins + end + + private + + def process(*) + end + end + end + + ## + # @example Allow attr_reader with default Hash options + # + # attribute_reader :client, as: {username: 'alx'} + # + module Steroids + def attribute_reader(*args, **opts) + args.count.times do + name = args.shift.to_sym + if opts.key? :as + define_method(name) do + instance_variable_set "@#{name}", opts[:as] + end + end + end + end + alias at_reader attribute_reader + end +end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index 4d9adc0..1ae4d6d 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -1,23 +1,15 @@ # frozen_string_literal: true module API - class Cryptocompare - BASE_URL = 'https://min-api.cryptocompare.com/data/pricemultifull?' - MAX_RETRY = 3 - - def initialize(fiat, coins) - @response = { counter: 0 } - call_cryptocompare fiat, coins - end + class Cryptocompare < ApiTemplate private - def call_cryptocompare(fiat, coins) - price_hash, coin_uri = {}, '' + def process(fiat, coins) coins.collect do |coin| - coin_uri += "fsyms=#{coin}&" + @response[:uri] += "fsyms=#{coin}&" end - request = RestClient.get(BASE_URL + "#{coin_uri}tsyms=#{fiat}") + request = RestClient.get @response[:uri] data = JSON.parse request process_json_data fiat, coins, data end @@ -27,17 +19,16 @@ def process_json_data(fiat, coins, data) @response[:data_row] = [Time.now.to_s] coins.each do |coin| value = data['RAW'][coin.upcase][fiat.name.upcase]['PRICE'].round(2) - price_hash[coin] = value + @response[:prices][coin] = value @response[:data_row] << value end - @response[:prices] = price_hash @response rescue StandardError if (@response[:counter] += 1) > MAX_RETRY puts @response[:data].to_s.split(',') false else - process_json_data fiat, coins, url + process_json_data fiat, coins, data end end diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index c8e3d3b..2275844 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -10,7 +10,6 @@ ## # @see CTFC::Request -# @see CTFC::API # @see Ctfc # module CTFC @@ -21,7 +20,7 @@ module CTFC # @note Instead of using CTFC::Client.new, you can also call Ctfc.new # class Client - attr_reader :response, :data, :url, :table, :prices + attr_reader :response, :data, :prices attr_accessor :fiat, :coins alias currency fiat diff --git a/lib/ctfc/request.rb b/lib/ctfc/request.rb index 9a0a064..98a3b00 100644 --- a/lib/ctfc/request.rb +++ b/lib/ctfc/request.rb @@ -4,6 +4,7 @@ module CTFC class Request + include API attr_reader :response def initialize(fiat: :eur, coins: [], source: :cryptocompare) @@ -17,7 +18,7 @@ def initialize(fiat: :eur, coins: [], source: :cryptocompare) def process_source(source = response[:source]) case source when :cryptocompare - Cryptocompare.get response[:fiat], response[:coins] + Cryptocompare.new response[:fiat], response[:coins] when :binance raise NoMethodError, 'Working on Binance implementation' else From 8ddef25ce779d6326d928c58eb0b4f49634757bb Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 9 Feb 2022 23:06:22 +0100 Subject: [PATCH 03/26] add module_function :attribute_reader --- lib/ctfc/api/apitemplate.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 340d014..cf8aa55 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -7,6 +7,8 @@ class ApiTemplate MAX_RETRY = 3 BASE_URL = { + binance: '' + kraken: '' cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?' }.freeze @@ -41,5 +43,6 @@ def attribute_reader(*args, **opts) end end alias at_reader attribute_reader + module_function :attribute_reader end end From d3c9b78f59c04c6b315643d3b7146e7c804f69fa Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 9 Feb 2022 23:54:14 +0100 Subject: [PATCH 04/26] working on attribute_reader --- lib/ctfc/api/apitemplate.rb | 11 ++++------- lib/ctfc/api/cryptocompare.rb | 4 ++++ lib/ctfc/request.rb | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index cf8aa55..025083b 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -3,7 +3,7 @@ module CTFC module API class ApiTemplate - include Steroids + extend Steroids MAX_RETRY = 3 BASE_URL = { @@ -12,16 +12,14 @@ class ApiTemplate cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?' }.freeze - at_reader :response, as:{counter: 0, prices: {}, - uri: BASE_URL[self.class.name.downcase.to_sym]} - - def initialize(fiat, coins) + def initialize(fiat, coins, source) + at_reader :response, as:{counter: 0, prices: {}, uri: BASE_URL[source]} process fiat, coins end private - def process(*) + def process(fiat, coins) end end end @@ -43,6 +41,5 @@ def attribute_reader(*args, **opts) end end alias at_reader attribute_reader - module_function :attribute_reader end end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index 1ae4d6d..fca3b9a 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -3,6 +3,10 @@ module API class Cryptocompare < ApiTemplate + def initialize(fiat, coins, source) + super fiat, coins, source + end + private def process(fiat, coins) diff --git a/lib/ctfc/request.rb b/lib/ctfc/request.rb index 98a3b00..66b737d 100644 --- a/lib/ctfc/request.rb +++ b/lib/ctfc/request.rb @@ -18,7 +18,7 @@ def initialize(fiat: :eur, coins: [], source: :cryptocompare) def process_source(source = response[:source]) case source when :cryptocompare - Cryptocompare.new response[:fiat], response[:coins] + Cryptocompare.new response[:fiat], response[:coins], source when :binance raise NoMethodError, 'Working on Binance implementation' else From a5b0e4dfbfffa2a441a6b4048a2c3c0c073fef9e Mon Sep 17 00:00:00 2001 From: alx3dev Date: Thu, 10 Feb 2022 22:42:23 +0100 Subject: [PATCH 05/26] remove steroids_attr --- lib/ctfc/api.rb | 21 ++++++++------- lib/ctfc/api/apitemplate.rb | 51 +++++++++++++++++------------------ lib/ctfc/api/cryptocompare.rb | 24 ++++++++++------- lib/ctfc/client.rb | 6 ++--- lib/ctfc/request.rb | 43 +++++++++++------------------ 5 files changed, 68 insertions(+), 77 deletions(-) diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index ab5d367..ea10564 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -3,24 +3,25 @@ require 'rest-client' require 'json' -require_relative 'api/cryptocompare' +# automatically require new apis +CTFC::API.list.select { |x| require_relative x } module CTFC module API class << self - ## - # List available sources by *:symbolizing* filenames without - # **.rb** extension in **API** directory. - # - # @return [Array] Array of symbols as available sources - # + def list - sources, skip = [], %w[. ..] - # use select instead of map to avoid nil in array + @list || get_sources_from_files_in_api_dir + end + + private + + def get_sources_from_files_in_api_dir + sources, skip = [], %w[. .. apitemplate.rb] Dir.entries("#{File.expand_path(__FILE__)}/api").select do |source| sources << source.gsub('.rb', '').to_sym unless skip.include? source end - sources + @list = sources end end end diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 025083b..6505d79 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -3,43 +3,42 @@ module CTFC module API class ApiTemplate - extend Steroids - MAX_RETRY = 3 BASE_URL = { - binance: '' - kraken: '' - cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?' - }.freeze + cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?', + kraken: '', + binance: '' }.freeze + + attr_reader :response - def initialize(fiat, coins, source) - at_reader :response, as:{counter: 0, prices: {}, uri: BASE_URL[source]} + def initialize(fiat, coins) + @response = { fiat: fiat, coins: coins, uri: BASE_URL[source] } process fiat, coins end + def go + process + end + private - def process(fiat, coins) + def process(fiat = response[:fiat], coins = response[:coins]) + return false unless fiat && coins end - end - end - ## - # @example Allow attr_reader with default Hash options - # - # attribute_reader :client, as: {username: 'alx'} - # - module Steroids - def attribute_reader(*args, **opts) - args.count.times do - name = args.shift.to_sym - if opts.key? :as - define_method(name) do - instance_variable_set "@#{name}", opts[:as] - end - end + def save_csv_data(table, data_row, coins) + return unless save? + + create_csv_headers(table, coins) unless File.exist?(table) + CSV.open(table, 'ab') { |column| column << data_row } end + + def create_csv_headers(table, coins) + header_array = ['TIME'] + coins.each { |coin| header_array << coin } + CSV.open(table, 'w') { |header| header << header_array } + end + end - alias at_reader attribute_reader end end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index fca3b9a..e6fcb77 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -3,33 +3,37 @@ module API class Cryptocompare < ApiTemplate - def initialize(fiat, coins, source) - super fiat, coins, source + def self.[](fiat, coins) + result = new fiat, coins + result.response + end + + def initialize(fiat, coins) + super fiat, coins end private - def process(fiat, coins) + def process(fiat = response[:fiat], coins = response[:coins]) coins.collect do |coin| @response[:uri] += "fsyms=#{coin}&" end - request = RestClient.get @response[:uri] + request = RestClient.get response[:uri] data = JSON.parse request process_json_data fiat, coins, data end def process_json_data(fiat, coins, data) - @response[:data] = data - @response[:data_row] = [Time.now.to_s] + prices, data_row = {}, [Time.now.to_s] coins.each do |coin| value = data['RAW'][coin.upcase][fiat.name.upcase]['PRICE'].round(2) - @response[:prices][coin] = value - @response[:data_row] << value + prices[coin] = value + data_row << value end - @response + @response = { data: data, data_row: data_row, prices: prices } rescue StandardError if (@response[:counter] += 1) > MAX_RETRY - puts @response[:data].to_s.split(',') + puts response[:data].to_s.split(',') false else process_json_data fiat, coins, data diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index 2275844..ae3de68 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -20,7 +20,7 @@ module CTFC # @note Instead of using CTFC::Client.new, you can also call Ctfc.new # class Client - attr_reader :response, :data, :prices + attr_reader :response, :prices, :source attr_accessor :fiat, :coins alias currency fiat @@ -66,8 +66,8 @@ def get(curr = nil, opts = {}) @save = opts[:save] unless opts[:save].nil? @print = opts[:print] unless opts[:print].nil? @source = opts[:source] unless opts[:source].nil? - request = Request.new @fiat, @coins, @source - @response = request.response + @response = Request[@fiat, @coins, @source] + @prices = @response.prices end ## diff --git a/lib/ctfc/request.rb b/lib/ctfc/request.rb index 66b737d..a8eff97 100644 --- a/lib/ctfc/request.rb +++ b/lib/ctfc/request.rb @@ -5,40 +5,27 @@ module CTFC class Request include API - attr_reader :response - def initialize(fiat: :eur, coins: [], source: :cryptocompare) + def self.[fiat = :eur, coins = [], source = :cryptocompare] unless fiat.is_a?(Symbol) && source.is_a?(Symbol) && coins.is_a?(Array) raise TypeError, 'Use symbols, and array of strings for coins' end - @response = { fiat: fiat, coins: coins, source: source, prices: {} } - process_source source - end - - def process_source(source = response[:source]) - case source - when :cryptocompare - Cryptocompare.new response[:fiat], response[:coins], source - when :binance - raise NoMethodError, 'Working on Binance implementation' - else - raise NoMethodError, 'Not implemented, yet! Feel free to contribute!' - end + process_source fiat, coins, source end private - - def save_csv_data - return unless save? - - create_csv_headers unless File.exist?(@table) - CSV.open(@table, 'ab') { |column| column << @data_array } - end - - def create_csv_headers - header_array = ['TIME'] - @coins.each { |coin| header_array << coin } - CSV.open(@table, 'w') { |header| header << header_array } + def process_source(fiat, coins, source) + @response = + case source + when :cryptocompare + Cryptocompare[fiat, coins] + when :binance + # Binance[fiat, coins] + raise NoMethodError, 'Working on Binance implementation' + else + raise NoMethodError, 'Not implemented, yet! Feel free to contribute!' + end end - + end +end From b3de419e3db1d018c520cfb24ef1e32814c62177 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Thu, 10 Feb 2022 23:31:05 +0100 Subject: [PATCH 06/26] remove request class --- lib/ctfc/api/apitemplate.rb | 9 +++-- lib/ctfc/api/cryptocompare.rb | 10 +++--- lib/ctfc/client.rb | 66 +++++++++-------------------------- lib/ctfc/request.rb | 31 ---------------- 4 files changed, 25 insertions(+), 91 deletions(-) delete mode 100644 lib/ctfc/request.rb diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 6505d79..409836f 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -3,6 +3,8 @@ module CTFC module API class ApiTemplate + class << self + MAX_RETRY = 3 BASE_URL = { cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?', @@ -11,15 +13,11 @@ class ApiTemplate attr_reader :response - def initialize(fiat, coins) + def prepare_response_hash(fiat, coins, source) @response = { fiat: fiat, coins: coins, uri: BASE_URL[source] } process fiat, coins end - def go - process - end - private def process(fiat = response[:fiat], coins = response[:coins]) @@ -39,6 +37,7 @@ def create_csv_headers(table, coins) CSV.open(table, 'w') { |header| header << header_array } end + end end end end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index e6fcb77..f5faa71 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -2,16 +2,13 @@ module API class Cryptocompare < ApiTemplate + class << self - def self.[](fiat, coins) - result = new fiat, coins + def [](fiat, coins) + result = prepare_response_hash fiat, coins, :cryptocompare result.response end - def initialize(fiat, coins) - super fiat, coins - end - private def process(fiat = response[:fiat], coins = response[:coins]) @@ -40,5 +37,6 @@ def process_json_data(fiat, coins, data) end end + end end end diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index ae3de68..2339dbb 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -20,8 +20,8 @@ module CTFC # @note Instead of using CTFC::Client.new, you can also call Ctfc.new # class Client - attr_reader :response, :prices, :source - attr_accessor :fiat, :coins + attr_reader :response + attr_accessor :fiat, :coins, :prices, :source alias currency fiat @@ -46,28 +46,9 @@ def initialize(curr = :eur, opts = {}) @source = opts[:source].nil? ? :cryptocompare : opts[:source] end - ## - # @example Get fiat prices for initialized config - # - # @data.get - # - # @example Get prices and change initialized config "on-the-fly" - # - # @data.get :usd, save: false, coins: %w[BTC XMR ETH] - # - # @param [Symbol || String] currency **Optional**. Change fiat currency and execute request. - # @param [Hash] opts **Optional**. Options hash to change config 'on-the-fly' - see #initialize. - # - # @return [Hash || false] Hash of coins and fiat values, or false if all requests fail - # - def get(curr = nil, opts = {}) - @fiat = curr.to_s.upcase unless curr.nil? - @coins = opts[:coins] unless opts[:coins].nil? - @save = opts[:save] unless opts[:save].nil? - @print = opts[:print] unless opts[:print].nil? - @source = opts[:source] unless opts[:source].nil? - @response = Request[@fiat, @coins, @source] - @prices = @response.prices + def get + call_api_request + prices = response[:prices] end ## @@ -81,7 +62,7 @@ def get(curr = nil, opts = {}) # @return [Float] # def price(coin) - @prices[coin.to_s.upcase] + prices[coin.to_s.upcase] end ## @@ -133,31 +114,18 @@ def success? private - def print_fiat_values - return unless print? - - 30.times { print '='.cyan } - puts '' - puts "#{'['.cyan.bold}#{@fiat.to_s.upcase.yellow.bold}#{']'.cyan.bold} conversion rate" - 30.times { print '='.cyan } - puts '' - @prices.each do |name, value| - print '['.yellow.bold + name.to_s.cyan.bold + ']'.yellow.bold - puts ": #{value}".bold - end + def call_api_request + @response = + case @source + when :cryptocompare + Cryptocompare[@fiat, @coins] + when :binance + # Binance[fiat, coins] + raise NoMethodError, 'Working on Binance implementation' + else + raise NoMethodError, 'Not implemented, yet! Feel free to contribute!' + end end - def save_csv_data - return unless save? - - create_csv_headers unless File.exist?(@table) - CSV.open(@table, 'ab') { |column| column << @data_array } - end - - def create_csv_headers - header_array = ['TIME'] - @coins.each { |coin| header_array << coin } - CSV.open(@table, 'w') { |header| header << header_array } - end end end diff --git a/lib/ctfc/request.rb b/lib/ctfc/request.rb deleted file mode 100644 index a8eff97..0000000 --- a/lib/ctfc/request.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require_relative 'api' - -module CTFC - class Request - include API - - def self.[fiat = :eur, coins = [], source = :cryptocompare] - unless fiat.is_a?(Symbol) && source.is_a?(Symbol) && coins.is_a?(Array) - raise TypeError, 'Use symbols, and array of strings for coins' - end - process_source fiat, coins, source - end - - private - - def process_source(fiat, coins, source) - @response = - case source - when :cryptocompare - Cryptocompare[fiat, coins] - when :binance - # Binance[fiat, coins] - raise NoMethodError, 'Working on Binance implementation' - else - raise NoMethodError, 'Not implemented, yet! Feel free to contribute!' - end - end - end -end From 69f51d3085eef61e8b9e18a2913ddf10b9dd52ff Mon Sep 17 00:00:00 2001 From: alx3dev Date: Fri, 11 Feb 2022 15:32:42 +0100 Subject: [PATCH 07/26] add export module --- lib/ctfc.rb | 2 +- lib/ctfc/api.rb | 8 +++++-- lib/ctfc/api/apitemplate.rb | 43 +++++++++++++-------------------- lib/ctfc/api/cryptocompare.rb | 17 ++++++------- lib/ctfc/client.rb | 45 ++++++++++++----------------------- lib/ctfc/export.rb | 31 ++++++++++++++++++++++++ 6 files changed, 78 insertions(+), 68 deletions(-) create mode 100644 lib/ctfc/export.rb diff --git a/lib/ctfc.rb b/lib/ctfc.rb index eae1d19..d59bf57 100644 --- a/lib/ctfc.rb +++ b/lib/ctfc.rb @@ -8,7 +8,7 @@ # # @note For instance methods look at CTFC::Data. # -class Ctfc < CTFC::Data +class Ctfc < CTFC::Client ## # @todo Allow Ctfc to use proxy and/or tor # diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index ea10564..33e4e4b 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -3,8 +3,11 @@ require 'rest-client' require 'json' +require_relative 'api/apitemplate' + # automatically require new apis -CTFC::API.list.select { |x| require_relative x } +# to-do: change #to_s to #name for ruby3 +CTFC::API.list.select { |api| require_relative api.to_s } module CTFC module API @@ -19,7 +22,8 @@ def list def get_sources_from_files_in_api_dir sources, skip = [], %w[. .. apitemplate.rb] Dir.entries("#{File.expand_path(__FILE__)}/api").select do |source| - sources << source.gsub('.rb', '').to_sym unless skip.include? source + next if skip.include? source + sources << source.gsub('.rb', '').to_sym end @list = sources end diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 409836f..44b1ea3 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -3,41 +3,30 @@ module CTFC module API class ApiTemplate - class << self + class << self - MAX_RETRY = 3 - BASE_URL = { - cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?', - kraken: '', - binance: '' }.freeze + attr_reader :response - attr_reader :response + MAX_RETRY = 3 + BASE_URL = { + cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?', + kraken: '', + binance: '' }.freeze - def prepare_response_hash(fiat, coins, source) - @response = { fiat: fiat, coins: coins, uri: BASE_URL[source] } - process fiat, coins - end + def prepare_response_hash(fiat, coins, source) + @response = { fiat: fiat, coins: coins, uri: BASE_URL[source] } + process fiat, coins + end - private - def process(fiat = response[:fiat], coins = response[:coins]) - return false unless fiat && coins - end + private - def save_csv_data(table, data_row, coins) - return unless save? + def process(fiat = response[:fiat], coins = response[:coins]) + return false unless fiat && coins + # to-do: check for api key before sending request + end - create_csv_headers(table, coins) unless File.exist?(table) - CSV.open(table, 'ab') { |column| column << data_row } end - - def create_csv_headers(table, coins) - header_array = ['TIME'] - coins.each { |coin| header_array << coin } - CSV.open(table, 'w') { |header| header << header_array } - end - - end end end end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index f5faa71..f85a6ad 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -1,33 +1,34 @@ # frozen_string_literal: true +require_relative 'apitemplate' + module API class Cryptocompare < ApiTemplate class << self def [](fiat, coins) - result = prepare_response_hash fiat, coins, :cryptocompare - result.response + # load template attributes and send request + request = prepare_response_hash fiat, coins, :cryptocompare + request.response end private def process(fiat = response[:fiat], coins = response[:coins]) - coins.collect do |coin| - @response[:uri] += "fsyms=#{coin}&" - end + super + coins.collect { |coin| @response[:uri] += "fsyms=#{coin}&" } request = RestClient.get response[:uri] data = JSON.parse request process_json_data fiat, coins, data end def process_json_data(fiat, coins, data) - prices, data_row = {}, [Time.now.to_s] + prices, time_at = {}, Time.now.to_s coins.each do |coin| value = data['RAW'][coin.upcase][fiat.name.upcase]['PRICE'].round(2) prices[coin] = value - data_row << value end - @response = { data: data, data_row: data_row, prices: prices } + @response.merge! time_at: time_at, data: data, prices: prices rescue StandardError if (@response[:counter] += 1) > MAX_RETRY puts response[:data].to_s.split(',') diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index 2339dbb..c46f94e 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'config' +require_relative 'export' require_relative 'version' require 'json' @@ -8,16 +8,13 @@ require 'kolorit' require 'rest-client' -## +# # @see CTFC::Request # @see Ctfc # module CTFC - ## - # Data class keep all the logic to send request, receive response, - # and everything between. Class Ctfc extend CTFC::Client, for easier work. # - # @note Instead of using CTFC::Client.new, you can also call Ctfc.new + # Get data from source. # class Client attr_reader :response @@ -25,19 +22,6 @@ class Client alias currency fiat - ## - # @example Initialization example - # @data = CTFC::Client.new :eur, save: true - # - # @param [Symbol] currency **Optional**. Define fiat currency. - # @param [Hash] opts **Optional**. Additional options hash. - # - # @option opts [Boolean] print **Optional**. Print terminal output. - # @option opts [Boolean] save **Optional**. Save `.csv` output. - # @option opts [Array] coins **Optional**. Define coins to scrap. - # - # @return [Client] Client instance - # def initialize(curr = :eur, opts = {}) @fiat = curr.name.upcase @save = opts[:save].nil? ? true : opts[:save] @@ -46,12 +30,13 @@ def initialize(curr = :eur, opts = {}) @source = opts[:source].nil? ? :cryptocompare : opts[:source] end - def get - call_api_request - prices = response[:prices] + def get(source = @source) + send_api_request(source) + Export.to_csv(response) if save? + @prices = response[:prices] end - ## + # # Get fiat value from response hash with crypto prices # # @example @@ -65,7 +50,7 @@ def price(coin) prices[coin.to_s.upcase] end - ## + # # Check if crypto prices will be saved in `.csv` table # # @return [true || false] @@ -74,7 +59,7 @@ def save? @save == true end - ## + # # Check if crypto prices will be printed in terminal # # @return [true || false] @@ -83,7 +68,7 @@ def print? @print == true end - ## + # # Change option to save '.csv' table with prices # # @return [true || false] @@ -92,7 +77,7 @@ def save=(opt) @save = opt.is_a?(TrueClass) end - ## + # # Change option to print prices in terminal # # @return [true || false] @@ -101,7 +86,7 @@ def print=(opt) @print = opt.is_a?(TrueClass) end - ## + # # Check if request was successful or not. # # @return [true || false] @@ -114,9 +99,9 @@ def success? private - def call_api_request + def send_api_request(source) @response = - case @source + case source when :cryptocompare Cryptocompare[@fiat, @coins] when :binance diff --git a/lib/ctfc/export.rb b/lib/ctfc/export.rb new file mode 100644 index 0000000..2ae6687 --- /dev/null +++ b/lib/ctfc/export.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Export + class << self + + def to_csv(**response) + table = response[:table] + coins = response[:coins] + data_row = get_price_array_from response + create_csv_headers(table, coins) unless File.exist?(table) + CSV.open(table, 'ab') { |column| column << data_row } + end + + private + + def create_csv_headers(table, coins) + header_array = ['TIME'] + coins.each { |coin| header_array << coin } + CSV.open(table, 'w') { |header| header << header_array } + end + + def get_price_array_from(**response) + price_array = [response[:time_at]] + response[:prices].each do |coin, price| + price_array << price + end + price_array + end + + end +end From ad38b3abdb0b48f7893466c1461454ae84c0ee60 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Sat, 12 Feb 2022 01:52:10 +0100 Subject: [PATCH 08/26] pry debug --- ctfc.gemspec | 16 +++++---- lib/ctfc.rb | 2 +- lib/ctfc/api.rb | 6 ++-- lib/ctfc/api/apitemplate.rb | 40 +++++++++++----------- lib/ctfc/api/cryptocompare.rb | 51 ++++++++++++++++------------ lib/ctfc/client.rb | 15 ++++----- lib/ctfc/export.rb | 8 ++--- spec/config_spec.rb | 19 ----------- spec/data_spec.rb | 63 ----------------------------------- spec/spec_helper.rb | 12 ------- 10 files changed, 72 insertions(+), 160 deletions(-) delete mode 100644 spec/config_spec.rb delete mode 100644 spec/data_spec.rb delete mode 100644 spec/spec_helper.rb diff --git a/ctfc.gemspec b/ctfc.gemspec index e462967..b67870f 100644 --- a/ctfc.gemspec +++ b/ctfc.gemspec @@ -31,20 +31,22 @@ Gem::Specification.new do |s| bin/console lib/ctfc.rb lib/ctfc/version.rb - lib/ctfc/base.rb - lib/ctfc/config.rb + lib/ctfc/export.rb + lib/ctfc/client.rb + lib/ctfc/api.rb + lib/ctfc/api/apitemplate.rb + lib/ctfc/api/cryptocompare.rb LICENSE README.md ctfc.gemspec] - s.required_ruby_version = '>= 2.6', '< 4' + s.required_ruby_version = '~> 3' s.add_runtime_dependency 'kolorit', '~> 0.1.3' s.add_runtime_dependency 'optimist', '~> 3.0.1' s.add_runtime_dependency 'rest-client', '~> 2.1.0' - s.add_development_dependency 'bundler', '~> 2.2.9' - s.add_development_dependency 'pry', '~> 0.14.1' - s.add_development_dependency 'rake', '~> 13.0.3' - s.add_development_dependency 'rspec', '~> 3.10.0' + s.add_development_dependency 'bundler', '~> 2.3' + s.add_development_dependency 'pry', '~> 0.14' + s.add_development_dependency 'rake', '~> 13.0' end diff --git a/lib/ctfc.rb b/lib/ctfc.rb index d59bf57..6cee305 100644 --- a/lib/ctfc.rb +++ b/lib/ctfc.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'ctfc/base' +require_relative 'ctfc/client' ## # For easier job use Ctfc, instead of typing CTFC::Data. diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index 33e4e4b..ab1ee54 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -7,14 +7,14 @@ # automatically require new apis # to-do: change #to_s to #name for ruby3 -CTFC::API.list.select { |api| require_relative api.to_s } - +#CTFC::API.list.select { |api| require_relative api.to_s } +require_relative 'api/cryptocompare' module CTFC module API class << self def list - @list || get_sources_from_files_in_api_dir + @list ||= get_sources_from_files_in_api_dir end private diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 44b1ea3..6633f8c 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -3,29 +3,27 @@ module CTFC module API class ApiTemplate - class << self - - attr_reader :response - - MAX_RETRY = 3 - BASE_URL = { - cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?', - kraken: '', - binance: '' }.freeze - - def prepare_response_hash(fiat, coins, source) - @response = { fiat: fiat, coins: coins, uri: BASE_URL[source] } - process fiat, coins - end - - - private + attr_reader :response + + MAX_RETRY = 3 + BASE_URL = { + cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?', + kraken: '', + binance: '' }.freeze + + def initialize(fiat, coins, source) + @response = { fiat: fiat, + coins: coins, + uri: BASE_URL[source] } + process + end - def process(fiat = response[:fiat], coins = response[:coins]) - return false unless fiat && coins - # to-do: check for api key before sending request - end + private + # @todo api key support + # @todo proxy support + def process + return false unless response[:fiat] && response[:coins] end end end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index f85a6ad..6c7748b 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -2,42 +2,49 @@ require_relative 'apitemplate' -module API +module CTFC::API class Cryptocompare < ApiTemplate - class << self - def [](fiat, coins) - # load template attributes and send request - request = prepare_response_hash fiat, coins, :cryptocompare - request.response + def self.[](fiat, coins) + new(fiat, coins).response + end + + def initialize(fiat, coins) + super fiat, coins, :cryptocompare end private - def process(fiat = response[:fiat], coins = response[:coins]) + def process super - coins.collect { |coin| @response[:uri] += "fsyms=#{coin}&" } - request = RestClient.get response[:uri] + fiat, coins = response[:fiat], response[:coins] + uri = response[:uri] + coins.collect do |coin| + next if uri.include? coin + uri += "fsyms=#{coin}&" + end + uri += "tsyms=#{fiat}" unless uri.include? fiat + time = Time.now.to_s + request = RestClient.get uri data = JSON.parse request - process_json_data fiat, coins, data + process_json_data fiat, coins, data, time + rescue StandardError => e + if (@counter += 1) > MAX_RETRY + puts e.message + @counter = 0 + false + else + retry + end end - def process_json_data(fiat, coins, data) - prices, time_at = {}, Time.now.to_s + def process_json_data(fiat, coins, data, time_at) + prices = {} coins.each do |coin| - value = data['RAW'][coin.upcase][fiat.name.upcase]['PRICE'].round(2) + value = data['RAW'][coin.upcase][fiat.upcase]['PRICE'].round(2) prices[coin] = value end @response.merge! time_at: time_at, data: data, prices: prices - rescue StandardError - if (@response[:counter] += 1) > MAX_RETRY - puts response[:data].to_s.split(',') - false - else - process_json_data fiat, coins, data - end end - - end end end diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index c46f94e..34ee814 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'export' +require_relative 'api' require_relative 'version' require 'json' @@ -17,6 +18,8 @@ module CTFC # Get data from source. # class Client + include CTFC::API + attr_reader :response attr_accessor :fiat, :coins, :prices, :source @@ -31,16 +34,16 @@ def initialize(curr = :eur, opts = {}) end def get(source = @source) - send_api_request(source) + request = send_api_request(source) + #binding.pry + @response.merge! request Export.to_csv(response) if save? @prices = response[:prices] end # # Get fiat value from response hash with crypto prices - # # @example - # # @data.price(:btc) # # @param [Symbol || String] coin **Required**. Coin name as symbol or string. @@ -52,7 +55,6 @@ def price(coin) # # Check if crypto prices will be saved in `.csv` table - # # @return [true || false] # def save? @@ -70,7 +72,6 @@ def print? # # Change option to save '.csv' table with prices - # # @return [true || false] # def save=(opt) @@ -79,7 +80,6 @@ def save=(opt) # # Change option to print prices in terminal - # # @return [true || false] # def print=(opt) @@ -88,7 +88,6 @@ def print=(opt) # # Check if request was successful or not. - # # @return [true || false] # def success? @@ -103,7 +102,7 @@ def send_api_request(source) @response = case source when :cryptocompare - Cryptocompare[@fiat, @coins] + Cryptocompare[fiat, coins] when :binance # Binance[fiat, coins] raise NoMethodError, 'Working on Binance implementation' diff --git a/lib/ctfc/export.rb b/lib/ctfc/export.rb index 2ae6687..97a1322 100644 --- a/lib/ctfc/export.rb +++ b/lib/ctfc/export.rb @@ -3,10 +3,10 @@ module Export class << self - def to_csv(**response) - table = response[:table] + def to_csv(response = {}) + table = "ctfc_#{response[:fiat]}_#{response[:source]}.csv" coins = response[:coins] - data_row = get_price_array_from response + data_row = price_array_from response create_csv_headers(table, coins) unless File.exist?(table) CSV.open(table, 'ab') { |column| column << data_row } end @@ -19,7 +19,7 @@ def create_csv_headers(table, coins) CSV.open(table, 'w') { |header| header << header_array } end - def get_price_array_from(**response) + def price_array_from(response = {}) price_array = [response[:time_at]] response[:prices].each do |coin, price| price_array << price diff --git a/spec/config_spec.rb b/spec/config_spec.rb deleted file mode 100644 index b416536..0000000 --- a/spec/config_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require_relative './spec_helper' - -RSpec.describe CTFC::CONFIG do - context 'Configuration Constants' do - it 'has crypto coins defined before initialisation' do - expect(CTFC::CONFIG::COINS).not_to be nil - end - - it 'has api url defined before initialisation' do - expect(CTFC::CONFIG::URL).not_to be nil - end - - it 'has request max retries defined before initialisation' do - expect(CTFC::CONFIG::MAX_RETRY).not_to be nil - end - end -end diff --git a/spec/data_spec.rb b/spec/data_spec.rb deleted file mode 100644 index 4c8e671..0000000 --- a/spec/data_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require_relative './spec_helper' - -FIAT = :usd -CRYPTO = %w[BTC XMR ETH].freeze - -RSpec.describe CTFC::Data do - let(:crypto) { CTFC::Data.new(FIAT, coins: CRYPTO, save: nil, print: nil) } - - context 'Initialized Configuration' do - it 'sets currency as string from given symbol' do - expect(crypto.fiat).to eq FIAT.to_s.upcase - expect(crypto.fiat.class).to be String - end - - it 'sets passed coins with precedence over configured' do - expect(crypto.coins).not_to be nil - expect(crypto.coins.class).to be Array - expect(crypto.coins).not_to eq CTFC::Data::COINS - end - - it 'save csv table unless otherwise defined' do - expect(crypto.save?).not_to be nil - expect(crypto.save?).to be true - end - - it 'print terminal output unless otherwise defined' do - expect(crypto.print?).not_to be nil - expect(crypto.print?).to be true - end - end -end - -RSpec.describe CTFC::Data do - let(:crypto) { CTFC::Data.new(FIAT, coins: @coins, save: nil, print: nil) } - - context 'Change configuration on-the-fly' do - it 'sets options passed to #get with precedence over initialized ones' do - initialized_currency = crypto.fiat - # execute request - crypto.get(:eur, print: false, save: false) - # test results - expect(crypto.fiat).not_to eq initialized_currency - expect(crypto.fiat.class).to be String - expect(crypto.fiat).to eq 'EUR' - expect(crypto.save?).to be false - expect(crypto.print?).to be false - end - - it 'allow setter method for option save' do - expect(crypto.save?).to be true - crypto.save = false - expect(crypto.save?).to be false - end - - it 'allow setter method for option print' do - expect(crypto.print?).to be true - crypto.print = false - expect(crypto.print?).to be false - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 5822de1..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'rspec' -require_relative '../lib/ctfc' - -RSpec.configure do |config| - config.example_status_persistence_file_path = '.rspec_status' - - config.expect_with :rspec do |c| - c.syntax = :expect - end -end From ba9d821ab324a2e0a4ab8b95ce4c30c7815e6b6a Mon Sep 17 00:00:00 2001 From: alx3dev Date: Sat, 12 Feb 2022 02:53:48 +0100 Subject: [PATCH 09/26] prepare for new version --- .rubocop.yml | 2 +- .rubocop_todo.yml | 2 +- .ruby-version | 1 - ctfc.gemspec | 2 +- lib/ctfc/api.rb | 11 ++--- lib/ctfc/api/apitemplate.rb | 6 ++- lib/ctfc/api/cryptocompare.rb | 75 ++++++++++++++++++----------------- lib/ctfc/client.rb | 5 +-- lib/ctfc/export.rb | 4 +- 9 files changed, 54 insertions(+), 54 deletions(-) delete mode 100644 .ruby-version diff --git a/.rubocop.yml b/.rubocop.yml index 1de8c2c..8f5e3b9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,3 @@ AllCops: - TargetRubyVersion: '2.6' + TargetRubyVersion: '2.7' NewCops: enable diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5c39285..2ec6779 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -19,4 +19,4 @@ Metrics/BlockLength: AllCops: NewCops: enable - TargetRubyVersion: '2.6' + TargetRubyVersion: '2.7' diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 75a22a2..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.0.3 diff --git a/ctfc.gemspec b/ctfc.gemspec index b67870f..77a154b 100644 --- a/ctfc.gemspec +++ b/ctfc.gemspec @@ -40,7 +40,7 @@ Gem::Specification.new do |s| README.md ctfc.gemspec] - s.required_ruby_version = '~> 3' + s.required_ruby_version = '> 2.7', '< 3.2' s.add_runtime_dependency 'kolorit', '~> 0.1.3' s.add_runtime_dependency 'optimist', '~> 3.0.1' diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index ab1ee54..a34a9f9 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -7,22 +7,23 @@ # automatically require new apis # to-do: change #to_s to #name for ruby3 -#CTFC::API.list.select { |api| require_relative api.to_s } +# CTFC::API.list.select { |api| require_relative api.to_s } require_relative 'api/cryptocompare' module CTFC module API class << self - def list - @list ||= get_sources_from_files_in_api_dir + @list ||= list_files_in_api_dir end private - def get_sources_from_files_in_api_dir - sources, skip = [], %w[. .. apitemplate.rb] + def list_files_in_api_dir + sources = [] + skip = %w[. .. apitemplate.rb] Dir.entries("#{File.expand_path(__FILE__)}/api").select do |source| next if skip.include? source + sources << source.gsub('.rb', '').to_sym end @list = sources diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 6633f8c..3edd9a0 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -3,15 +3,17 @@ module CTFC module API class ApiTemplate - attr_reader :response + attr_reader :response, :counter MAX_RETRY = 3 BASE_URL = { cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?', kraken: '', - binance: '' }.freeze + binance: '' + }.freeze def initialize(fiat, coins, source) + @counter = 0 @response = { fiat: fiat, coins: coins, uri: BASE_URL[source] } diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index 6c7748b..bea56d7 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -2,49 +2,50 @@ require_relative 'apitemplate' -module CTFC::API - class Cryptocompare < ApiTemplate - - def self.[](fiat, coins) - new(fiat, coins).response - end +module CTFC + module API + class Cryptocompare < ApiTemplate + def self.[](fiat, coins) + new(fiat, coins).response + end - def initialize(fiat, coins) - super fiat, coins, :cryptocompare - end + def initialize(fiat, coins) + super fiat, coins, :cryptocompare + end - private + private - def process - super - fiat, coins = response[:fiat], response[:coins] - uri = response[:uri] - coins.collect do |coin| - next if uri.include? coin - uri += "fsyms=#{coin}&" - end - uri += "tsyms=#{fiat}" unless uri.include? fiat - time = Time.now.to_s - request = RestClient.get uri - data = JSON.parse request - process_json_data fiat, coins, data, time - rescue StandardError => e - if (@counter += 1) > MAX_RETRY - puts e.message - @counter = 0 - false - else - retry + def process + super + uri = response[:uri] + fiat = response[:fiat] + coins = response[:coins] + coins.collect do |coin| + uri += "fsyms=#{coin}&" unless uri.include? coin + end + uri += "tsyms=#{fiat}" unless uri.include? fiat + time = Time.now.to_s + request = RestClient.get uri + data = JSON.parse request + process_json_data fiat, coins, data, time + rescue StandardError => e + if (@counter += 1) > MAX_RETRY + puts e.message + @counter = 0 + false + else + retry + end end - end - def process_json_data(fiat, coins, data, time_at) - prices = {} - coins.each do |coin| - value = data['RAW'][coin.upcase][fiat.upcase]['PRICE'].round(2) - prices[coin] = value + def process_json_data(fiat, coins, data, time_at) + prices = {} + coins.each do |coin| + value = data['RAW'][coin.upcase][fiat.upcase]['PRICE'].round(2) + prices[coin] = value + end + @response.merge! time_at: time_at, data: data, prices: prices end - @response.merge! time_at: time_at, data: data, prices: prices end end end diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index 34ee814..755eed3 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -26,7 +26,7 @@ class Client alias currency fiat def initialize(curr = :eur, opts = {}) - @fiat = curr.name.upcase + @fiat = curr.to_s.upcase @save = opts[:save].nil? ? true : opts[:save] @print = opts[:print].nil? ? true : opts[:print] @coins = opts[:coins].nil? ? COINS : Array(opts[:coins]) @@ -35,7 +35,7 @@ def initialize(curr = :eur, opts = {}) def get(source = @source) request = send_api_request(source) - #binding.pry + # binding.pry @response.merge! request Export.to_csv(response) if save? @prices = response[:prices] @@ -110,6 +110,5 @@ def send_api_request(source) raise NoMethodError, 'Not implemented, yet! Feel free to contribute!' end end - end end diff --git a/lib/ctfc/export.rb b/lib/ctfc/export.rb index 97a1322..334b0ca 100644 --- a/lib/ctfc/export.rb +++ b/lib/ctfc/export.rb @@ -2,7 +2,6 @@ module Export class << self - def to_csv(response = {}) table = "ctfc_#{response[:fiat]}_#{response[:source]}.csv" coins = response[:coins] @@ -21,11 +20,10 @@ def create_csv_headers(table, coins) def price_array_from(response = {}) price_array = [response[:time_at]] - response[:prices].each do |coin, price| + response[:prices].each do |_coin, price| price_array << price end price_array end - end end From 20ed277fa321775c4a64dfd6901dacd8de679cbc Mon Sep 17 00:00:00 2001 From: alx3dev Date: Sat, 12 Feb 2022 03:01:15 +0100 Subject: [PATCH 10/26] fix csv table name --- lib/ctfc/client.rb | 2 +- lib/ctfc/export.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index 755eed3..27e09ec 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -37,8 +37,8 @@ def get(source = @source) request = send_api_request(source) # binding.pry @response.merge! request - Export.to_csv(response) if save? @prices = response[:prices] + Export.to_csv(source, response) if save? end # diff --git a/lib/ctfc/export.rb b/lib/ctfc/export.rb index 334b0ca..2cd1a48 100644 --- a/lib/ctfc/export.rb +++ b/lib/ctfc/export.rb @@ -2,8 +2,8 @@ module Export class << self - def to_csv(response = {}) - table = "ctfc_#{response[:fiat]}_#{response[:source]}.csv" + def to_csv(source, response = {}) + table = "ctfc_#{response[:fiat]}_#{source}.csv" coins = response[:coins] data_row = price_array_from response create_csv_headers(table, coins) unless File.exist?(table) From 120e4d5e49108e17efc3031d1dbb477acc2aee35 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Sat, 12 Feb 2022 12:18:54 +0100 Subject: [PATCH 11/26] fix export file --- bin/console | 2 +- bin/ctfc | 16 ++++++-------- examples/example.rb | 29 -------------------------- examples/example1.rb | 31 ---------------------------- examples/example2.rb | 39 ----------------------------------- lib/ctfc.rb | 1 - lib/ctfc/api.rb | 13 ++++++------ lib/ctfc/api/apitemplate.rb | 11 ++++++++-- lib/ctfc/api/cryptocompare.rb | 7 ++++--- lib/ctfc/client.rb | 17 ++++++--------- lib/ctfc/export.rb | 11 ++++++++++ 11 files changed, 44 insertions(+), 133 deletions(-) delete mode 100755 examples/example.rb delete mode 100755 examples/example1.rb delete mode 100755 examples/example2.rb diff --git a/bin/console b/bin/console index 45e0f5d..05750db 100755 --- a/bin/console +++ b/bin/console @@ -1,4 +1,4 @@ -#/usr/bin/env ruby +#!/usr/bin/env ruby # frozen_string_literal: true require 'pry' diff --git a/bin/ctfc b/bin/ctfc index 4eef8c4..7876f94 100755 --- a/bin/ctfc +++ b/bin/ctfc @@ -15,7 +15,7 @@ opts = Optimist.options do banner ' ruby bin/ctfc eur usd --no-save --coins btc xmr ltc' banner '' - opt :coins, 'Set crypto coins', default: CTFC::CONFIG::COINS + opt :coins, 'Set crypto coins', default: %w[BTC XMR ETH LTC] opt :no_save, "Do not save '.csv' output" opt :no_print, 'Do not print terminal output' opt :loop, 'Run script N times', default: 1, type: :integer @@ -31,20 +31,16 @@ print = opts[:no_print] ? false : true if ARGV.empty? # default behavior without arguments - change to suit your needs - @crypto.get(:eur, save: false, print: true, coins: coins) - + crypto = CTFC::Client.new(:eur, save: true, print: true, coins: coins) + crypto.get else - opts[:loop].times do ARGV.each do |fiat| next if opts.include?(fiat.downcase) - # in Ruby 3.1: @crypto.get(fiat, save:, print:, coins:) - @crypto.get(fiat, - save: save, - print: print, - coins: coins) - sleep 1 # wait between requests + crypto = CTFC::Client.new(fiat, save: save, print: print, coins: coins) + crypto.get + sleep 0.5 end # end if no `--loop` arg diff --git a/examples/example.rb b/examples/example.rb deleted file mode 100755 index e3aaf2b..0000000 --- a/examples/example.rb +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require_relative '../lib/ctfc' - -## -# Use default coins, get prices in RSD every 5 minutes. -# Print terminal output, append data to `.csv` table. -# -@client = Ctfc.new :rsd -loop do - @client.get - sleep 300 -end - -## -# Get prices for EUR, USD and RSD with different configuration -# -# @eur = Ctfc.new :eur, coins: %w[BTC XMR] -# @usd = Ctfc.new :usd, print: false, coins: %w[BTC XMR] -# @rsd = Ctfc.new :rsd, save: false, %w[LTC ETH] -# -# loop do -# @eur.get -# @usd.get -# @rsd.get -# sleep 300 -# end -# diff --git a/examples/example1.rb b/examples/example1.rb deleted file mode 100755 index 11013f4..0000000 --- a/examples/example1.rb +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require_relative '../lib/ctfc' - -# define coins to scrap -Crypto::COINS = %w[BTC XMR LTC ETH].freeze - -# define class for USD [save only] -class USD - def initialize - Crypto.to :usd, save: true, print: false - end -end - -# define class for EUR [print and save] -class EUR - def initialize - Crypto.to :eur, save: true, print: true - end -end - -## -# Get crypto prices every 5 minutes. -# Print and save EUR rates, print-only USD rates. -# -loop do - USD.new - EUR.new - sleep 300 -end diff --git a/examples/example2.rb b/examples/example2.rb deleted file mode 100755 index 2c8385a..0000000 --- a/examples/example2.rb +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require_relative '../lib/ctfc' - -## -# @note You can do this with `bin/ruby ctfc usd eur rsd`, this is just example. -# -# Make base class to extend it with class named as currency code. -# Set configuration to save '.csv' table without terminal output, -# and scrap data for Bitcoin, Monero and Ethereum. -# -class Fiat - def initialize - currency = instance_of?(Fiat) ? 'EUR' : self.class.name - - Crypto.to(currency, - save: true, - print: false, - coins: %w[BTC XMR ETH]) - end -end - -# name class as currency code -class USD < Fiat; end -class EUR < Fiat; end -class RSD < Fiat; end -class GBP < Fiat; end - -# check if arguments contain any of defined currencies -ARGV.select do |arg| - case arg - when 'usd', 'USD' then USD.new - when 'rsd', 'RSD' then RSD.new - when 'eur', 'EUR' then EUR.new - when 'gbp', 'GBP' then GBP.new - else puts "#{arg} is not supported at this time." - end -end diff --git a/lib/ctfc.rb b/lib/ctfc.rb index 6cee305..7566f30 100644 --- a/lib/ctfc.rb +++ b/lib/ctfc.rb @@ -13,7 +13,6 @@ class Ctfc < CTFC::Client # @todo Allow Ctfc to use proxy and/or tor # def initialize(currency = :eur, opts = {}) - opts[:coins] ||= COINS super(currency, opts) end diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index a34a9f9..9e495a3 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -5,10 +5,6 @@ require_relative 'api/apitemplate' -# automatically require new apis -# to-do: change #to_s to #name for ruby3 -# CTFC::API.list.select { |api| require_relative api.to_s } -require_relative 'api/cryptocompare' module CTFC module API class << self @@ -21,13 +17,18 @@ def list def list_files_in_api_dir sources = [] skip = %w[. .. apitemplate.rb] - Dir.entries("#{File.expand_path(__FILE__)}/api").select do |source| + path = File.expand_path(__FILE__).gsub!('.rb', '') + Dir.entries(path).select do |source| next if skip.include? source sources << source.gsub('.rb', '').to_sym end - @list = sources + sources end end end end + +# automatically require new apis +# to-do: change #to_s to #name for ruby3 +CTFC::API.list.select { |source| require_relative "api/#{source}" } diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 3edd9a0..f581537 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -3,7 +3,7 @@ module CTFC module API class ApiTemplate - attr_reader :response, :counter + attr_reader :response MAX_RETRY = 3 BASE_URL = { @@ -12,10 +12,12 @@ class ApiTemplate binance: '' }.freeze + # use hash shortcut in ruby >= 3.1 def initialize(fiat, coins, source) - @counter = 0 @response = { fiat: fiat, coins: coins, + counter: 0, + success: false, uri: BASE_URL[source] } process end @@ -27,6 +29,11 @@ def initialize(fiat, coins, source) def process return false unless response[:fiat] && response[:coins] end + + def success!(set: true) + set = false unless set == true + @response[:success] = set + end end end end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index bea56d7..2f453b2 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -29,10 +29,10 @@ def process data = JSON.parse request process_json_data fiat, coins, data, time rescue StandardError => e - if (@counter += 1) > MAX_RETRY + success! set: false + if (@response[:counter] += 1) > MAX_RETRY puts e.message - @counter = 0 - false + @response[:counter] = 0 else retry end @@ -44,6 +44,7 @@ def process_json_data(fiat, coins, data, time_at) value = data['RAW'][coin.upcase][fiat.upcase]['PRICE'].round(2) prices[coin] = value end + success! @response.merge! time_at: time_at, data: data, prices: prices end end diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index 27e09ec..dd2993d 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -27,10 +27,10 @@ class Client def initialize(curr = :eur, opts = {}) @fiat = curr.to_s.upcase - @save = opts[:save].nil? ? true : opts[:save] - @print = opts[:print].nil? ? true : opts[:print] - @coins = opts[:coins].nil? ? COINS : Array(opts[:coins]) - @source = opts[:source].nil? ? :cryptocompare : opts[:source] + @print = opts[:print] || true + @save = opts[:save] || true + @coins = opts[:coins] || %w[BTC XMR LTC ETH] + @source = opts[:source] || :cryptocompare end def get(source = @source) @@ -39,6 +39,7 @@ def get(source = @source) @response.merge! request @prices = response[:prices] Export.to_csv(source, response) if save? + # pp prices if print? end # @@ -86,14 +87,8 @@ def print=(opt) @print = opt.is_a?(TrueClass) end - # - # Check if request was successful or not. - # @return [true || false] - # def success? - return false if @response.nil? - - @response.code == 200 + response[:success] == true end private diff --git a/lib/ctfc/export.rb b/lib/ctfc/export.rb index 2cd1a48..0054e2c 100644 --- a/lib/ctfc/export.rb +++ b/lib/ctfc/export.rb @@ -10,6 +10,17 @@ def to_csv(source, response = {}) CSV.open(table, 'ab') { |column| column << data_row } end + def to_json(source, response = {}) + table = "ctfc_#{response[:fiat]}_#{source}.json" + data = JSON.pretty_generate(response[:data]) + File.write(table, data) + end + + def all(*args) + to_csv(*args) + to_json(*args) + end + private def create_csv_headers(table, coins) From abac6cc44d18b124c8038d29d53b4289449b5c53 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Tue, 15 Feb 2022 09:06:25 +0100 Subject: [PATCH 12/26] add BASE_URL to source files --- ctfc.gemspec | 5 +-- lib/ctfc/api.rb | 7 ++-- lib/ctfc/api/apitemplate.rb | 29 ++++++++++------ lib/ctfc/api/cryptocompare.rb | 60 +++++++++++++++++++++++++------- lib/ctfc/client.rb | 65 +++++++++++++++++------------------ lib/ctfc/export.rb | 13 +++++++ 6 files changed, 118 insertions(+), 61 deletions(-) diff --git a/ctfc.gemspec b/ctfc.gemspec index 77a154b..5dabefd 100644 --- a/ctfc.gemspec +++ b/ctfc.gemspec @@ -27,9 +27,7 @@ Gem::Specification.new do |s| s.metadata['license_uri'] = 'https://github.com/alx3dev/ctfc/LICENSE' s.metadata['rubygems_mfa_required'] = 'true' - s.files = %w[ bin/ctfc - bin/console - lib/ctfc.rb + s.files = %w[ lib/ctfc.rb lib/ctfc/version.rb lib/ctfc/export.rb lib/ctfc/client.rb @@ -42,7 +40,6 @@ Gem::Specification.new do |s| s.required_ruby_version = '> 2.7', '< 3.2' - s.add_runtime_dependency 'kolorit', '~> 0.1.3' s.add_runtime_dependency 'optimist', '~> 3.0.1' s.add_runtime_dependency 'rest-client', '~> 2.1.0' diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index 9e495a3..a69d9d2 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -1,13 +1,16 @@ # frozen_string_literal: true +require_relative 'api/apitemplate' + require 'rest-client' require 'json' -require_relative 'api/apitemplate' - module CTFC module API class << self + # + # Get list of sources from files in api dir. + # def list @list ||= list_files_in_api_dir end diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index f581537..4143e9d 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -2,23 +2,32 @@ module CTFC module API + # Template for other sources. Every file in api dir should extend this class. + # Automatically call method #process to send api request after initialization. + # This mean every source should include #process method, that will be executed + # after calling **super** in initialize. + # + # @see CTFC::API::Cryptocompare + # class ApiTemplate attr_reader :response + # max number of requests to send MAX_RETRY = 3 - BASE_URL = { - cryptocompare: 'https://min-api.cryptocompare.com/data/pricemultifull?', - kraken: '', - binance: '' - }.freeze - # use hash shortcut in ruby >= 3.1 + # Construct response hash from given arguments, and start counting requests. + # + # @param [Symbol] fiat **Required**. Fiat currency to convert coin price. + # @param [Array] coins **Required**. Array of coins to scrap data for. + # @param [Symbol] source **Required**. Source to tell us which api to call. + # + # @return [Object] ApiTemplate instance. + # def initialize(fiat, coins, source) @response = { fiat: fiat, coins: coins, - counter: 0, - success: false, - uri: BASE_URL[source] } + success: false } + @counter = 0 process end @@ -31,7 +40,7 @@ def process end def success!(set: true) - set = false unless set == true + @counter = 0 if set == true @response[:success] = set end end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index 2f453b2..c581f6b 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -4,43 +4,79 @@ module CTFC module API + # Source file for cryptocompare api. class Cryptocompare < ApiTemplate + # Cryptocompare API base url, where we add coins and fiat currency. + BASE_URL = 'https://min-api.cryptocompare.com/data/pricemultifull?' + + # Initialize new instance, send request and return response hash. + # @example + # Cryptocompare[:eur, %w[BTC XMR]] + # + # @param [Symbol] fiat **Required**. Fiat currency. + # @param [Array] coins **Required**. Cryptocurrency coins. + # + # @return [Hash] Response hash object. + # def self.[](fiat, coins) new(fiat, coins).response end + # Initialize will automatically call #process + # to send request after all settings are configured. + # + # @example Send request to cryptocompare + # crypto = Cryptocompare.new :eur, %w[BTC XMR] + # + # @param [Symbol] fiat **Required**. Fiat currency. + # @param [Array] coins **Required**. Cryptocurrency coins. + # + # @return [Object] Cryptocompare instance. + # def initialize(fiat, coins) super fiat, coins, :cryptocompare end + # Repeat request to cryptocompare api + # @example Repeat request + # crypto.get + # + def get + do_rest_request + end + private def process super - uri = response[:uri] - fiat = response[:fiat] - coins = response[:coins] - coins.collect do |coin| + uri = '' + response[:coins].collect do |coin| uri += "fsyms=#{coin}&" unless uri.include? coin end - uri += "tsyms=#{fiat}" unless uri.include? fiat + uri += "tsyms=#{response[:fiat]}" + @response[:uri] = BASE_URL + uri + do_rest_request + end + + def do_rest_request time = Time.now.to_s - request = RestClient.get uri - data = JSON.parse request - process_json_data fiat, coins, data, time + rest = RestClient.get response[:uri] + data = JSON.parse rest + process_json_data data, time rescue StandardError => e success! set: false - if (@response[:counter] += 1) > MAX_RETRY + if (@counter += 1) > MAX_RETRY puts e.message - @response[:counter] = 0 + @counter = 0 else retry end end - def process_json_data(fiat, coins, data, time_at) + def process_json_data(data, time_at) + fiat = response[:fiat] prices = {} - coins.each do |coin| + response[:coins].each do |coin| value = data['RAW'][coin.upcase][fiat.upcase]['PRICE'].round(2) prices[coin] = value end diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index dd2993d..cf10485 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -9,90 +9,89 @@ require 'kolorit' require 'rest-client' -# # @see CTFC::Request # @see Ctfc # module CTFC - # - # Get data from source. + + # Initialize client to set configuration, and get data from source. # class Client include CTFC::API - attr_reader :response - attr_accessor :fiat, :coins, :prices, :source + attr_reader :response, :prices + attr_accessor :fiat, :coins, :source alias currency fiat - def initialize(curr = :eur, opts = {}) - @fiat = curr.to_s.upcase - @print = opts[:print] || true + # Choose fiat currency, coins and source for new client. + # @example Initialize new **EUR** client + # client = CTFC::Client.new :eur, coins: %w[BTC XMR LTC ETH] + # + # @param [Symbol] currency **Required**. Set fiat currency + # @param [Hash] opts Options hash for additional configuration. + # + # @option opts [Array] coins Set default coins to scrap. + # @option opts [Symbol] source Set source to scrap data. + # @option opts [Boolean] save Set option to export data to file. + # + def initialize(fiat, opts = {}) + @fiat = fiat.to_s.upcase @save = opts[:save] || true @coins = opts[:coins] || %w[BTC XMR LTC ETH] @source = opts[:source] || :cryptocompare end + # Scrap data from source. + # @example + # client.get :cryptocompare + # + # @param [Symbol] source Source to send api request + # @return [Hash] Hash of fiat values for scrapped coins + # def get(source = @source) request = send_api_request(source) # binding.pry - @response.merge! request @prices = response[:prices] Export.to_csv(source, response) if save? - # pp prices if print? + prices end - # # Get fiat value from response hash with crypto prices # @example - # @data.price(:btc) + # client.price(:btc) # - # @param [Symbol || String] coin **Required**. Coin name as symbol or string. + # @param [Symbol] coin **Required**. Coin name as symbol. # @return [Float] # def price(coin) prices[coin.to_s.upcase] end - # - # Check if crypto prices will be saved in `.csv` table + # Check if output will be saved after request. # @return [true || false] # def save? @save == true end - # - # Check if crypto prices will be printed in terminal - # - # @return [true || false] - # - def print? - @print == true - end - - # - # Change option to save '.csv' table with prices + # Change option to save output after request. # @return [true || false] # def save=(opt) @save = opt.is_a?(TrueClass) end + # Check if request was successful. # - # Change option to print prices in terminal - # @return [true || false] - # - def print=(opt) - @print = opt.is_a?(TrueClass) - end - def success? response[:success] == true end private + # Send api request based on source + # def send_api_request(source) @response = case source diff --git a/lib/ctfc/export.rb b/lib/ctfc/export.rb index 0054e2c..c6ca980 100644 --- a/lib/ctfc/export.rb +++ b/lib/ctfc/export.rb @@ -1,7 +1,14 @@ # frozen_string_literal: true +# +# Keep methods to export data to csv or json. +# JSON save complete data, while CSV extract only prices. +# module Export class << self + # + # Save crypto prices in csv table + # def to_csv(source, response = {}) table = "ctfc_#{response[:fiat]}_#{source}.csv" coins = response[:coins] @@ -10,12 +17,18 @@ def to_csv(source, response = {}) CSV.open(table, 'ab') { |column| column << data_row } end + # + # Save output as json. + # def to_json(source, response = {}) table = "ctfc_#{response[:fiat]}_#{source}.json" data = JSON.pretty_generate(response[:data]) File.write(table, data) end + # + # Save prices in csv table, and complete output as json. + # def all(*args) to_csv(*args) to_json(*args) From eaffe3e116158d9fd1c56f89b69c971705ffb793 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Tue, 15 Feb 2022 12:49:40 +0100 Subject: [PATCH 13/26] simplify source files --- lib/ctfc/api.rb | 8 +++++- lib/ctfc/api/apitemplate.rb | 27 +++++++++++++++----- lib/ctfc/api/cryptocompare.rb | 48 ++++++----------------------------- lib/ctfc/client.rb | 11 +++----- 4 files changed, 39 insertions(+), 55 deletions(-) diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index a69d9d2..d4e556d 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -6,9 +6,15 @@ require 'json' module CTFC + # Keep sources to scrap data. Each source should be class, + # named as api domain, extending ApiTemplate. + # + # @example Add new source + # class NewSource < ApiTemplate + # end + # module API class << self - # # Get list of sources from files in api dir. # def list diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 4143e9d..07a0c67 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -5,7 +5,7 @@ module API # Template for other sources. Every file in api dir should extend this class. # Automatically call method #process to send api request after initialization. # This mean every source should include #process method, that will be executed - # after calling **super** in initialize. + # after initialization. # # @see CTFC::API::Cryptocompare # @@ -17,20 +17,33 @@ class ApiTemplate # Construct response hash from given arguments, and start counting requests. # + # @example Send request to cryptocompare + # crypto = Cryptocompare.new :eur, %w[BTC XMR] + # # @param [Symbol] fiat **Required**. Fiat currency to convert coin price. # @param [Array] coins **Required**. Array of coins to scrap data for. - # @param [Symbol] source **Required**. Source to tell us which api to call. # - # @return [Object] ApiTemplate instance. + # @return [Object] Source instance. # - def initialize(fiat, coins, source) - @response = { fiat: fiat, - coins: coins, - success: false } + def initialize(fiat, coins) + @response = { fiat: fiat, coins: coins, success: false } @counter = 0 process end + # Initialize new instance, send request and return response hash. + # @example + # Cryptocompare[:eur, %w[BTC XMR]] + # + # @param [Symbol] fiat **Required**. Fiat currency. + # @param [Array] coins **Required**. Cryptocurrency coins. + # + # @return [Hash] Response hash object. + # + def self.[](fiat, coins) + new(fiat, coins).response + end + private # @todo api key support diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index c581f6b..b8aa8f3 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -5,46 +5,16 @@ module CTFC module API # Source file for cryptocompare api. + # + # Initialize will automatically call #process + # to send request after all settings are configured. + # + # @see CTFC::API::ApiTemplate + # class Cryptocompare < ApiTemplate # Cryptocompare API base url, where we add coins and fiat currency. BASE_URL = 'https://min-api.cryptocompare.com/data/pricemultifull?' - # Initialize new instance, send request and return response hash. - # @example - # Cryptocompare[:eur, %w[BTC XMR]] - # - # @param [Symbol] fiat **Required**. Fiat currency. - # @param [Array] coins **Required**. Cryptocurrency coins. - # - # @return [Hash] Response hash object. - # - def self.[](fiat, coins) - new(fiat, coins).response - end - - # Initialize will automatically call #process - # to send request after all settings are configured. - # - # @example Send request to cryptocompare - # crypto = Cryptocompare.new :eur, %w[BTC XMR] - # - # @param [Symbol] fiat **Required**. Fiat currency. - # @param [Array] coins **Required**. Cryptocurrency coins. - # - # @return [Object] Cryptocompare instance. - # - def initialize(fiat, coins) - super fiat, coins, :cryptocompare - end - - # Repeat request to cryptocompare api - # @example Repeat request - # crypto.get - # - def get - do_rest_request - end - private def process @@ -58,11 +28,9 @@ def process do_rest_request end - def do_rest_request - time = Time.now.to_s + def do_rest_request(time = Time.now.to_s) rest = RestClient.get response[:uri] - data = JSON.parse rest - process_json_data data, time + process_json_data JSON.parse(rest), time rescue StandardError => e success! set: false if (@counter += 1) > MAX_RETRY diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index cf10485..d36af73 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -6,20 +6,18 @@ require 'json' require 'csv' -require 'kolorit' require 'rest-client' # @see CTFC::Request # @see Ctfc # module CTFC - # Initialize client to set configuration, and get data from source. # class Client include CTFC::API - attr_reader :response, :prices + attr_reader :response, :prices, :save attr_accessor :fiat, :coins, :source alias currency fiat @@ -47,14 +45,13 @@ def initialize(fiat, opts = {}) # client.get :cryptocompare # # @param [Symbol] source Source to send api request - # @return [Hash] Hash of fiat values for scrapped coins + # @return [Hash] Hash of fiat values for scrapped coins # def get(source = @source) - request = send_api_request(source) + send_api_request(source) # binding.pry - @prices = response[:prices] Export.to_csv(source, response) if save? - prices + @prices = response[:prices] end # Get fiat value from response hash with crypto prices From 158b9108f37b2ec41c14e32c5d20b165e01c8ab5 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Tue, 15 Feb 2022 18:11:51 +0100 Subject: [PATCH 14/26] almost ready for v1 - passed rubocop syntax check --- .rubocop_todo.yml | 15 ++--------- ctfc.gemspec | 5 ++-- lib/ctfc.rb | 49 +++++------------------------------ lib/ctfc/api.rb | 16 +++++++++++- lib/ctfc/api/apitemplate.rb | 7 +++-- lib/ctfc/api/cryptocompare.rb | 11 ++++---- lib/ctfc/client.rb | 9 ++++--- 7 files changed, 41 insertions(+), 71 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2ec6779..e433192 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,22 +1,11 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-01-26 10:27:09 UTC using RuboCop version 1.24.1. +# on 2022-02-15 16:52:21 UTC using RuboCop version 1.25.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. -Metrics/AbcSize: - Max: 32 - -# Offense count: 1 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. -# IgnoredMethods: refine -Metrics/BlockLength: - Max: 43 - AllCops: - NewCops: enable TargetRubyVersion: '2.7' + NewCops: enable diff --git a/ctfc.gemspec b/ctfc.gemspec index 5dabefd..a74a079 100644 --- a/ctfc.gemspec +++ b/ctfc.gemspec @@ -19,6 +19,8 @@ Gem::Specification.new do |s| s.require_paths = 'lib' s.executables = 'ctfc' + s.required_ruby_version = '>= 2.7', '< 3.1' + s.metadata['homepage_uri'] = 'https://github.com/alx3dev/ctfc' s.metadata['source_code_uri'] = 'https://github.com/alx3dev/ctfc' s.metadata['bug_tracker_uri'] = 'https://github.com/alx3dev/ctfc/issues' @@ -38,9 +40,6 @@ Gem::Specification.new do |s| README.md ctfc.gemspec] - s.required_ruby_version = '> 2.7', '< 3.2' - - s.add_runtime_dependency 'optimist', '~> 3.0.1' s.add_runtime_dependency 'rest-client', '~> 2.1.0' s.add_development_dependency 'bundler', '~> 2.3' diff --git a/lib/ctfc.rb b/lib/ctfc.rb index 7566f30..3e8433d 100644 --- a/lib/ctfc.rb +++ b/lib/ctfc.rb @@ -2,47 +2,10 @@ require_relative 'ctfc/client' -## -# For easier job use Ctfc, instead of typing CTFC::Data. -# You can define default coins with Ctfc::COINS= -# -# @note For instance methods look at CTFC::Data. -# -class Ctfc < CTFC::Client - ## - # @todo Allow Ctfc to use proxy and/or tor - # - def initialize(currency = :eur, opts = {}) - super(currency, opts) - end +# Ctfc is shortcut for CTFC::Client. +# @see CTFC::Client +class Ctfc < CTFC::Client; end - ## - # @example Get EUR data for BTC, XMR, LTC, ETH, print but don't save output - # - # Ctfc.to :eur, save: false, coins: %w[BTC XMR LTC ETH] - # - # @param [Symbol] currency **Required**. Define fiat currency. - # @param [Hash] opts **Optional**. Additional options hash. - # - # @option opts [Boolean] print **Optional**. Print terminal output. - # @option opts [Boolean] save **Optional**. Save `.csv` output. - # @option opts [Array] coins **Optional**. Define coins to scrap. - # - # @return [Hash] CTFC::Data#prices || CTFC::Data#response - # - def self.to(currency, opts = {}) - new(currency.to_sym, opts).get - end -end - -## -# Same as Ctfc -# @see Ctfc -# @see CTFC::Data -# -class Crypto < Ctfc - def initialize(currency = :eur, opts = {}) - opts[:coins] ||= COINS - super(currency, opts) - end -end +# Crypto is shortcut for CTFC::Client. +# @see CTFC::Client +class Crypto < CTFC::Client; end diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index d4e556d..21e47ff 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -6,16 +6,29 @@ require 'json' module CTFC + # # Keep sources to scrap data. Each source should be class, # named as api domain, extending ApiTemplate. # + # @see CTFC::API::ApiTemplate + # @see CTFC::API::Cryptocompare + # # @example Add new source # class NewSource < ApiTemplate + # + # private + # + # def process + # super + # # write method to scrap data from NewSource + # end # end # module API class << self + # # Get list of sources from files in api dir. + # @return [Array] Array of symbols # def list @list ||= list_files_in_api_dir @@ -37,7 +50,8 @@ def list_files_in_api_dir end end end - +# # automatically require new apis # to-do: change #to_s to #name for ruby3 +# CTFC::API.list.select { |source| require_relative "api/#{source}" } diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index 07a0c67..b3132af 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -46,8 +46,11 @@ def self.[](fiat, coins) private - # @todo api key support - # @todo proxy support + # Method to scrap data from source. Call **super** in source file + # to check for currency and coins in response hash. + # + # @todo add api-key and proxy support + # def process return false unless response[:fiat] && response[:coins] end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index b8aa8f3..46aac43 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -5,8 +5,7 @@ module CTFC module API # Source file for cryptocompare api. - # - # Initialize will automatically call #process + # Initialize will automatically call #process, # to send request after all settings are configured. # # @see CTFC::API::ApiTemplate @@ -17,6 +16,8 @@ class Cryptocompare < ApiTemplate private + # Send API request. Automatically called on initialization, + # to scrap data from source. def process super uri = '' @@ -32,7 +33,7 @@ def do_rest_request(time = Time.now.to_s) rest = RestClient.get response[:uri] process_json_data JSON.parse(rest), time rescue StandardError => e - success! set: false + request_fail! if (@counter += 1) > MAX_RETRY puts e.message @counter = 0 @@ -48,8 +49,8 @@ def process_json_data(data, time_at) value = data['RAW'][coin.upcase][fiat.upcase]['PRICE'].round(2) prices[coin] = value end - success! - @response.merge! time_at: time_at, data: data, prices: prices + @response.merge!(time_at: time_at, data: data, + prices: prices, success: true) end end end diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index d36af73..76447c0 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -8,10 +8,8 @@ require 'csv' require 'rest-client' -# @see CTFC::Request -# @see Ctfc -# module CTFC + # # Initialize client to set configuration, and get data from source. # class Client @@ -22,6 +20,10 @@ class Client alias currency fiat + def self.to(*args) + new(*args).get + end + # Choose fiat currency, coins and source for new client. # @example Initialize new **EUR** client # client = CTFC::Client.new :eur, coins: %w[BTC XMR LTC ETH] @@ -49,7 +51,6 @@ def initialize(fiat, opts = {}) # def get(source = @source) send_api_request(source) - # binding.pry Export.to_csv(source, response) if save? @prices = response[:prices] end From 3e2ff5c70c024b1b01e3cdd151045f6420991f2d Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 16 Feb 2022 00:42:20 +0100 Subject: [PATCH 15/26] rename changelog to old --- changelog-dev-old.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 changelog-dev-old.md diff --git a/changelog-dev-old.md b/changelog-dev-old.md new file mode 100644 index 0000000..1301166 --- /dev/null +++ b/changelog-dev-old.md @@ -0,0 +1,34 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +## [Unreleased] + + - Prepare `version-0.4.1` to be ready for Open Source under MIT license. + - Removed gem `colorize` because of GPLv2. + - Override String class with color codes for Linux. + +## [0.4.0] - 2022-01-24 + + - Add options `--loop` and `--wait` for terminal scripting + - Only signed and verified commits from `v-0.4.0` + - PGP Public key available at https://www.github.com/alx3dev/contact + - Before `v-0.4.0` unverified commits were pushed. + - From now on, only VERIFIED signed commits should be trusted + +-----BEGIN PGP SIGNATURE----- + +iQIzBAEBCgAdFiEEtl+v9iZj5AyyvRYdTFhE2vUGMucFAmHvxM0ACgkQTFhE2vUG +MuejCQ//Uw+xaFBNrat3evChlifrxAckEqhtQeUwiuluSkReYmV+TqdSNqhzzezh +BJkgp5d7/Lzt/7wCwK9aVuvejKG0op4Ernk1jRH7jdOkH7TeqVyRe8837XXbn8Q3 +FRh5ih/dbbeQtvIVuI1Z7unRQGvlfMnBKK65wQfHRffJg3kTgO4ICx0Gz1LgxfCa +OwjSYp2+pXQtGOI+ab9/a7avBb6TIW5uQjegZ5LsVR/at3RySki8v1fOA0Tfmsgv +pZ4aKhZkpVhZ2l6fj181hBwBspmJPxpB8v4mZXhXAfe9z/425favlfyKLxRvmFu5 +aQK6hZ/gTEXz2s0ypVVPtwQS5FK3YurhKeun6Vto1lQJM10MngwB53B18eNSAB3h +tsFks8QojwtPH2nttksWOKy5V5tQdBB0uCd72DdEwnAOo1jyMhG7Otkpt4ch5CAd +xRhmrC0Pp1GgqH2avpeezA3uQZ2HW2Gp1nFnKHeRhPutEBF8x8NLjCxbS3GExrgk ++Lijy13Eu/y4f2S7TIfN/LMB+ruqQrZe+NmzE8J1tuMcaDqIYsV17c5FXvaxGS/q +9mJ++v6xHfMykvCNR/FtuUxq+TaD/aNBm+pKYhjqzr4jmoAy2h1gvQEIl4OMXxlf +5xzgdP6ePWkdwWnYvMtrIbkVWfKvfaBUgYsT/+FRVDierWq2aj0= +=/ZSG +-----END PGP SIGNATURE----- + From 51d65fed6ba1d8cae2cc8d71d75a09e22b4662be Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 16 Feb 2022 00:42:52 +0100 Subject: [PATCH 16/26] remove changelog --- CHANGELOG.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1301166..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -## [Unreleased] - - - Prepare `version-0.4.1` to be ready for Open Source under MIT license. - - Removed gem `colorize` because of GPLv2. - - Override String class with color codes for Linux. - -## [0.4.0] - 2022-01-24 - - - Add options `--loop` and `--wait` for terminal scripting - - Only signed and verified commits from `v-0.4.0` - - PGP Public key available at https://www.github.com/alx3dev/contact - - Before `v-0.4.0` unverified commits were pushed. - - From now on, only VERIFIED signed commits should be trusted - ------BEGIN PGP SIGNATURE----- - -iQIzBAEBCgAdFiEEtl+v9iZj5AyyvRYdTFhE2vUGMucFAmHvxM0ACgkQTFhE2vUG -MuejCQ//Uw+xaFBNrat3evChlifrxAckEqhtQeUwiuluSkReYmV+TqdSNqhzzezh -BJkgp5d7/Lzt/7wCwK9aVuvejKG0op4Ernk1jRH7jdOkH7TeqVyRe8837XXbn8Q3 -FRh5ih/dbbeQtvIVuI1Z7unRQGvlfMnBKK65wQfHRffJg3kTgO4ICx0Gz1LgxfCa -OwjSYp2+pXQtGOI+ab9/a7avBb6TIW5uQjegZ5LsVR/at3RySki8v1fOA0Tfmsgv -pZ4aKhZkpVhZ2l6fj181hBwBspmJPxpB8v4mZXhXAfe9z/425favlfyKLxRvmFu5 -aQK6hZ/gTEXz2s0ypVVPtwQS5FK3YurhKeun6Vto1lQJM10MngwB53B18eNSAB3h -tsFks8QojwtPH2nttksWOKy5V5tQdBB0uCd72DdEwnAOo1jyMhG7Otkpt4ch5CAd -xRhmrC0Pp1GgqH2avpeezA3uQZ2HW2Gp1nFnKHeRhPutEBF8x8NLjCxbS3GExrgk -+Lijy13Eu/y4f2S7TIfN/LMB+ruqQrZe+NmzE8J1tuMcaDqIYsV17c5FXvaxGS/q -9mJ++v6xHfMykvCNR/FtuUxq+TaD/aNBm+pKYhjqzr4jmoAy2h1gvQEIl4OMXxlf -5xzgdP6ePWkdwWnYvMtrIbkVWfKvfaBUgYsT/+FRVDierWq2aj0= -=/ZSG ------END PGP SIGNATURE----- - From 099b47b0e743797d7c6503d438676a2ae8af3f0c Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 16 Feb 2022 00:43:40 +0100 Subject: [PATCH 17/26] pass rubocop inspection --- .rubocop_todo.yml | 11 ----------- check-syntax.sh | 17 ----------------- 2 files changed, 28 deletions(-) delete mode 100644 .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index e433192..0000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,11 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config` -# on 2022-02-15 16:52:21 UTC using RuboCop version 1.25.0. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. - -AllCops: - TargetRubyVersion: '2.7' - NewCops: enable diff --git a/check-syntax.sh b/check-syntax.sh index 65b8e3b..68f715b 100755 --- a/check-syntax.sh +++ b/check-syntax.sh @@ -1,20 +1,3 @@ #!/usr/bin/env bash -## -# Inspect source code with rubocop. Do not change .rubocop_todo.yml. -# -# To check your changes in source code, run: -# ./check-syntax.sh -# -# To inspect all errors that should be fixed run: -# ./check-syntax.sh --all -# -# This will return all errors hidden by todo file. -# Errors should be fixed, then manualy removed from .rubocop_todo.yml -## - -if [ "$1" == "-a" ] || [ "$1" == "--all" ] || [ "$1" == "--total" ]; then rubocop --format simple --config .rubocop.yml -else -rubocop --format simple --config .rubocop_todo.yml -fi From c81f788e658827c3ebaba2319a9bb4ce24f56a85 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 16 Feb 2022 01:31:53 +0100 Subject: [PATCH 18/26] edit to work for development --- bin/ctfc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bin/ctfc b/bin/ctfc index 7876f94..acf9ec9 100755 --- a/bin/ctfc +++ b/bin/ctfc @@ -3,9 +3,10 @@ require_relative '../lib/ctfc' require 'optimist' +require 'kolorit' opts = Optimist.options do - version "Crypto To Fiat Currency\n".cyan.bold \ + version "Crypto To Fiat Currency\n" \ + "Gem Version: #{CTFC::VERSION}" banner '' @@ -26,20 +27,20 @@ coins = opts[:coins] save = opts[:no_save] ? false : true print = opts[:no_print] ? false : true -@crypto = Ctfc.new - if ARGV.empty? # default behavior without arguments - change to suit your needs - crypto = CTFC::Client.new(:eur, save: true, print: true, coins: coins) + crypto = CTFC::Client.new(:eur, coins, :cryptocompare, save: true, print: true) crypto.get + crypto.prices.each { |x, y| puts "#{x}: #{y} eur".cyan.bold } if print else opts[:loop].times do ARGV.each do |fiat| next if opts.include?(fiat.downcase) - crypto = CTFC::Client.new(fiat, save: save, print: print, coins: coins) + crypto = CTFC::Client.new(fiat, coins, :cryptocompare, save: save, print: print) crypto.get + crypto.prices.each { |x, y| puts "#{x}: #{y} #{fiat}".cyan.bold } if print sleep 0.5 end From f105bf6cedaac01ceba66f1d3748e2215b0dba59 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 16 Feb 2022 01:40:43 +0100 Subject: [PATCH 19/26] update gemspec --- ctfc.gemspec | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ctfc.gemspec b/ctfc.gemspec index a74a079..4d2482f 100644 --- a/ctfc.gemspec +++ b/ctfc.gemspec @@ -5,10 +5,11 @@ require_relative './lib/ctfc/version' Gem::Specification.new do |s| s.name = 'ctfc' s.version = CTFC::VERSION - s.summary = 'Cryptocurrency to Fiat values, get data and save prices.' + s.summary = 'Cryptocurrency data gathering gem. Scrap and save as CSV and/or JSON.' s.description = <<~DESCRIPTION - Convert any cryptocurrency to any fiat value, export data to csv table. - Print colorized terminal output. + Cryptocurrency data gathering gem. Get data from multiple APIs, print and + save output as you wish. Run script from terminal, or use in another app. + Class-template based, easy to extend to add more sources. MIT License. DESCRIPTION s.license = 'MIT' @@ -19,8 +20,6 @@ Gem::Specification.new do |s| s.require_paths = 'lib' s.executables = 'ctfc' - s.required_ruby_version = '>= 2.7', '< 3.1' - s.metadata['homepage_uri'] = 'https://github.com/alx3dev/ctfc' s.metadata['source_code_uri'] = 'https://github.com/alx3dev/ctfc' s.metadata['bug_tracker_uri'] = 'https://github.com/alx3dev/ctfc/issues' @@ -40,6 +39,10 @@ Gem::Specification.new do |s| README.md ctfc.gemspec] + s.required_ruby_version = '> 2.7', '< 3.2' + + s.add_runtime_dependency 'optimist', '~> 3.0.1' + s.add_runtime_dependency 'kolorit', '~> 0.2' s.add_runtime_dependency 'rest-client', '~> 2.1.0' s.add_development_dependency 'bundler', '~> 2.3' From f91a7367e03d080e3c1af026ee00cb1c169d22d4 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 16 Feb 2022 01:58:35 +0100 Subject: [PATCH 20/26] add comments --- lib/ctfc/api.rb | 9 +-- lib/ctfc/api/apitemplate.rb | 6 +- lib/ctfc/api/cryptocompare.rb | 15 ++--- lib/ctfc/client.rb | 101 ++++++++++++++++++++++------------ lib/ctfc/export.rb | 25 +++------ lib/ctfc/version.rb | 2 +- 6 files changed, 86 insertions(+), 72 deletions(-) diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index 21e47ff..c0b2f72 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -7,18 +7,19 @@ module CTFC # - # Keep sources to scrap data. Each source should be class, - # named as api domain, extending ApiTemplate. + # Keep sources to extract data. Each source shall be class, + # named as API domain, extending ApiTemplate. # # @see CTFC::API::ApiTemplate # @see CTFC::API::Cryptocompare # - # @example Add new source + # @example Add a new source to extract data: # class NewSource < ApiTemplate # # private # # def process + # # check response hash for existence of fiat and coins # super # # write method to scrap data from NewSource # end @@ -28,7 +29,7 @@ module API class << self # # Get list of sources from files in api dir. - # @return [Array] Array of symbols + # @return [Array] Array of symbols. # def list @list ||= list_files_in_api_dir diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index b3132af..df3c274 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -16,6 +16,7 @@ class ApiTemplate MAX_RETRY = 3 # Construct response hash from given arguments, and start counting requests. + # Call private method #process to extract data from web. # # @example Send request to cryptocompare # crypto = Cryptocompare.new :eur, %w[BTC XMR] @@ -46,11 +47,6 @@ def self.[](fiat, coins) private - # Method to scrap data from source. Call **super** in source file - # to check for currency and coins in response hash. - # - # @todo add api-key and proxy support - # def process return false unless response[:fiat] && response[:coins] end diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index 46aac43..f614f4b 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -16,8 +16,6 @@ class Cryptocompare < ApiTemplate private - # Send API request. Automatically called on initialization, - # to scrap data from source. def process super uri = '' @@ -29,28 +27,27 @@ def process do_rest_request end - def do_rest_request(time = Time.now.to_s) + def do_rest_request(time = Time.now) rest = RestClient.get response[:uri] + success! if rest.code == 200 process_json_data JSON.parse(rest), time rescue StandardError => e - request_fail! + success! set: false if (@counter += 1) > MAX_RETRY puts e.message - @counter = 0 else retry end end - def process_json_data(data, time_at) + def process_json_data(data, time) fiat = response[:fiat] prices = {} response[:coins].each do |coin| - value = data['RAW'][coin.upcase][fiat.upcase]['PRICE'].round(2) + value = data['RAW'][coin.upcase][fiat.to_s.upcase]['PRICE'].round(2) prices[coin] = value end - @response.merge!(time_at: time_at, data: data, - prices: prices, success: true) + @response.merge!(time: time.to_s, prices: prices, data: data) end end end diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index 76447c0..574ff53 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -15,10 +15,7 @@ module CTFC class Client include CTFC::API - attr_reader :response, :prices, :save - attr_accessor :fiat, :coins, :source - - alias currency fiat + attr_reader :config, :response, :prices def self.to(*args) new(*args).get @@ -26,20 +23,25 @@ def self.to(*args) # Choose fiat currency, coins and source for new client. # @example Initialize new **EUR** client - # client = CTFC::Client.new :eur, coins: %w[BTC XMR LTC ETH] + # client = CTFC::Client.new :eur, %w[BTC XMR LTC ETH] # # @param [Symbol] currency **Required**. Set fiat currency + # @param [Array] coins **Required**. Set crypto coins. + # @param [Symbol] source Optional. Source for data extraction. # @param [Hash] opts Options hash for additional configuration. # - # @option opts [Array] coins Set default coins to scrap. - # @option opts [Symbol] source Set source to scrap data. + # @option opts [Symbol] source Set source to extract data. # @option opts [Boolean] save Set option to export data to file. # - def initialize(fiat, opts = {}) - @fiat = fiat.to_s.upcase - @save = opts[:save] || true - @coins = opts[:coins] || %w[BTC XMR LTC ETH] - @source = opts[:source] || :cryptocompare + # @return [Client] Client instance. + # + def initialize(fiat, coins, source = nil, opts = {}) + @config = { + fiat: fiat, + coins: coins, + source: source || opts[:source], + save: [nil, true].include? opts[:save] + } end # Scrap data from source. @@ -49,35 +51,65 @@ def initialize(fiat, opts = {}) # @param [Symbol] source Source to send api request # @return [Hash] Hash of fiat values for scrapped coins # - def get(source = @source) + def get(source = nil) + source ||= config[:source] send_api_request(source) Export.to_csv(source, response) if save? + Export.to_json(source, response) if export? @prices = response[:prices] end - # Get fiat value from response hash with crypto prices - # @example - # client.price(:btc) + # Source for data extraction. + # @return [Symbol] # - # @param [Symbol] coin **Required**. Coin name as symbol. - # @return [Float] + def source + config[:source] + end + + # Set source for data extraction. + # @return [Symbol] # - def price(coin) - prices[coin.to_s.upcase] + def source=(param) + @config[:source] = param end - # Check if output will be saved after request. + # Check if csv output will be saved after request. # @return [true || false] # def save? - @save == true + config[:save] == true end - # Change option to save output after request. + # Change option to save csv output after request. # @return [true || false] # def save=(opt) - @save = opt.is_a?(TrueClass) + @config[:save] = opt.is_a?(TrueClass) + end + + # Check if json output will be exported after request. + # @return [true || false] + # + def export? + config[:export] == true + end + + # Change option to save csv output after request. + # @return [true || false] + # + def export=(opt) + @config[:export] = opt.is_a?(TrueClass) + end + + # Get fiat value from response hash with crypto prices + # @example + # client.price(:btc) + # + # @param [Symbol] coin **Required**. Coin name as symbol. + # @return [Float] + # + def price(coin) + prices[coin.to_s.upcase] end # Check if request was successful. @@ -88,19 +120,16 @@ def success? private - # Send api request based on source - # def send_api_request(source) - @response = - case source - when :cryptocompare - Cryptocompare[fiat, coins] - when :binance - # Binance[fiat, coins] - raise NoMethodError, 'Working on Binance implementation' - else - raise NoMethodError, 'Not implemented, yet! Feel free to contribute!' - end + fiat = config[:fiat] + coins = config[:coins] + @response = case source + when :cryptocompare + Cryptocompare[fiat, coins] + else + raise NoMethodError, 'Not implemented, yet! ' \ + 'Feel free to contribute!' + end end end end diff --git a/lib/ctfc/export.rb b/lib/ctfc/export.rb index c6ca980..14f986b 100644 --- a/lib/ctfc/export.rb +++ b/lib/ctfc/export.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -# -# Keep methods to export data to csv or json. -# JSON save complete data, while CSV extract only prices. +# Keep methods to export data as csv or json. +# JSON extract all data, while CSV only save prices. # module Export class << self # - # Save crypto prices in csv table + # Save crypto prices in csv table. # def to_csv(source, response = {}) table = "ctfc_#{response[:fiat]}_#{source}.csv" @@ -17,21 +16,13 @@ def to_csv(source, response = {}) CSV.open(table, 'ab') { |column| column << data_row } end - # - # Save output as json. + # Extract all data in json file. # def to_json(source, response = {}) table = "ctfc_#{response[:fiat]}_#{source}.json" - data = JSON.pretty_generate(response[:data]) - File.write(table, data) - end - - # - # Save prices in csv table, and complete output as json. - # - def all(*args) - to_csv(*args) - to_json(*args) + File.open(table, 'ab') do |append| + append.puts JSON.pretty_generate response + end end private @@ -43,7 +34,7 @@ def create_csv_headers(table, coins) end def price_array_from(response = {}) - price_array = [response[:time_at]] + price_array = [response[:time]] response[:prices].each do |_coin, price| price_array << price end diff --git a/lib/ctfc/version.rb b/lib/ctfc/version.rb index 3f89e39..6db5c58 100644 --- a/lib/ctfc/version.rb +++ b/lib/ctfc/version.rb @@ -2,5 +2,5 @@ module CTFC # gem version - VERSION = '0.4.2' + VERSION = '1-dev' end From cb7ceb50e3ae03c1654d5db3f8c3ed3e2b90f732 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 16 Feb 2022 02:08:43 +0100 Subject: [PATCH 21/26] typo fix --- lib/ctfc/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index 574ff53..c7fd1e6 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -40,7 +40,7 @@ def initialize(fiat, coins, source = nil, opts = {}) fiat: fiat, coins: coins, source: source || opts[:source], - save: [nil, true].include? opts[:save] + save: [nil, true].include?(opts[:save]) } end From dc59bdd5e264ffa0f3f8245eee2c88857667bd38 Mon Sep 17 00:00:00 2001 From: alx3dev Date: Wed, 16 Feb 2022 21:05:40 +0100 Subject: [PATCH 22/26] version 1.0.0-dev --- .rubocop_todo.yml | 28 ++++++++++++++ CHANGELOG.md | 16 ++++++++ bin/ctfc | 15 +++++--- check-syntax.sh | 2 +- ctfc.gemspec | 13 +++++-- lib/ctfc/api.rb | 41 ++++----------------- lib/ctfc/api/apitemplate.rb | 6 ++- lib/ctfc/api/cryptocompare.rb | 6 +-- lib/ctfc/client.rb | 69 ++++++++++++++++++++--------------- lib/ctfc/export.rb | 4 +- lib/ctfc/version.rb | 36 ++++++++++++++++++ 11 files changed, 157 insertions(+), 79 deletions(-) create mode 100644 .rubocop_todo.yml create mode 100644 CHANGELOG.md diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..8ef6615 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,28 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2022-02-16 19:05:21 UTC using RuboCop version 1.25.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +AllCops: + TargetRubyVersion: '2.7' + NewCops: enable + +# Offense count: 1 +Security/Eval: + Exclude: + - 'lib/ctfc/client.rb' + +# Offense count: 1 +# Configuration parameters: AllowedConstants. +Style/Documentation: + Exclude: + - 'lib/ctfc/version.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/EvalWithLocation: + Exclude: + - 'lib/ctfc/client.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0e4741d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +## [1.0.0-dev] + +Testing changes before merge. + +**About:** + + - Allow multiple sources + - Class-Template based - easy to add more APIs + - New sources are automatically required and added to `.gemspec` + - Save prices in `.csv` table + - Extract all data in `.json` file + +**Security:** + + - `CTFC::Client` use `eval` - to call source class to extract data. Only source +string is evaluated, but we check for persistence of source class before it. diff --git a/bin/ctfc b/bin/ctfc index acf9ec9..108e176 100755 --- a/bin/ctfc +++ b/bin/ctfc @@ -6,8 +6,7 @@ require 'optimist' require 'kolorit' opts = Optimist.options do - version "Crypto To Fiat Currency\n" \ - + "Gem Version: #{CTFC::VERSION}" + version "Crypto To Fiat Currency\nGem Version: #{CTFC::VERSION}" banner '' banner ' Enter fiat currencies with/out additional arguments:' @@ -17,20 +16,22 @@ opts = Optimist.options do banner '' opt :coins, 'Set crypto coins', default: %w[BTC XMR ETH LTC] - opt :no_save, "Do not save '.csv' output" + opt :save, "Save prices in '.csv' table" + opt :export, "Export all data in '.json' file" opt :no_print, 'Do not print terminal output' opt :loop, 'Run script N times', default: 1, type: :integer opt :wait, 'Wait N seconds between loop', default: 0, type: :integer end coins = opts[:coins] -save = opts[:no_save] ? false : true +save = opts[:save] +export = opts[:export] print = opts[:no_print] ? false : true if ARGV.empty? # default behavior without arguments - change to suit your needs - crypto = CTFC::Client.new(:eur, coins, :cryptocompare, save: true, print: true) + crypto = CTFC::Client.new(:eur, coins, :cryptocompare, save: false) crypto.get crypto.prices.each { |x, y| puts "#{x}: #{y} eur".cyan.bold } if print else @@ -38,7 +39,9 @@ else ARGV.each do |fiat| next if opts.include?(fiat.downcase) - crypto = CTFC::Client.new(fiat, coins, :cryptocompare, save: save, print: print) + crypto = CTFC::Client.new(fiat, coins, :cryptocompare, + save: save, export: export, print: print) + crypto.get crypto.prices.each { |x, y| puts "#{x}: #{y} #{fiat}".cyan.bold } if print sleep 0.5 diff --git a/check-syntax.sh b/check-syntax.sh index 68f715b..0e2da47 100755 --- a/check-syntax.sh +++ b/check-syntax.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -rubocop --format simple --config .rubocop.yml +rubocop --format simple --config .rubocop_todo.yml diff --git a/ctfc.gemspec b/ctfc.gemspec index 4d2482f..9f471c5 100644 --- a/ctfc.gemspec +++ b/ctfc.gemspec @@ -29,20 +29,25 @@ Gem::Specification.new do |s| s.metadata['rubygems_mfa_required'] = 'true' s.files = %w[ lib/ctfc.rb - lib/ctfc/version.rb - lib/ctfc/export.rb lib/ctfc/client.rb + lib/ctfc/export.rb + lib/ctfc/version.rb lib/ctfc/api.rb lib/ctfc/api/apitemplate.rb - lib/ctfc/api/cryptocompare.rb LICENSE README.md ctfc.gemspec] + # auto-add sources in api dir + CTFC::API.list_of_sources.select do |source| + file = "lib/ctfc/api/#{source}" + s.files += [file] + end + s.required_ruby_version = '> 2.7', '< 3.2' - s.add_runtime_dependency 'optimist', '~> 3.0.1' s.add_runtime_dependency 'kolorit', '~> 0.2' + s.add_runtime_dependency 'optimist', '~> 3.0.1' s.add_runtime_dependency 'rest-client', '~> 2.1.0' s.add_development_dependency 'bundler', '~> 2.3' diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index c0b2f72..ca76752 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -1,19 +1,22 @@ # frozen_string_literal: true -require_relative 'api/apitemplate' +require_relative 'version' unless defined? CTFC::VERSION +require_relative 'api/apitemplate' unless defined? CTFC::API::ApiTemplate -require 'rest-client' -require 'json' +# automatically require new apis +CTFC::API.list.select { |source| require_relative "api/#{source}" } module CTFC # - # Keep sources to extract data. Each source shall be class, - # named as API domain, extending ApiTemplate. + # Keep sources to extract data. Each source has to be a class, + # named as API domain, extending ApiTemplate. This will automatically + # make it available in Client, but also added to .gemspec. # # @see CTFC::API::ApiTemplate # @see CTFC::API::Cryptocompare # # @example Add a new source to extract data: + # # make file new_source.rb # class NewSource < ApiTemplate # # private @@ -26,33 +29,5 @@ module CTFC # end # module API - class << self - # - # Get list of sources from files in api dir. - # @return [Array] Array of symbols. - # - def list - @list ||= list_files_in_api_dir - end - - private - - def list_files_in_api_dir - sources = [] - skip = %w[. .. apitemplate.rb] - path = File.expand_path(__FILE__).gsub!('.rb', '') - Dir.entries(path).select do |source| - next if skip.include? source - - sources << source.gsub('.rb', '').to_sym - end - sources - end - end end end -# -# automatically require new apis -# to-do: change #to_s to #name for ruby3 -# -CTFC::API.list.select { |source| require_relative "api/#{source}" } diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index df3c274..c48575c 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -1,12 +1,16 @@ # frozen_string_literal: true +require 'rest-client' +require 'json' + module CTFC module API # Template for other sources. Every file in api dir should extend this class. # Automatically call method #process to send api request after initialization. - # This mean every source should include #process method, that will be executed + # This mean every source should include method #process, that will be executed # after initialization. # + # @see CTFC::API # @see CTFC::API::Cryptocompare # class ApiTemplate diff --git a/lib/ctfc/api/cryptocompare.rb b/lib/ctfc/api/cryptocompare.rb index f614f4b..f49d5f5 100644 --- a/lib/ctfc/api/cryptocompare.rb +++ b/lib/ctfc/api/cryptocompare.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require_relative 'apitemplate' +require_relative 'apitemplate' unless defined? CTFC::API::ApiTemplate module CTFC module API # Source file for cryptocompare api. # Initialize will automatically call #process, - # to send request after all settings are configured. + # to send request after all attributes and variables are configured. # # @see CTFC::API::ApiTemplate # @@ -28,7 +28,7 @@ def process end def do_rest_request(time = Time.now) - rest = RestClient.get response[:uri] + rest = RestClient.get(response[:uri]) success! if rest.code == 200 process_json_data JSON.parse(rest), time rescue StandardError => e diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index c7fd1e6..f1461ed 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -2,19 +2,13 @@ require_relative 'export' require_relative 'api' -require_relative 'version' - -require 'json' -require 'csv' -require 'rest-client' module CTFC # # Initialize client to set configuration, and get data from source. + # Client allow us to get data from any source, and to manipulate with data. # class Client - include CTFC::API - attr_reader :config, :response, :prices def self.to(*args) @@ -25,13 +19,14 @@ def self.to(*args) # @example Initialize new **EUR** client # client = CTFC::Client.new :eur, %w[BTC XMR LTC ETH] # - # @param [Symbol] currency **Required**. Set fiat currency + # @param [Symbol] currency **Required**. Set fiat currency. # @param [Array] coins **Required**. Set crypto coins. # @param [Symbol] source Optional. Source for data extraction. # @param [Hash] opts Options hash for additional configuration. # # @option opts [Symbol] source Set source to extract data. - # @option opts [Boolean] save Set option to export data to file. + # @option opts [Boolean] save Set option to save prices in csv table. + # @option opts [Boolean] export Set option to export all data in json file. # # @return [Client] Client instance. # @@ -40,7 +35,8 @@ def initialize(fiat, coins, source = nil, opts = {}) fiat: fiat, coins: coins, source: source || opts[:source], - save: [nil, true].include?(opts[:save]) + save: [nil, true].include?(opts[:save]), + export: opts[:export].is_a?(TrueClass) } end @@ -54,8 +50,10 @@ def initialize(fiat, coins, source = nil, opts = {}) def get(source = nil) source ||= config[:source] send_api_request(source) - Export.to_csv(source, response) if save? - Export.to_json(source, response) if export? + if success? + Export.to_csv(source, response) if save? + Export.to_json(source, response) if export? + end @prices = response[:prices] end @@ -74,28 +72,28 @@ def source=(param) end # Check if csv output will be saved after request. - # @return [true || false] + # @return [Boolean] # def save? - config[:save] == true + config[:save].is_a?(TrueClass) end - # Change option to save csv output after request. - # @return [true || false] + # Change option to save prices in csv table after request. + # @return [Boolean] # def save=(opt) @config[:save] = opt.is_a?(TrueClass) end # Check if json output will be exported after request. - # @return [true || false] + # @return [Boolean] # def export? - config[:export] == true + config[:export].is_a?(TrueClass) end - # Change option to save csv output after request. - # @return [true || false] + # Change option to export all data in json file after request. + # @return [Boolean] # def export=(opt) @config[:export] = opt.is_a?(TrueClass) @@ -115,21 +113,32 @@ def price(coin) # Check if request was successful. # def success? - response[:success] == true + return false if response.nil? + + response[:success].is_a?(TrueClass) end private def send_api_request(source) - fiat = config[:fiat] - coins = config[:coins] - @response = case source - when :cryptocompare - Cryptocompare[fiat, coins] - else - raise NoMethodError, 'Not implemented, yet! ' \ - 'Feel free to contribute!' - end + # automatically add new sources to the client, but be careful with eval. + if API.list.include? source + klass = check_source_name source + @response = eval "CTFC::API::#{klass}[config[:fiat], config[:coins]]"\ + '# CTFC::API::Crypto_Exchange[fiat, coins]' + else + message = 'Add source to extract data' if source.nil? + message = "#{source} not included in API list" if source + raise ArgumentError, message + end + end + + def check_source_name(source) + if source.to_s.include? '_' + source.split('_').select(&:capitalize!).join + else + source.to_s.capitalize + end end end end diff --git a/lib/ctfc/export.rb b/lib/ctfc/export.rb index 14f986b..39f3003 100644 --- a/lib/ctfc/export.rb +++ b/lib/ctfc/export.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true +require 'csv' + # Keep methods to export data as csv or json. -# JSON extract all data, while CSV only save prices. +# JSON extract all data, while CSV only prices. # module Export class << self diff --git a/lib/ctfc/version.rb b/lib/ctfc/version.rb index 6db5c58..de2b2de 100644 --- a/lib/ctfc/version.rb +++ b/lib/ctfc/version.rb @@ -3,4 +3,40 @@ module CTFC # gem version VERSION = '1-dev' + + module API + class << self + # + # List sources as Array of Symbols. + # @return [Array] Array of sources as :symbols + # + def list + @list ||= list_files_in_api_dir + end + + # Get list of sources from files in api dir. + # For use in .gemspec, to allow automated require. + # + # @return [Array] Array of sources as strings. + # + def list_of_sources + @list_of_sources ||= list_files_in_api_dir(gemspec: true) + end + + private + + def list_files_in_api_dir(gemspec: false) + sources = [] + skip = %w[. .. apitemplate.rb] + path = File.expand_path(__FILE__).gsub!('version.rb', 'api') + Dir.entries(path).select do |source| + next if skip.include? source + + source = source.gsub('.rb', '').to_sym unless gemspec == true + sources << source + end + sources + end + end + end end From 07cdc28c9293da94a5a05c85dbc09df67198fa7f Mon Sep 17 00:00:00 2001 From: alx3dev Date: Fri, 18 Feb 2022 00:00:51 +0100 Subject: [PATCH 23/26] add cli helper for colorized output --- .rubocop_todo.yml | 12 +-------- CHANGELOG.md | 1 + bin/ctfc | 47 +++++++++++++++------------------ lib/ctfc/api.rb | 2 +- lib/ctfc/api/apitemplate.rb | 4 +-- lib/ctfc/client.rb | 13 +++++++--- lib/ctfc/helpers/cli.rb | 52 +++++++++++++++++++++++++++++++++++++ lib/ctfc/version.rb | 5 ++-- 8 files changed, 89 insertions(+), 47 deletions(-) create mode 100644 lib/ctfc/helpers/cli.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8ef6615..f1a2375 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-02-16 19:05:21 UTC using RuboCop version 1.25.0. +# on 2022-02-17 14:20:49 UTC using RuboCop version 1.25.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -10,19 +10,9 @@ AllCops: TargetRubyVersion: '2.7' NewCops: enable -# Offense count: 1 -Security/Eval: - Exclude: - - 'lib/ctfc/client.rb' # Offense count: 1 # Configuration parameters: AllowedConstants. Style/Documentation: Exclude: - 'lib/ctfc/version.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/EvalWithLocation: - Exclude: - - 'lib/ctfc/client.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4741d..ddc5a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Testing changes before merge. - New sources are automatically required and added to `.gemspec` - Save prices in `.csv` table - Extract all data in `.json` file + - Print colorized output - added Cli helper **Security:** diff --git a/bin/ctfc b/bin/ctfc index 108e176..f0738fa 100755 --- a/bin/ctfc +++ b/bin/ctfc @@ -1,9 +1,11 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require_relative '../lib/ctfc' require 'optimist' -require 'kolorit' +require_relative '../lib/ctfc' +require_relative '../lib/ctfc/helpers/cli' + +COINS = %w[BTC XMR ETH LTC].freeze opts = Optimist.options do version "Crypto To Fiat Currency\nGem Version: #{CTFC::VERSION}" @@ -15,45 +17,38 @@ opts = Optimist.options do banner ' ruby bin/ctfc eur usd --no-save --coins btc xmr ltc' banner '' - opt :coins, 'Set crypto coins', default: %w[BTC XMR ETH LTC] - opt :save, "Save prices in '.csv' table" - opt :export, "Export all data in '.json' file" - opt :no_print, 'Do not print terminal output' - opt :loop, 'Run script N times', default: 1, type: :integer - opt :wait, 'Wait N seconds between loop', default: 0, type: :integer + opt :coins, 'Set crypto coins', default: COINS + opt :save, "Save prices in '.csv' table", default: false + opt :export, "Export all data in '.json' file", default: false + opt :no_print, 'Do not print terminal output', default: false + opt :source, 'Set source to extract data', default: 'cryptocompare' + opt :loop, 'Run script N times', default: 1, type: :integer + opt :wait, 'Wait N seconds between loop', default: 0, type: :integer end -coins = opts[:coins] -save = opts[:save] +coins = opts[:coins] +source = opts[:source] +save = opts[:save] export = opts[:export] -print = opts[:no_print] ? false : true +print = true unless opts[:no_print] if ARGV.empty? - - # default behavior without arguments - change to suit your needs - crypto = CTFC::Client.new(:eur, coins, :cryptocompare, save: false) - crypto.get - crypto.prices.each { |x, y| puts "#{x}: #{y} eur".cyan.bold } if print + prices = Crypto.to :usd, coins, source.to_sym, save: save, export: export + Cli.print_output :USD, prices if print else opts[:loop].times do ARGV.each do |fiat| next if opts.include?(fiat.downcase) - crypto = CTFC::Client.new(fiat, coins, :cryptocompare, - save: save, export: export, print: print) + prices = Crypto.to fiat, coins, source.to_sym, save: save, export: export + next unless print - crypto.get - crypto.prices.each { |x, y| puts "#{x}: #{y} #{fiat}".cyan.bold } if print - sleep 0.5 + system 'clear' or system 'cls' + Cli.print_output fiat, prices end - # end if no `--loop` arg next unless opts[:loop] > 1 - # pause between loops sleep opts[:wait] - - # clear screen - depending on OS - system 'clear' or system 'cls' end end diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index ca76752..280560b 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -22,7 +22,7 @@ module CTFC # private # # def process - # # check response hash for existence of fiat and coins + # # check response hash for persistence of fiat and coins # super # # write method to scrap data from NewSource # end diff --git a/lib/ctfc/api/apitemplate.rb b/lib/ctfc/api/apitemplate.rb index c48575c..db06c81 100644 --- a/lib/ctfc/api/apitemplate.rb +++ b/lib/ctfc/api/apitemplate.rb @@ -25,8 +25,8 @@ class ApiTemplate # @example Send request to cryptocompare # crypto = Cryptocompare.new :eur, %w[BTC XMR] # - # @param [Symbol] fiat **Required**. Fiat currency to convert coin price. - # @param [Array] coins **Required**. Array of coins to scrap data for. + # @param [Symbol] fiat **Required**. Fiat currency to use for conversion. + # @param [Array] coins **Required**. Array of coins to extract data. # # @return [Object] Source instance. # diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index f1461ed..fd5e515 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -5,8 +5,9 @@ module CTFC # - # Initialize client to set configuration, and get data from source. - # Client allow us to get data from any source, and to manipulate with data. + # Client allow us to get data from our sources, and to + # manipulate with that data. While other classes are mostly + # used by each-other, Client is mostly used directly by user. # class Client attr_reader :config, :response, :prices @@ -124,8 +125,10 @@ def send_api_request(source) # automatically add new sources to the client, but be careful with eval. if API.list.include? source klass = check_source_name source - @response = eval "CTFC::API::#{klass}[config[:fiat], config[:coins]]"\ - '# CTFC::API::Crypto_Exchange[fiat, coins]' + + @response = + instance_eval "CTFC::API::#{klass}[ config[:fiat], config[:coins] ]"\ + '# CTFC::API::Cryptocompare[fiat, coins]', __FILE__, __LINE__ - 1 else message = 'Add source to extract data' if source.nil? message = "#{source} not included in API list" if source @@ -133,6 +136,8 @@ def send_api_request(source) end end + # Check for underscore and capitalize each word. + # def check_source_name(source) if source.to_s.include? '_' source.split('_').select(&:capitalize!).join diff --git a/lib/ctfc/helpers/cli.rb b/lib/ctfc/helpers/cli.rb new file mode 100644 index 0000000..1bcfa32 --- /dev/null +++ b/lib/ctfc/helpers/cli.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'kolorit' + +# Helper class to print colorized output in terminal. +# +class Cli + LINES = ('=' * 30).cyan.bold.freeze + + class << self + # + # @example Print colorized output + # Cli.print_output :eur, prices = { 'BTC' => 36985.82, 'XMR' => 151.83 } + # + # @param [Symbol] fiat **Required**. Fiat currency symbol. + # @param [Hash] prices **Required**. Prices hash. + # + def print_output(fiat, prices) + puts LINES + puts colorize(:bold) { "[#{fiat.upcase.yellow}#{']'.bold} conversion rate" } + puts LINES + prices.each { |coin, value| print_prices coin, value } + end + + # @example Set terminal colors for price hash + # Cli.colors color1: :yellow, color2: :green + # + def colors(clrs = { color1: :yellow, color2: :cyan }) + config + @config[:color1] = clrs[:color1] if clrs[:color1] + @config[:color2] = clrs[:color2] if clrs[:color2] + end + + private + + def print_prices(coin, value) + color1 = config[:color1] + color2 = config[:color2] + + coin = kolorize coin.bold, color1 + value = kolorize value, :bold + br_open = kolorize '['.bold, color2 + br_close = kolorize ']'.bold, color2 + + puts colorize(:bold) { "#{br_open}#{coin}#{br_close} => #{value}" } + end + + def config + @config ||= {} + end + end +end diff --git a/lib/ctfc/version.rb b/lib/ctfc/version.rb index de2b2de..e254737 100644 --- a/lib/ctfc/version.rb +++ b/lib/ctfc/version.rb @@ -2,7 +2,7 @@ module CTFC # gem version - VERSION = '1-dev' + VERSION = '1.0.0-dev' module API class << self @@ -14,8 +14,7 @@ def list @list ||= list_files_in_api_dir end - # Get list of sources from files in api dir. - # For use in .gemspec, to allow automated require. + # Get list of sources from files in api dir (for .gemspec). # # @return [Array] Array of sources as strings. # From 2e966f36f9c3e62ede980e8fa6cd55ebba8b8e5c Mon Sep 17 00:00:00 2001 From: alx3dev Date: Fri, 18 Feb 2022 00:53:42 +0100 Subject: [PATCH 24/26] fix empty color --- lib/ctfc/helpers/cli.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ctfc/helpers/cli.rb b/lib/ctfc/helpers/cli.rb index 1bcfa32..74cd21f 100644 --- a/lib/ctfc/helpers/cli.rb +++ b/lib/ctfc/helpers/cli.rb @@ -25,7 +25,7 @@ def print_output(fiat, prices) # @example Set terminal colors for price hash # Cli.colors color1: :yellow, color2: :green # - def colors(clrs = { color1: :yellow, color2: :cyan }) + def colors(clrs = {}) config @config[:color1] = clrs[:color1] if clrs[:color1] @config[:color2] = clrs[:color2] if clrs[:color2] @@ -46,7 +46,7 @@ def print_prices(coin, value) end def config - @config ||= {} + @config ||= { color1: :yellow, color2: :cyan } end end end From 86e5ae5c92a3935ad41c61891aca8d6b3d5bd3cc Mon Sep 17 00:00:00 2001 From: alx3dev Date: Fri, 18 Feb 2022 11:07:10 +0100 Subject: [PATCH 25/26] fix color setup --- lib/ctfc/helpers/cli.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/ctfc/helpers/cli.rb b/lib/ctfc/helpers/cli.rb index 74cd21f..ed79bd3 100644 --- a/lib/ctfc/helpers/cli.rb +++ b/lib/ctfc/helpers/cli.rb @@ -5,8 +5,6 @@ # Helper class to print colorized output in terminal. # class Cli - LINES = ('=' * 30).cyan.bold.freeze - class << self # # @example Print colorized output @@ -16,9 +14,9 @@ class << self # @param [Hash] prices **Required**. Prices hash. # def print_output(fiat, prices) - puts LINES + puts LINE puts colorize(:bold) { "[#{fiat.upcase.yellow}#{']'.bold} conversion rate" } - puts LINES + puts LINE prices.each { |coin, value| print_prices coin, value } end @@ -26,9 +24,10 @@ def print_output(fiat, prices) # Cli.colors color1: :yellow, color2: :green # def colors(clrs = {}) - config + configure if @config.nil? @config[:color1] = clrs[:color1] if clrs[:color1] @config[:color2] = clrs[:color2] if clrs[:color2] + config end private @@ -48,5 +47,8 @@ def print_prices(coin, value) def config @config ||= { color1: :yellow, color2: :cyan } end + alias configure config end + # helper to print line + LINE = kolorize ('=' * 30).bold, self.config[:color2] end From cd89b31320ae69da1afc3e45329461857b36e76d Mon Sep 17 00:00:00 2001 From: alx3dev Date: Fri, 18 Feb 2022 23:08:03 +0100 Subject: [PATCH 26/26] add helper to list sources --- .rubocop.yml | 3 - .rubocop_todo.yml | 18 ---- CHANGELOG.md | 13 ++- README.md | 179 +-------------------------------------- changelog-dev-old.md | 34 -------- check-syntax.sh | 3 - ctfc.gemspec | 3 +- lib/ctfc.rb | 21 ++++- lib/ctfc/api.rb | 7 +- lib/ctfc/client.rb | 9 +- lib/ctfc/helpers/cli.rb | 18 ++-- lib/ctfc/helpers/list.rb | 37 ++++++++ lib/ctfc/version.rb | 35 -------- 13 files changed, 86 insertions(+), 294 deletions(-) delete mode 100644 .rubocop.yml delete mode 100644 .rubocop_todo.yml delete mode 100644 changelog-dev-old.md delete mode 100755 check-syntax.sh create mode 100644 lib/ctfc/helpers/list.rb diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index 8f5e3b9..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,3 +0,0 @@ -AllCops: - TargetRubyVersion: '2.7' - NewCops: enable diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index f1a2375..0000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,18 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config` -# on 2022-02-17 14:20:49 UTC using RuboCop version 1.25.0. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. - -AllCops: - TargetRubyVersion: '2.7' - NewCops: enable - - -# Offense count: 1 -# Configuration parameters: AllowedConstants. -Style/Documentation: - Exclude: - - 'lib/ctfc/version.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index ddc5a2f..3f589bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,18 @@ Testing changes before merge. - Allow multiple sources - Class-Template based - easy to add more APIs - New sources are automatically required and added to `.gemspec` + - Each source can be required alone + - List helper - list available sources (required in gem) + - Cli helper - print colorized output (required only in executable) - Save prices in `.csv` table - Extract all data in `.json` file - - Print colorized output - added Cli helper **Security:** - - `CTFC::Client` use `eval` - to call source class to extract data. Only source -string is evaluated, but we check for persistence of source class before it. + - `CTFC::Client` use `eval` - to call source class to extract data. Only source name is dinamic, and we check for persistence of source class before evaluation. There's no place for malicious use. + + - Example of eval use in CTFC - call source class to extract data: + +```ruby +eval "CTFC::API::#{source_class} if List.sources.include? source_class" +``` diff --git a/README.md b/README.md index 63071b9..11f1136 100644 --- a/README.md +++ b/README.md @@ -1,178 +1 @@ -[**NOTE:**](https://www.github.com/alx3dev/ctfc/LICENSE_DEPENDENCIES.md) - >Removed gem `colorize` to allow **MIT** license. - >All comits from `v-0.4.0` must be signed and tagged **verified**. - - -# About -Convert any crypto to fiat currency, gather all data and/or save in `.csv` table. -For now only prices are printed/saved, while all data remain easily accessible from variable (for developers). - - -# How to install -Make sure you have ruby and git installed - -Install from source: -```bash - git clone https://github.com/alx3dev/ctfc.git - cd ctfc && bundle install -``` - -Install from rubygems: - -```bash -gem install ctfc -``` -# How to run - - **Read documentation on:** https://rubydoc.info/gems/ctfc/CTFC/Data - -```bash -ruby bin/ctfc fiat_1 fiat_2 fiat_3 -``` - -This command also accept multiple arguments: - - - `--no-save` - do not save `.csv.` output - - `--no-print` - do not print terminal output - - `--coins` - coins to scrap (default: BTC, LTC, XMR, ETH, BCH, ZEC ) - - `--loop` - repeat script N times (default 1) - - `--wait` - wait N seconds between loops (default 0) - - `--help` - help menu - - -# Script Examples - 1 - Run script without arguments (default options) - -```ruby - ruby bin/ctfc - - => return: - print EUR rates for default coins (BTC, LTC, XMR, ETH, BCH, ZEC) - do not save '.csv' table -``` - - - 2 - Add fiat currencies as arguments - -```ruby -ruby bin/ctfc eur usd rsd - - => return: - print EUR, USD, RSD rates for default coins - save data in '.csv' table with pattern: 'crypto_#{CURRENCY}.csv' - -> './crypto_eur.csv', './crypto_usd.csv', './crypto_rsd.csv' -``` - - 3 - Use `--no-save`, `--no-print`, `--loop`, `--wait` - -```ruby -ruby bin/ctfc eur --no-print --coins btc xmr ltc - - => return: - save EUR rates for BTC, XMR and LTC - do not print output - - -ruby bin/ctfc rsd --no-save --coins btc xmr - - => return: - print RSD rates for BTC and XMR - - -# added in version 0.4.0 -ruby bin/ctfc rsd --no-print --loop 1440 --wait 60 - - => return: - save RSD rates without print, run each minute for 24 hours -``` - - -# Developer Examples -```ruby - # define coins to scrap - COINS = %w[ BTC XMR LTC ETH ] - - # initialize Data class - @data = Ctfc.new :eur, save: false, print: false, coins: COINS - => return Ctfc object to work with - -> # - - # execute request - @data.get - => return Hash with upcase string coins as keys, and float prices - -> {"BTC"=>36760.11, "XMR"=>169.55, "LTC"=>114.4, "ETH"=>2746.22} - - # now you can use ::Data instance methods - @data.response - => return RestClient response to cryptocomare API - -> - - # check request url - @data.url - => return Cryptocompare API url - -> "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC&fsyms=LTC&fsyms=XMR&fsyms=ETH&fsyms=BCH&fsyms=ZEC&tsyms=EUR" - - # name of csv table (saved in working directory) - @data.table - => return '.csv' table name - -> 'ctfc_eur.csv' - - # array of coins to work with - @data.coins - => return coins for scrap, also allow setter method @data.coins = [...] - -> ['BTC', 'XMR', 'LTC', 'ETH'] - - # get all data about all coins (json api response) - @data.data - => return all data returned by cryptocompare API - -> {"RAW"=> - {"BTC"=> - {"EUR"=> - {"TYPE"=>"5", - "MARKET"=>"CCCAGG", - "FROMSYMBOL"=>"BTC", - "TOSYMBOL"=>"EUR", - "FLAGS"=>"2049", - "PRICE"=>33851.17, - "LASTUPDATE"=>1642773847, - "MEDIAN"=>33853.8, - "LASTVOLUME"=>0.1, - "LASTVOLUMETO"=>3384.3676, - "LASTTRADEID"=>"2024043", - ... ... ... ... ... ... ... - - - TO BE CONTINIUED ... -``` - -**Class methods as shortcuts:** - -```ruby -# get default coins in EUR, save output without printing - prices = Ctfc.to :eur, print: false - -# get default coins in RSD, print output, don't save - Ctfc.to :rsd, save: false - -# For those who don't like name `Ctfc`, you can use `Crypto` too: - prices = Crypto.to :eur, coins: %w[BTC XMR] -``` - -# Tests -To run tests call `rspec --format doc` -To test code syntax use `./check-syntax.sh`. -This command will run rubocop for code inspection, but with some errors hidden by `.rubocop_todo.yml`. Using check-syntax script, all test should pass. - - -# Contribution -Any contribution is highly appreciated, as long as you follow Code of Conduct. - - - Fork repository - - Make your changes - - Write tests - - Submit pull request - -# License -Don't be a dick - it's MIT. - -# To-Do: -See **Projects** +This is development branch, testing code before merge. diff --git a/changelog-dev-old.md b/changelog-dev-old.md deleted file mode 100644 index 1301166..0000000 --- a/changelog-dev-old.md +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -## [Unreleased] - - - Prepare `version-0.4.1` to be ready for Open Source under MIT license. - - Removed gem `colorize` because of GPLv2. - - Override String class with color codes for Linux. - -## [0.4.0] - 2022-01-24 - - - Add options `--loop` and `--wait` for terminal scripting - - Only signed and verified commits from `v-0.4.0` - - PGP Public key available at https://www.github.com/alx3dev/contact - - Before `v-0.4.0` unverified commits were pushed. - - From now on, only VERIFIED signed commits should be trusted - ------BEGIN PGP SIGNATURE----- - -iQIzBAEBCgAdFiEEtl+v9iZj5AyyvRYdTFhE2vUGMucFAmHvxM0ACgkQTFhE2vUG -MuejCQ//Uw+xaFBNrat3evChlifrxAckEqhtQeUwiuluSkReYmV+TqdSNqhzzezh -BJkgp5d7/Lzt/7wCwK9aVuvejKG0op4Ernk1jRH7jdOkH7TeqVyRe8837XXbn8Q3 -FRh5ih/dbbeQtvIVuI1Z7unRQGvlfMnBKK65wQfHRffJg3kTgO4ICx0Gz1LgxfCa -OwjSYp2+pXQtGOI+ab9/a7avBb6TIW5uQjegZ5LsVR/at3RySki8v1fOA0Tfmsgv -pZ4aKhZkpVhZ2l6fj181hBwBspmJPxpB8v4mZXhXAfe9z/425favlfyKLxRvmFu5 -aQK6hZ/gTEXz2s0ypVVPtwQS5FK3YurhKeun6Vto1lQJM10MngwB53B18eNSAB3h -tsFks8QojwtPH2nttksWOKy5V5tQdBB0uCd72DdEwnAOo1jyMhG7Otkpt4ch5CAd -xRhmrC0Pp1GgqH2avpeezA3uQZ2HW2Gp1nFnKHeRhPutEBF8x8NLjCxbS3GExrgk -+Lijy13Eu/y4f2S7TIfN/LMB+ruqQrZe+NmzE8J1tuMcaDqIYsV17c5FXvaxGS/q -9mJ++v6xHfMykvCNR/FtuUxq+TaD/aNBm+pKYhjqzr4jmoAy2h1gvQEIl4OMXxlf -5xzgdP6ePWkdwWnYvMtrIbkVWfKvfaBUgYsT/+FRVDierWq2aj0= -=/ZSG ------END PGP SIGNATURE----- - diff --git a/check-syntax.sh b/check-syntax.sh deleted file mode 100755 index 0e2da47..0000000 --- a/check-syntax.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -rubocop --format simple --config .rubocop_todo.yml diff --git a/ctfc.gemspec b/ctfc.gemspec index 9f471c5..da196c6 100644 --- a/ctfc.gemspec +++ b/ctfc.gemspec @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative './lib/ctfc/version' +require_relative './lib/ctfc/helpers/list' Gem::Specification.new do |s| s.name = 'ctfc' @@ -39,7 +40,7 @@ Gem::Specification.new do |s| ctfc.gemspec] # auto-add sources in api dir - CTFC::API.list_of_sources.select do |source| + List.source_files.select do |source| file = "lib/ctfc/api/#{source}" s.files += [file] end diff --git a/lib/ctfc.rb b/lib/ctfc.rb index 3e8433d..208e53e 100644 --- a/lib/ctfc.rb +++ b/lib/ctfc.rb @@ -1,11 +1,24 @@ # frozen_string_literal: true require_relative 'ctfc/client' +require_relative 'ctfc/version' # Ctfc is shortcut for CTFC::Client. # @see CTFC::Client -class Ctfc < CTFC::Client; end +class Ctfc < CTFC::Client +end -# Crypto is shortcut for CTFC::Client. -# @see CTFC::Client -class Crypto < CTFC::Client; end +# Shortcut to initialize new client, +# and get prices hash. +# +# @example Get EUR prices for coins: +# coins = %w[BTC XMR LTC ETH] +# Crypto.to :eur, coins, :cryptocompare, save: true +# +class Crypto + # @return [Hash] + # @see CTFC::Client + def self.to(*args) + CTFC::Client.new(*args).get + end +end diff --git a/lib/ctfc/api.rb b/lib/ctfc/api.rb index 280560b..3c2285f 100644 --- a/lib/ctfc/api.rb +++ b/lib/ctfc/api.rb @@ -1,10 +1,13 @@ # frozen_string_literal: true -require_relative 'version' unless defined? CTFC::VERSION +# Benchmark say it's faster to use **unless defined?** if we require file +# on multiple locations + require_relative 'api/apitemplate' unless defined? CTFC::API::ApiTemplate +require_relative 'helpers/list' unless defined? List # automatically require new apis -CTFC::API.list.select { |source| require_relative "api/#{source}" } +List.sources.select { |source| require_relative "api/#{source}" } module CTFC # diff --git a/lib/ctfc/client.rb b/lib/ctfc/client.rb index fd5e515..b72a822 100644 --- a/lib/ctfc/client.rb +++ b/lib/ctfc/client.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative 'export' require_relative 'api' +require_relative 'export' module CTFC # @@ -12,10 +12,7 @@ module CTFC class Client attr_reader :config, :response, :prices - def self.to(*args) - new(*args).get - end - + # # Choose fiat currency, coins and source for new client. # @example Initialize new **EUR** client # client = CTFC::Client.new :eur, %w[BTC XMR LTC ETH] @@ -123,7 +120,7 @@ def success? def send_api_request(source) # automatically add new sources to the client, but be careful with eval. - if API.list.include? source + if List.sources.include? source klass = check_source_name source @response = diff --git a/lib/ctfc/helpers/cli.rb b/lib/ctfc/helpers/cli.rb index ed79bd3..46da1fe 100644 --- a/lib/ctfc/helpers/cli.rb +++ b/lib/ctfc/helpers/cli.rb @@ -14,9 +14,9 @@ class << self # @param [Hash] prices **Required**. Prices hash. # def print_output(fiat, prices) - puts LINE + puts line puts colorize(:bold) { "[#{fiat.upcase.yellow}#{']'.bold} conversion rate" } - puts LINE + puts line prices.each { |coin, value| print_prices coin, value } end @@ -25,8 +25,9 @@ def print_output(fiat, prices) # def colors(clrs = {}) configure if @config.nil? - @config[:color1] = clrs[:color1] if clrs[:color1] - @config[:color2] = clrs[:color2] if clrs[:color2] + @config[:color1] = clrs[:color1] if clrs[:color1] + @config[:color2] = clrs[:color2] if clrs[:color2] + @config[:line_color] = clrs[:line_color] if clrs[:line_color] config end @@ -45,10 +46,13 @@ def print_prices(coin, value) end def config - @config ||= { color1: :yellow, color2: :cyan } + @config ||= { color1: :yellow, color2: :cyan, line_color: :cyan } end alias configure config + + # helper to print line + def line + kolorize ('=' * 30).bold, config[:line_color] + end end - # helper to print line - LINE = kolorize ('=' * 30).bold, self.config[:color2] end diff --git a/lib/ctfc/helpers/list.rb b/lib/ctfc/helpers/list.rb new file mode 100644 index 0000000..d595310 --- /dev/null +++ b/lib/ctfc/helpers/list.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Get list of sources. One method for :symbolized list, +# and one for complete filenames - to include in gemspec. +# +class List + class << self + # List sources as Array of Symbols. + # @return [Array] Array of sources as :symbols + # + def sources + @sources ||= list_files_in_api_dir + end + + # Get list of sources from files in api dir (for .gemspec). + # @return [Array] Array of sources as strings. + # + def source_files + @source_files ||= list_files_in_api_dir(gemspec: true) + end + + private + + def list_files_in_api_dir(gemspec: false) + sources = [] + skip = %w[. .. apitemplate.rb] + path = File.expand_path(__FILE__).gsub!('helpers/list.rb', 'api') + Dir.entries(path).select do |source| + next if skip.include? source + + source = source.gsub('.rb', '').to_sym unless gemspec == true + sources << source + end + sources + end + end +end diff --git a/lib/ctfc/version.rb b/lib/ctfc/version.rb index e254737..7dcf69d 100644 --- a/lib/ctfc/version.rb +++ b/lib/ctfc/version.rb @@ -3,39 +3,4 @@ module CTFC # gem version VERSION = '1.0.0-dev' - - module API - class << self - # - # List sources as Array of Symbols. - # @return [Array] Array of sources as :symbols - # - def list - @list ||= list_files_in_api_dir - end - - # Get list of sources from files in api dir (for .gemspec). - # - # @return [Array] Array of sources as strings. - # - def list_of_sources - @list_of_sources ||= list_files_in_api_dir(gemspec: true) - end - - private - - def list_files_in_api_dir(gemspec: false) - sources = [] - skip = %w[. .. apitemplate.rb] - path = File.expand_path(__FILE__).gsub!('version.rb', 'api') - Dir.entries(path).select do |source| - next if skip.include? source - - source = source.gsub('.rb', '').to_sym unless gemspec == true - sources << source - end - sources - end - end - end end