sqa 0.0.14 → 0.0.17

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.
data/lib/sqa/errors.rb CHANGED
@@ -1,6 +1,30 @@
1
1
  # lib/sqa/errors.rb
2
2
 
3
- module SQA
4
- # raised when an API contract is broken
5
- class BadParameterError < ArgumentError; end
3
+ # raised when a method is still in TODO state
4
+ class ApiError < RuntimeError
5
+ def self.raise(why)
6
+ puts "="*64
7
+ puts "== API Error"
8
+ puts why
9
+ puts
10
+ puts "Callback trace:"
11
+ puts caller
12
+ puts "="*64
13
+ super
14
+ end
6
15
  end
16
+
17
+ # raised when a method is still in TODO state
18
+ class NotImplemented < RuntimeError
19
+ def self.raise
20
+ puts "="*64
21
+ puts "== Not Yet Implemented"
22
+ puts "Callback trace:"
23
+ puts caller
24
+ puts "="*64
25
+ super
26
+ end
27
+ end
28
+
29
+ # raised when an API contract is broken
30
+ class BadParameterError < ArgumentError; end
data/lib/sqa/init.rb ADDED
@@ -0,0 +1,51 @@
1
+ # sqa/lib/sqa/init.rb
2
+
3
+ module SQA
4
+ class << self
5
+ @@config = nil
6
+ @@av = ApiKeyManager::RateLimited.new(
7
+ api_keys: ENV['AV_API_KEYS'],
8
+ delay: true,
9
+ rate_count: ENV['AV_RATE_CNT'] || 5,
10
+ rate_period: ENV['AV_RATE_PER'] || 60
11
+ )
12
+
13
+ # Initializes the SQA modules
14
+ # returns the configuration
15
+ #
16
+ def init(argv=ARGV)
17
+ if argv.is_a? String
18
+ argv = argv.split()
19
+ end
20
+
21
+
22
+ # Ran at SQA::Config elaboration time
23
+ # @@config = Config.new
24
+
25
+ if defined? CLI
26
+ CLI.run(argv)
27
+ else
28
+ # There are no real command line parameters
29
+ # because the sqa gem is being required within
30
+ # the context of a larger program.
31
+ end
32
+
33
+ config.data_dir = homify(config.data_dir)
34
+
35
+ config
36
+ end
37
+
38
+ def av() = @@av
39
+
40
+ def debug?() = @@config.debug?
41
+ def verbose?() = @@config.verbose?
42
+
43
+ def homify(filepath) = filepath.gsub(/^~/, Nenv.home)
44
+ def data_dir() = Pathname.new(config.data_dir)
45
+ def config() = @@config
46
+
47
+ def config=(an_object)
48
+ @@config = an_object
49
+ end
50
+ end
51
+ end
data/lib/sqa/stock.rb CHANGED
@@ -1,91 +1,148 @@
1
1
  # lib/sqa/stock.rb
2
2
 
3
- require 'active_support/core_ext/string' # for String#underscore
4
- require 'hashie' # for Hashie::Mash
5
-
6
3
 
7
4
  # SMELL: SQA::Stock is now pretty coupled to the Alpha Vantage
8
5
  # API service. Should that stuff be extracted into a
9
6
  # separate class and injected by the requiring program?
10
7
 
11
8
  class SQA::Stock
9
+ extend Forwardable
10
+
12
11
  CONNECTION = Faraday.new(url: "https://www.alphavantage.co")
13
12
 
14
- attr_accessor :company_name
15
- attr_accessor :df # The DataFrane
16
- attr_accessor :ticker
17
- attr_accessor :type # type of data store (default is CSV)
18
- attr_accessor :indicators
13
+ attr_accessor :data # General Info -- SQA::DataFrame::Data
14
+ attr_accessor :df # Historical Prices -- SQA::DataFrame::Data
15
+
16
+ attr_accessor :klass # class of historical and current prices
17
+ attr_accessor :transformers # procs for changing column values from String to Numeric
19
18
 
20
19
  def initialize(
21
20
  ticker:,
22
- source: :alpha_vantage,
23
- type: :csv
21
+ source: :alpha_vantage
24
22
  )
23
+
24
+ @ticker = ticker.downcase
25
+ @source = source
26
+
25
27
  raise "Invalid Ticker #{ticker}" unless SQA::Ticker.valid?(ticker)
26
28
 
27
- # TODO: Change API on lookup to return array instead of hash
28
- # Could this also incorporate the validation process to
29
- # save an additiona hash lookup?
29
+ @data_path = SQA.data_dir + "#{@ticker}.json"
30
+ @df_path = SQA.data_dir + "#{@ticker}.csv"
30
31
 
31
- entry = SQA::Ticker.lookup(ticker)
32
+ @klass = "SQA::DataFrame::#{@source.to_s.camelize}".constantize
33
+ @transformers = "SQA::DataFrame::#{@source.to_s.camelize}::TRANSFORMERS".constantize
32
34
 
33
- @ticker = ticker.downcase
34
- @company_name = entry[:name]
35
- @exchange = entry[:exchange]
36
- @klass = "SQA::DataFrame::#{source.to_s.camelize}".constantize
37
- @type = type
38
- @indicators = OpenStruct.new
35
+ if @data_path.exist?
36
+ load
37
+ else
38
+ create
39
+ update
40
+ save
41
+ end
39
42
 
40
43
  update_the_dataframe
41
44
  end
42
45
 
43
46
 
44
- def update_the_dataframe
45
- df1 = @klass.load(@ticker, type)
46
- df2 = @klass.recent(@ticker)
47
+ def load
48
+ @data = SQA::DataFrame::Data.new(
49
+ JSON.parse(@data_path.read)
50
+ )
51
+ end
52
+
53
+
54
+ def create
55
+ @data =
56
+ SQA::DataFrame::Data.new(
57
+ {
58
+ ticker: @ticker,
59
+ source: @source,
60
+ indicators: { xyzzy: "Magic" },
61
+ }
62
+ )
63
+ end
64
+
65
+
66
+ def update
67
+ merge_overview
68
+ end
69
+
70
+
71
+ def save
72
+ @data_path.write @data.to_json
73
+ end
74
+
75
+
76
+ def_delegator :@data, :ticker, :ticker
77
+ def_delegator :@data, :name, :name
78
+ def_delegator :@data, :exchange, :exchange
79
+ def_delegator :@data, :source, :source
80
+ def_delegator :@data, :indicators, :indicators
81
+ def_delegator :@data, :indicators=, :indicators=
82
+ def_delegator :@data, :overview, :overview
47
83
 
48
- df1_nrows = df1.nrows
49
- @df = @klass.append(df1, df2)
50
84
 
51
- if @df.nrows > df1_nrows
52
- @df.send("to_#{@type}", SQA.data_dir + "#{ticker}.csv")
85
+
86
+ def update_the_dataframe
87
+ if @df_path.exist?
88
+ @df = SQA::DataFrame.load(
89
+ source: @df_path,
90
+ transformers: @transformers
91
+ )
92
+ else
93
+ @df = klass.recent(@ticker, full: true)
94
+ @df.to_csv(@df_path)
95
+ return
53
96
  end
54
97
 
55
- # Adding a ticker vector in case I want to do
56
- # some multi-stock analysis in the same data frame.
57
- # For example to see how one stock coorelates with another.
58
- @df[:ticker] = @ticker
98
+ from_date = Date.parse(@df.timestamp.last) + 1
99
+ df2 = klass.recent(@ticker, from_date: from_date)
100
+
101
+ return if df2.nil? # CSV file is up to date.
102
+
103
+ df_nrows = @df.nrows
104
+ @df.append(df2)
105
+
106
+ if @df.nrows > df_nrows
107
+ @df.to_csv(file_path)
108
+ end
59
109
  end
60
110
 
111
+
61
112
  def to_s
62
113
  "#{ticker} with #{@df.size} data points from #{@df.timestamp.first} to #{@df.timestamp.last}"
63
114
  end
115
+ alias_method :inspect, :to_s
64
116
 
65
- # TODO: Turn this into a class Stock::Overview
66
- # which is a sub-class of Hashie::Dash
67
- def overview
68
- return @overview unless @overview.nil?
69
117
 
118
+ def merge_overview
70
119
  temp = JSON.parse(
71
- CONNECTION.get("/query?function=OVERVIEW&symbol=#{@ticker.upcase}&apikey=#{Nenv.av_api_key}")
120
+ CONNECTION.get("/query?function=OVERVIEW&symbol=#{ticker.upcase}&apikey=#{SQA.av.key}")
72
121
  .to_hash[:body]
73
122
  )
74
123
 
124
+ if temp.has_key? "Information"
125
+ ApiError.raise(temp["Information"])
126
+ end
127
+
75
128
  # TODO: CamelCase hash keys look common in Alpha Vantage
76
129
  # JSON; look at making a special Hashie-based class
77
130
  # to convert the keys to normal Ruby standards.
78
131
 
79
132
  temp2 = {}
80
133
 
81
- string_values = %w[ address asset_type cik country currency description dividend_date ex_dividend_date exchange fiscal_year_end industry latest_quarter name sector symbol ]
134
+ string_values = %w[ address asset_type cik country currency
135
+ description dividend_date ex_dividend_date
136
+ exchange fiscal_year_end industry latest_quarter
137
+ name sector symbol
138
+ ]
82
139
 
83
140
  temp.keys.each do |k|
84
141
  new_k = k.underscore
85
142
  temp2[new_k] = string_values.include?(new_k) ? temp[k] : temp[k].to_f
86
143
  end
87
144
 
88
- @overview = Hashie::Mash.new temp2
145
+ @data.overview = temp2
89
146
  end
90
147
 
91
148
 
@@ -101,13 +158,13 @@ class SQA::Stock
101
158
  def top
102
159
  return @@top unless @@top.nil?
103
160
 
104
- mash = Hashie::Mash.new(
105
- JSON.parse(
106
- CONNECTION.get(
107
- "/query?function=TOP_GAINERS_LOSERS&apikey=#{Nenv.av_api_key}"
108
- ).to_hash[:body]
109
- )
110
- )
161
+ a_hash = JSON.parse(
162
+ CONNECTION.get(
163
+ "/query?function=TOP_GAINERS_LOSERS&apikey=#{SQA.av.key}"
164
+ ).to_hash[:body]
165
+ )
166
+
167
+ mash = Hashie::Mash.new(a_hash)
111
168
 
112
169
  keys = mash.top_gainers.first.keys
113
170
 
data/lib/sqa/strategy.rb CHANGED
@@ -8,7 +8,7 @@ class SQA::Strategy
8
8
  end
9
9
 
10
10
  def add(a_strategy)
11
- raise SQA::BadParameterError unless [Class, Method].include? a_strategy.class
11
+ raise BadParameterError unless [Class, Method].include? a_strategy.class
12
12
 
13
13
  a_proc = if Class == a_strategy.class
14
14
  a_strategy.method(:trade)
data/lib/sqa/version.rb CHANGED
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sem_version'
4
- require 'sem_version/core_ext'
5
-
6
3
  module SQA
7
- VERSION = "0.0.14"
4
+ VERSION = "0.0.17"
8
5
 
9
6
  class << self
10
7
  def version
data/lib/sqa/web.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # lib/sqa/command/web.rb
2
2
 
3
- require 'tty-option'
3
+ # require 'tty-option'
4
4
 
5
5
 
6
6
  module SQA
data/lib/sqa.rb CHANGED
@@ -1,77 +1,56 @@
1
1
  # lib/sqa.rb
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'active_support'
5
- require 'active_support/core_ext/string'
6
- require 'amazing_print'
7
- require 'daru'
8
- require 'date'
9
- require 'descriptive_statistics'
10
- require 'nenv'
11
- require 'pathname'
4
+ # TODO: Create a new gem for the dumbstockapi website
12
5
 
13
- require_relative "sqa/version"
14
- require_relative "sqa/errors"
6
+ #############################################
7
+ ## Standard Libraries
15
8
 
9
+ require 'date'
10
+ require 'pathname'
16
11
 
17
12
  unless defined?(HOME)
18
- HOME = Pathname.new(Nenv.home)
13
+ HOME = Pathname.new(ENV['HOME'])
19
14
  end
20
15
 
16
+ #############################################
17
+ ## Additional Libraries
21
18
 
22
- module SQA
23
- class << self
24
- @@config = nil
25
-
26
- # Initializes the SQA modules
27
- # returns the configuration
28
- #
29
- def init(argv=ARGV)
30
- if argv.is_a? String
31
- argv = argv.split()
32
- end
33
-
34
-
35
- # Ran at SQA::Config elaboration time
36
- # @@config = Config.new
37
-
38
- if defined? CLI
39
- CLI.run(argv)
40
- else
41
- # There are no real command line parameters
42
- # because the sqa gem is being required within
43
- # the context of a larger program.
44
- end
45
-
46
- config.data_dir = homify(config.data_dir)
19
+ require 'active_support/core_ext/string'
20
+ require 'alphavantage' # TODO: add rate limiter to it; ** PR submitted! **
21
+ require 'api_key_manager'
22
+ require 'amazing_print'
23
+ require 'descriptive_statistics'
24
+ require 'faraday'
25
+ require 'hashie'
26
+ require 'nenv'
27
+ require 'sem_version'
28
+ require 'sem_version/core_ext'
29
+ require 'tty-option'
30
+ require 'tty-table'
47
31
 
48
- Daru.lazy_update = config.lazy_update
49
- Daru.plotting_library = config.plotting_library
50
32
 
51
- config
52
- end
33
+ #############################################
34
+ ## SQA soecufuc code
53
35
 
54
- def debug?() = @@config.debug?
55
- def verbose?() = @@config.verbose?
36
+ require_relative "sqa/version"
37
+ require_relative "sqa/errors"
56
38
 
57
- def homify(filepath) = filepath.gsub(/^~/, Nenv.home)
58
- def data_dir() = Pathname.new(config.data_dir)
59
- def config() = @@config
39
+ require_relative 'sqa/init.rb'
60
40
 
61
- def config=(an_object)
62
- @@config = an_object
63
- end
64
- end
65
- end
66
41
 
67
- # require_relative "patches/daru" # TODO: extract Daru::DataFrame in new gem sqa-data_frame
42
+ # TODO: Some of these components make direct calls to the
43
+ # Alpha Vantage API. Convert them to use the
44
+ # alphavantage gem.
68
45
 
69
46
  require_relative "sqa/config"
70
- require_relative "sqa/constants"
71
- require_relative "sqa/data_frame"
47
+ require_relative "sqa/constants" # SMELL: more app than gem
48
+ require_relative "sqa/data_frame" # TODO: drop the daru gem
72
49
  require_relative "sqa/indicator"
73
50
  require_relative "sqa/portfolio"
74
51
  require_relative "sqa/strategy"
75
52
  require_relative "sqa/stock"
76
53
  require_relative "sqa/ticker"
77
- require_relative "sqa/trade"
54
+ require_relative "sqa/trade" # SMELL: Not really a core gem; more of an application thing
55
+
56
+
metadata CHANGED
@@ -1,45 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-23 00:00:00.000000000 Z
11
+ date: 2023-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: daru
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
17
+ - - '='
32
18
  - !ruby/object:Gem::Version
33
- version: '0'
19
+ version: 7.0.6
34
20
  type: :runtime
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
- - - ">="
24
+ - - '='
39
25
  - !ruby/object:Gem::Version
40
- version: '0'
26
+ version: 7.0.6
41
27
  - !ruby/object:Gem::Dependency
42
- name: descriptive_statistics
28
+ name: alphavantage
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - ">="
@@ -53,7 +39,7 @@ dependencies:
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
- name: hashie
42
+ name: api_key_manager
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - ">="
@@ -67,35 +53,7 @@ dependencies:
67
53
  - !ruby/object:Gem::Version
68
54
  version: '0'
69
55
  - !ruby/object:Gem::Dependency
70
- name: nenv
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: tty-logger
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: tty-markdown
56
+ name: descriptive_statistics
99
57
  requirement: !ruby/object:Gem::Requirement
100
58
  requirements:
101
59
  - - ">="
@@ -109,7 +67,7 @@ dependencies:
109
67
  - !ruby/object:Gem::Version
110
68
  version: '0'
111
69
  - !ruby/object:Gem::Dependency
112
- name: tty-option
70
+ name: faraday
113
71
  requirement: !ruby/object:Gem::Requirement
114
72
  requirements:
115
73
  - - ">="
@@ -123,21 +81,21 @@ dependencies:
123
81
  - !ruby/object:Gem::Version
124
82
  version: '0'
125
83
  - !ruby/object:Gem::Dependency
126
- name: tty-progressbar
84
+ name: hashie
127
85
  requirement: !ruby/object:Gem::Requirement
128
86
  requirements:
129
- - - ">="
87
+ - - "~>"
130
88
  - !ruby/object:Gem::Version
131
- version: '0'
89
+ version: 4.1.0
132
90
  type: :runtime
133
91
  prerelease: false
134
92
  version_requirements: !ruby/object:Gem::Requirement
135
93
  requirements:
136
- - - ">="
94
+ - - "~>"
137
95
  - !ruby/object:Gem::Version
138
- version: '0'
96
+ version: 4.1.0
139
97
  - !ruby/object:Gem::Dependency
140
- name: tty-prompt
98
+ name: nenv
141
99
  requirement: !ruby/object:Gem::Requirement
142
100
  requirements:
143
101
  - - ">="
@@ -151,7 +109,7 @@ dependencies:
151
109
  - !ruby/object:Gem::Version
152
110
  version: '0'
153
111
  - !ruby/object:Gem::Dependency
154
- name: tty-reader
112
+ name: sem_version
155
113
  requirement: !ruby/object:Gem::Requirement
156
114
  requirements:
157
115
  - - ">="
@@ -165,7 +123,7 @@ dependencies:
165
123
  - !ruby/object:Gem::Version
166
124
  version: '0'
167
125
  - !ruby/object:Gem::Dependency
168
- name: tty-spinner
126
+ name: tty-option
169
127
  requirement: !ruby/object:Gem::Requirement
170
128
  requirements:
171
129
  - - ">="
@@ -282,7 +240,8 @@ files:
282
240
  - checksums/sqa-0.0.11.gem.sha512
283
241
  - checksums/sqa-0.0.12.gem.sha512
284
242
  - checksums/sqa-0.0.13.gem.sha512
285
- - checksums/sqa-0.0.14.gem.sha512
243
+ - checksums/sqa-0.0.15.gem.sha512
244
+ - checksums/sqa-0.0.17.gem.sha512
286
245
  - checksums/sqa-0.0.2.gem.sha512
287
246
  - checksums/sqa-0.0.3.gem.sha512
288
247
  - checksums/sqa-0.0.4.gem.sha512
@@ -293,6 +252,7 @@ files:
293
252
  - checksums/sqa-0.0.9.gem.sha512
294
253
  - docs/.gitignore
295
254
  - docs/README.md
255
+ - docs/alpha_vantage_technical_indicators.md
296
256
  - docs/average_true_range.md
297
257
  - docs/bollinger_bands.md
298
258
  - docs/candlestick_pattern_recognizer.md
@@ -316,14 +276,6 @@ files:
316
276
  - docs/stochastic_oscillator.md
317
277
  - docs/strategy.md
318
278
  - docs/true_range.md
319
- - lib/patches/daru.rb
320
- - lib/patches/daru/category.rb
321
- - lib/patches/daru/data_frame.rb
322
- - lib/patches/daru/plotting/svg-graph.rb
323
- - lib/patches/daru/plotting/svg-graph/category.rb
324
- - lib/patches/daru/plotting/svg-graph/dataframe.rb
325
- - lib/patches/daru/plotting/svg-graph/vector.rb
326
- - lib/patches/daru/vector.rb
327
279
  - lib/sqa.rb
328
280
  - lib/sqa/activity.rb
329
281
  - lib/sqa/analysis.rb
@@ -356,6 +308,7 @@ files:
356
308
  - lib/sqa/indicator/simple_moving_average_trend.rb
357
309
  - lib/sqa/indicator/stochastic_oscillator.rb
358
310
  - lib/sqa/indicator/true_range.rb
311
+ - lib/sqa/init.rb
359
312
  - lib/sqa/portfolio.rb
360
313
  - lib/sqa/stock.rb
361
314
  - lib/sqa/strategy.rb
@@ -379,7 +332,8 @@ licenses:
379
332
  metadata:
380
333
  allowed_push_host: https://rubygems.org
381
334
  homepage_uri: https://github.com/MadBomber/sqa
382
- source_code_uri: https://github.com/MadBomber/sta
335
+ source_code_uri: https://github.com/MadBomber/sqa
336
+ changelog_uri: https://github.com/MadBomber/sqa
383
337
  post_install_message:
384
338
  rdoc_options: []
385
339
  require_paths:
@@ -395,7 +349,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
395
349
  - !ruby/object:Gem::Version
396
350
  version: '0'
397
351
  requirements: []
398
- rubygems_version: 3.4.19
352
+ rubygems_version: 3.4.20
399
353
  signing_key:
400
354
  specification_version: 4
401
355
  summary: sqa - Stock Qualitative Analysis
@@ -1 +0,0 @@
1
- c5eea4fb74e6ab7b7b1715a81598585ec540a3eb1e9b902ca339ff0a2ba9cc7624ae3f43e00b5fbbad1d3d510ed938e8f91154c0bee2e14d6503564b1b0ccfb1
@@ -1,19 +0,0 @@
1
- # lib/patches/daru/category.rb
2
-
3
- module Daru
4
- module Category
5
-
6
- def plotting_lig lib
7
- if :svg_graph = lib
8
- @plotting_library = lib
9
- if Daru.send("has_#{lib}?".to_sym)
10
- extend Module.const_get(
11
- "Daru::Plotting::Category::#{lib.to_s.capitalize}Library"
12
- )
13
- end
14
- else
15
- super
16
- end
17
- end
18
- end
19
- end
@@ -1,19 +0,0 @@
1
- # lib/patches/daru/data_frame.rb
2
-
3
- module Daru
4
- module DataFrame
5
-
6
- def plotting_lig lib
7
- if :svg_graph = lib
8
- @plotting_library = lib
9
- if Daru.send("has_#{lib}?".to_sym)
10
- extend Module.const_get(
11
- "Daru::Plotting::DataFrame::#{lib.to_s.capitalize}Library"
12
- )
13
- end
14
- else
15
- super
16
- end
17
- end
18
- end
19
- end