dolarblue 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/.rspec +1 -1
- data/.ruby-gemset +2 -0
- data/.ruby-version +1 -1
- data/.travis.yml +9 -4
- data/CHANGELOG.md +9 -4
- data/Gemfile +20 -0
- data/Gemfile.lock +110 -0
- data/README.md +12 -11
- data/Rakefile +3 -2
- data/config/xpaths.yml +17 -0
- data/dolarblue.gemspec +20 -16
- data/lib/dolarblue.rb +9 -4
- data/lib/dolarblue/blue.rb +7 -0
- data/lib/dolarblue/card.rb +20 -0
- data/lib/dolarblue/configuration.rb +17 -48
- data/lib/dolarblue/inflector.rb +23 -0
- data/lib/dolarblue/instance_methods.rb +88 -80
- data/lib/dolarblue/official.rb +7 -0
- data/lib/dolarblue/version.rb +1 -1
- data/lib/dolarblue/xchange.rb +86 -0
- data/spec/blue_spec.rb +5 -0
- data/spec/cassettes/.keep +0 -0
- data/spec/dolarblue_spec.rb +17 -87
- data/spec/spec_helper.rb +21 -11
- metadata +75 -58
- data/lib/dolarblue/class_methods.rb +0 -11
- data/lib/dolarblue/exchange.rb +0 -108
- data/spec/exchange_spec.rb +0 -92
@@ -0,0 +1,23 @@
|
|
1
|
+
class Dolarblue
|
2
|
+
module Inflector
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Removes the module part from the expression in the string.
|
6
|
+
#
|
7
|
+
# @param path [String] the module expression stringified
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections"
|
11
|
+
# demodulize('Inflections') # => "Inflections"
|
12
|
+
#
|
13
|
+
# @return [String] with the module part removed and the stringified class name only
|
14
|
+
def demodulize(path)
|
15
|
+
path = path.to_s
|
16
|
+
if i = path.rindex('::')
|
17
|
+
path[(i+2)..-1]
|
18
|
+
else
|
19
|
+
path
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,94 +1,102 @@
|
|
1
|
+
require 'dolarblue/configuration'
|
2
|
+
require 'dolarblue/blue'
|
3
|
+
require 'dolarblue/official'
|
4
|
+
require 'dolarblue/card'
|
5
|
+
|
6
|
+
require 'nokogiri' # gem 'nokogiri'
|
7
|
+
require 'open-uri' # stdlib
|
8
|
+
|
1
9
|
class Dolarblue
|
2
|
-
module InstanceMethods
|
3
|
-
|
4
|
-
# Create a new Dolarblue instance to work later on
|
5
|
-
#
|
6
|
-
# @param [Configuration] config the configuration instance
|
7
|
-
#
|
8
|
-
# @return [Dolarblue] new instance
|
9
|
-
def initialize(config = Configuration.instance)
|
10
|
-
fail ArgumentError, "Expected a Dolarblue::Configuration instance as argument" unless config.is_a?(Configuration)
|
11
|
-
@card_fee = config.card_fee
|
12
|
-
@blue = Dolarblue::Exchange.new('Blue', config.blue_screen_name, config.blue_regexp, config.buy_sell_blue_factor)
|
13
|
-
@official = Dolarblue::Exchange.new('Official', config.official_screen_name, config.official_regexp, config.buy_sell_official_factor)
|
14
|
-
self
|
15
|
-
end
|
16
10
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
11
|
+
# Create a new Dolarblue instance to work later on
|
12
|
+
#
|
13
|
+
# @param config [Configuration] the configuration instance
|
14
|
+
#
|
15
|
+
# @return [Dolarblue] new instance
|
16
|
+
def initialize(config = Configuration.instance)
|
17
|
+
@config = config.defaults
|
18
|
+
@blue = Blue.new
|
19
|
+
@official = Official.new
|
20
|
+
@card = Card.new
|
21
|
+
@output = nil
|
22
|
+
self
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
print "Done.\n\n"
|
32
|
-
self
|
33
|
-
end
|
25
|
+
# Connect to the source and retrieve dollar exchange values
|
26
|
+
#
|
27
|
+
# @return (see #initialize)
|
28
|
+
def update!
|
29
|
+
@output = ''
|
30
|
+
base_url = @config.base_url
|
31
|
+
fail ArgumentError, "Need base_url configuration to know where to web-scrape from. Current value: #{base_url}" if base_url.empty?
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
# @return [Float] percentile value between 0..1
|
38
|
-
def gap_official
|
39
|
-
fail "Need blue and official values to be setup before calculating the gap" unless @blue.sell_value && @blue.sell_value > 0 && @official.sell_value && @official.sell_value > 0
|
40
|
-
(@blue.sell_value / @official.sell_value - 1)
|
41
|
-
end
|
33
|
+
log "Obtaining latest AR$ vs US$ exchange values..."
|
34
|
+
html_file = open(base_url)
|
42
35
|
|
43
|
-
|
44
|
-
|
45
|
-
# @return [Float] percentile value between 0..100
|
46
|
-
def gap_official_percent
|
47
|
-
(gap_official * 100).round(0)
|
48
|
-
end
|
36
|
+
log "Parsing values..."
|
37
|
+
parse_values Nokogiri::HTML(html_file)
|
49
38
|
|
50
|
-
|
51
|
-
|
52
|
-
|
39
|
+
log "\nDone: #{Time.now.localtime}\n"
|
40
|
+
self
|
41
|
+
end
|
53
42
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
43
|
+
# Returns the gap percentile (e.g. 60) between the real (blue) dollar value versus the official
|
44
|
+
#
|
45
|
+
# @return [Float] percentile value between 0..100
|
46
|
+
def gap_official_percent
|
47
|
+
gap_official = @blue.sell / @official.sell - 1
|
48
|
+
(gap_official * 100).round(0)
|
49
|
+
end
|
61
50
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
51
|
+
# Returns the gap percentile between the real (blue) dollar value versus the official
|
52
|
+
#
|
53
|
+
# @return (see #gap_official_percent)
|
54
|
+
def gap_card_percent
|
55
|
+
gap_card = @blue.sell / @card.sell - 1
|
56
|
+
(gap_card * 100).round(0)
|
57
|
+
end
|
68
58
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
59
|
+
# Output string to be used by the binary `dolarblue`
|
60
|
+
#
|
61
|
+
# @return [String] the output with dollar exchange information
|
62
|
+
def output
|
63
|
+
<<-OUTPUT
|
64
|
+
#{@output}
|
65
|
+
#{@official.output}
|
66
|
+
#{@card.output}
|
67
|
+
#{@blue.output}
|
68
|
+
|
69
|
+
- Gap card.......blue: #{gap_card_percent}%
|
70
|
+
- Gap official...blue: #{gap_official_percent}%
|
75
71
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
72
|
+
Information source:
|
73
|
+
#{@config.base_url}
|
74
|
+
OUTPUT
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Parse dollar related values using the XPath configured
|
80
|
+
#
|
81
|
+
# @param doc [Nokogiri::HTML] the html document to extract values from
|
82
|
+
#
|
83
|
+
# @return [true, false] boolean If parsed successfully or not
|
84
|
+
def parse_values(doc)
|
85
|
+
[@blue, @official, @card].each do |type|
|
86
|
+
type.extract_values(doc)
|
91
87
|
end
|
88
|
+
end
|
92
89
|
|
90
|
+
# Poor man's logger to keep user updated with http get activity
|
91
|
+
# while allowing to buffer the string while in RSpec test mode
|
92
|
+
#
|
93
|
+
# @param msg [String] the message to print or buffer
|
94
|
+
def log(msg)
|
95
|
+
if defined?(RSpec)
|
96
|
+
@output << msg
|
97
|
+
else
|
98
|
+
print msg
|
99
|
+
end
|
93
100
|
end
|
101
|
+
|
94
102
|
end
|
data/lib/dolarblue/version.rb
CHANGED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'dolarblue/configuration'
|
2
|
+
require 'dolarblue/inflector'
|
3
|
+
|
4
|
+
class Dolarblue
|
5
|
+
|
6
|
+
# Base class for Blue, Official and Card used to hold sell/buy values functionality
|
7
|
+
#
|
8
|
+
# @abstract
|
9
|
+
class XChange
|
10
|
+
attr_reader :buy, :sell
|
11
|
+
|
12
|
+
# Create a new Blue / Official / Card instance to work later on
|
13
|
+
#
|
14
|
+
# @param config [Configuration] the configuration instance
|
15
|
+
#
|
16
|
+
# @return [self] new instance
|
17
|
+
def initialize(config = Configuration.instance)
|
18
|
+
@config = config.defaults
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return the demodulized class name
|
23
|
+
#
|
24
|
+
# @return [String] demodulized class name string
|
25
|
+
def cname
|
26
|
+
Inflector.demodulize(self.class.name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return downcased demodulized class name
|
30
|
+
#
|
31
|
+
# @return [String] downcased demodulized class name string
|
32
|
+
def name
|
33
|
+
cname.downcase
|
34
|
+
end
|
35
|
+
|
36
|
+
# Performs buy and sell values extraction from a Nokogiri::HTML Document
|
37
|
+
#
|
38
|
+
# @param [Nokogiri::HTML] doc the html document to extract values from
|
39
|
+
def extract_values(doc)
|
40
|
+
@buy = extract_val(doc, 'buy')
|
41
|
+
@sell = extract_val(doc, 'sell')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return a formatted string suitable for user output with current buy value
|
45
|
+
#
|
46
|
+
# @return [String] formatted output buy exchange value
|
47
|
+
def buy_output
|
48
|
+
'%.2f' % buy
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return a formatted string suitable for user output with current sell value
|
52
|
+
#
|
53
|
+
# @return [String] formatted output sell exchange value
|
54
|
+
def sell_output
|
55
|
+
'%.2f' % sell
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return a formatted string suitable for user output with current buy and sell values
|
59
|
+
#
|
60
|
+
# @return [String] formatted output with buy and sell exchange values
|
61
|
+
def output
|
62
|
+
t = cname.ljust(10, '.')
|
63
|
+
b = buy_output.rjust(5)
|
64
|
+
s = sell_output.rjust(5)
|
65
|
+
%Q{- Dollar #{t}..: #{b} / #{s}}
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
# Extract individual buy/sell values from a Nokogiri::HTML Document
|
71
|
+
#
|
72
|
+
# @param doc [Nokogiri::HTML] the html document to extract values from
|
73
|
+
# @param type [String] the dollar type, can be 'blue', 'official', 'card'
|
74
|
+
#
|
75
|
+
# @raise [RuntimeError] if unable to proper buy/sell value for current dollar type
|
76
|
+
#
|
77
|
+
# @private
|
78
|
+
def extract_val(doc, type)
|
79
|
+
xpath = @config[name][type].xpath
|
80
|
+
value = doc.xpath(xpath).text.gsub(',', '.')
|
81
|
+
fail RuntimeError, "Failed to capture #{name} #{type} value" if value.empty?
|
82
|
+
value.to_f.round(2)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/spec/blue_spec.rb
ADDED
File without changes
|
data/spec/dolarblue_spec.rb
CHANGED
@@ -1,92 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
before do
|
22
|
-
Twitter::Client.stub_chain(:new, :last_tweet).with('DolarBlue').and_return(blue_tweet)
|
23
|
-
Twitter::Client.stub_chain(:new, :last_tweet).with('cotizacionhoyar').and_return(official_tweet)
|
24
|
-
end
|
25
|
-
|
26
|
-
describe '#new' do
|
27
|
-
it 'fails if argument is not a Configuration class' do
|
28
|
-
expect { described_class.new(nil) }.to raise_error(ArgumentError)
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'start as invalid right after creation' do
|
32
|
-
subject.should_not be_valid
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'fails if you try to use it before is valid' do
|
36
|
-
expect { subject.gap }.to raise_error
|
37
|
-
expect { subject.gap_percent }.to raise_error
|
38
|
-
expect { subject.output }.to raise_error
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
context 'updated' do
|
43
|
-
before do
|
44
|
-
subject.update!
|
45
|
-
end
|
46
|
-
|
47
|
-
describe '#update!' do
|
48
|
-
it 'becomes valid after calling update!' do
|
49
|
-
subject.should be_valid
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
describe '#output' do
|
54
|
-
it 'has an output with real values' do
|
55
|
-
subject.output.should match(/Blue/)
|
56
|
-
subject.output.should match(/Tarjeta/)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
describe 'gap values' do
|
61
|
-
it 'has a valid gap card value' do
|
62
|
-
subject.gap_card.should be >= 0
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'has a valid gap card % value' do
|
66
|
-
subject.gap_card_percent.should be >= 0
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'has a valid gap official value' do
|
70
|
-
subject.gap_official.should be >= 0
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'has a valid gap official % value' do
|
74
|
-
subject.gap_official_percent.should be >= 0
|
75
|
-
end
|
3
|
+
vcr_options = { allow_playback_repeats: true }
|
4
|
+
|
5
|
+
describe Dolarblue, vcr: vcr_options do
|
6
|
+
context 'Class Methods' do
|
7
|
+
subject { Dolarblue }
|
8
|
+
|
9
|
+
it 'should return all the dollar exchange Blue/Official/Card values and percentiles suitable for user printing' do
|
10
|
+
# expect(subject.get_output).to match(/Dollar Blue.*\d+\.\d+.*^Information source/)
|
11
|
+
expect(subject.get_output).to match(/Obtaining/)
|
12
|
+
expect(subject.get_output).to match(/Done/)
|
13
|
+
values = %q{\d+\.\d+\s+\/\s+\d+\.\d+}
|
14
|
+
expect(subject.get_output).to match(/Official.*#{values}/)
|
15
|
+
expect(subject.get_output).to match(/Card.*n\/a\s+\/\s+\d+\.\d+/)
|
16
|
+
expect(subject.get_output).to match(/Blue.*#{values}/)
|
17
|
+
expect(subject.get_output).to match(/Gap card/)
|
18
|
+
expect(subject.get_output).to match(/Gap official/)
|
19
|
+
expect(subject.get_output).to match(/Information source/)
|
76
20
|
end
|
77
21
|
end
|
78
|
-
|
79
|
-
describe 'constants' do
|
80
|
-
it 'should have a version number' do
|
81
|
-
Dolarblue::VERSION.should_not be_nil
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
describe 'ClassMethods' do
|
86
|
-
it 'has an get_output() class method to retrieve latest values' do
|
87
|
-
Dolarblue.get_output.should match(/Blue/)
|
88
|
-
Dolarblue.get_output.should match(/Tarjeta/)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
22
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,22 +1,32 @@
|
|
1
|
-
|
2
|
-
require '
|
1
|
+
unless %w(jruby rbx).include? RUBY_ENGINE
|
2
|
+
require 'simplecov'
|
3
|
+
require 'coveralls'
|
3
4
|
|
4
|
-
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
5
|
-
|
6
|
-
|
7
|
-
]
|
8
|
-
SimpleCov.start
|
5
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
6
|
+
SimpleCov::Formatter::HTMLFormatter,
|
7
|
+
Coveralls::SimpleCov::Formatter
|
8
|
+
]
|
9
|
+
SimpleCov.start
|
10
|
+
end
|
9
11
|
|
10
|
-
# Internal
|
11
12
|
require 'dolarblue'
|
12
13
|
|
14
|
+
require 'vcr' # gem 'vcr'
|
15
|
+
|
16
|
+
VCR.configure do |c|
|
17
|
+
c.cassette_library_dir = 'spec/cassettes'
|
18
|
+
c.hook_into :webmock
|
19
|
+
c.configure_rspec_metadata!
|
20
|
+
end
|
21
|
+
|
13
22
|
# Require this file using `require "spec_helper"` within each of your specs
|
14
23
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
15
24
|
RSpec.configure do |config|
|
16
25
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
17
26
|
config.run_all_when_everything_filtered = true
|
18
27
|
config.filter_run :focus
|
19
|
-
|
20
|
-
|
21
|
-
|
28
|
+
config.order = 'random' # Run specs in random order to surface order dependencies.
|
29
|
+
config.expect_with :rspec do |c|
|
30
|
+
c.syntax = :expect # disable `should` syntax http://goo.gl/BGxqP
|
31
|
+
end
|
22
32
|
end
|