dolarblue 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # Returns current object state, whether valid or not
18
- #
19
- # @return [true, false] boolean state
20
- def valid?
21
- @blue.valid? && @official.valid?
22
- end
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
- # Connect to the source and retrieve dollar exchange values
25
- #
26
- # @return [self]
27
- def update!
28
- print "Obtaining latest AR$ vs US$ exchange values..."
29
- @blue.update!
30
- @official.update!
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
- # Returns the gap between the real (blue) dollar value versus the official
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
- # Returns the gap percentile 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 * 100).round(0)
48
- end
36
+ log "Parsing values..."
37
+ parse_values Nokogiri::HTML(html_file)
49
38
 
50
- def card_fee_sell_value
51
- (@official.sell_value * @card_fee).round(2)
52
- end
39
+ log "\nDone: #{Time.now.localtime}\n"
40
+ self
41
+ end
53
42
 
54
- # Returns the gap between the real (blue) dollar value versus the dollar "tarjeta" vale
55
- #
56
- # @return [Float] percentile value between 0..1
57
- def gap_card
58
- 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
59
- (@blue.sell_value / card_fee_sell_value - 1)
60
- end
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
- # Returns the gap percentile between the real (blue) dollar value versus the official
63
- #
64
- # @return [Float] percentile value between 0..100
65
- def gap_card_percent
66
- (gap_card * 100).round(0)
67
- end
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
- # Return a string suitable for user output about dollar "tarjeta" values
70
- #
71
- # @return [String] output exchange values 1 line string
72
- def card_output_values
73
- %Q{- Dolar "Tarjeta": n/a / #{'%.2f' % card_fee_sell_value} (Updated #{@official.updated_ago})}
74
- end
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
- # Output string to be used by the binary `dolarblue`
77
- #
78
- # @return [String] the output with dollar exchange information
79
- def output
80
- <<-OUTPUT
81
- #{@official.output_values}
82
- #{card_output_values}
83
- #{@blue.output_values}
84
- - Gap "tarjeta"..: #{gap_card_percent}%
85
- - Gap (official).: #{gap_official_percent}%
86
-
87
- Information sources:
88
- #{@official.output_link}
89
- #{@blue.output_link}
90
- OUTPUT
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
@@ -0,0 +1,7 @@
1
+ require 'dolarblue/xchange'
2
+
3
+ class Dolarblue
4
+ # Class used to hold sell/buy values functionality for dollar "Official"
5
+ class Official < XChange
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  class Dolarblue
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -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
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dolarblue::Blue do
4
+ #TODO
5
+ end
File without changes
@@ -1,92 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Dolarblue do
4
- let(:blue_tweet) do
5
- Twitter::Tweet.new({
6
- id: '309326405148233731',
7
- text: 'Dolar Paralelo: $7,82',
8
- created_at: DateTime.parse('2013-03-06T12:35:36-03:00'),
9
- screen_name: 'DolarBlue'
10
- })
11
- end
12
- let(:official_tweet) do
13
- Twitter::Tweet.new({
14
- id: '309289043722637312',
15
- text: '#Cotizacion cierre promedio parcial 06-03-13: #Dolar: 5.06 // #Euro: 6.71 // #Real: 2.74.',
16
- created_at: DateTime.parse('2013-03-06T10:07:08-03:00'),
17
- screen_name: 'cotizacionhoyar'
18
- })
19
- end
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
@@ -1,22 +1,32 @@
1
- require 'simplecov'
2
- require 'coveralls'
1
+ unless %w(jruby rbx).include? RUBY_ENGINE
2
+ require 'simplecov'
3
+ require 'coveralls'
3
4
 
4
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
- SimpleCov::Formatter::HTMLFormatter,
6
- Coveralls::SimpleCov::Formatter
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
- # Run specs in random order to surface order dependencies.
21
- config.order = 'random'
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