gooddata 0.6.0.pre11 → 0.6.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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +12 -1
  3. data/.yardopts +2 -0
  4. data/README.md +6 -3
  5. data/Rakefile +24 -7
  6. data/gooddata +2 -2
  7. data/gooddata.gemspec +4 -3
  8. data/lib/gooddata.rb +17 -12
  9. data/lib/gooddata/bricks/base_downloader.rb +7 -7
  10. data/lib/gooddata/bricks/brick.rb +7 -8
  11. data/lib/gooddata/bricks/bricks.rb +4 -1
  12. data/lib/gooddata/bricks/middleware/base_middleware.rb +2 -2
  13. data/lib/gooddata/bricks/middleware/bench_middleware.rb +5 -6
  14. data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +21 -22
  15. data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +3 -4
  16. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +14 -14
  17. data/lib/gooddata/bricks/middleware/logger_middleware.rb +6 -6
  18. data/lib/gooddata/bricks/middleware/middleware.rb +4 -1
  19. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +29 -32
  20. data/lib/gooddata/bricks/middleware/stdout_middleware.rb +5 -5
  21. data/lib/gooddata/bricks/middleware/twitter_middleware.rb +6 -8
  22. data/lib/gooddata/bricks/utils.rb +3 -3
  23. data/lib/gooddata/cli/cli.rb +4 -2
  24. data/lib/gooddata/cli/commands/api_cmd.rb +6 -4
  25. data/lib/gooddata/cli/commands/auth_cmd.rb +5 -3
  26. data/lib/gooddata/cli/commands/console_cmd.rb +1 -1
  27. data/lib/gooddata/cli/commands/process_cmd.rb +6 -4
  28. data/lib/gooddata/cli/commands/profile_cmd.rb +5 -3
  29. data/lib/gooddata/cli/commands/project_cmd.rb +24 -22
  30. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +12 -10
  31. data/lib/gooddata/cli/commands/scaffold_cmd.rb +8 -6
  32. data/lib/gooddata/cli/hooks.rb +4 -2
  33. data/lib/gooddata/cli/shared.rb +3 -1
  34. data/lib/gooddata/cli/terminal.rb +16 -0
  35. data/lib/gooddata/client.rb +28 -22
  36. data/lib/gooddata/commands/api.rb +43 -26
  37. data/lib/gooddata/commands/auth.rb +22 -53
  38. data/lib/gooddata/commands/base.rb +2 -0
  39. data/lib/gooddata/commands/commands.rb +3 -0
  40. data/lib/gooddata/commands/datasets.rb +39 -136
  41. data/lib/gooddata/commands/process.rb +134 -130
  42. data/lib/gooddata/commands/profile.rb +2 -0
  43. data/lib/gooddata/commands/projects.rb +91 -129
  44. data/lib/gooddata/commands/runners.rb +11 -11
  45. data/lib/gooddata/commands/scaffold.rb +28 -26
  46. data/lib/gooddata/connection.rb +61 -68
  47. data/lib/gooddata/core/core.rb +1 -2
  48. data/lib/gooddata/data/data.rb +7 -0
  49. data/lib/gooddata/data/guesser.rb +114 -0
  50. data/lib/gooddata/exceptions/command_failed.rb +7 -0
  51. data/lib/gooddata/exceptions/exceptions.rb +7 -0
  52. data/lib/gooddata/{exceptions.rb → exceptions/project_not_found.rb} +2 -2
  53. data/lib/gooddata/extensions/big_decimal.rb +5 -0
  54. data/lib/gooddata/extract.rb +2 -0
  55. data/lib/gooddata/goodzilla/goodzilla.rb +11 -12
  56. data/lib/gooddata/helpers.rb +49 -35
  57. data/lib/gooddata/models/attribute.rb +7 -5
  58. data/lib/gooddata/models/dashboard.rb +44 -45
  59. data/lib/gooddata/models/data_result.rb +10 -13
  60. data/lib/gooddata/models/data_set.rb +6 -6
  61. data/lib/gooddata/models/display_form.rb +4 -4
  62. data/lib/gooddata/models/empty_result.rb +4 -3
  63. data/lib/gooddata/models/fact.rb +5 -5
  64. data/lib/gooddata/models/links.rb +3 -1
  65. data/lib/gooddata/models/metadata.rb +34 -32
  66. data/lib/gooddata/models/metric.rb +33 -34
  67. data/lib/gooddata/models/model.rb +165 -173
  68. data/lib/gooddata/models/models.rb +3 -0
  69. data/lib/gooddata/models/process.rb +18 -17
  70. data/lib/gooddata/models/profile.rb +3 -1
  71. data/lib/gooddata/models/project.rb +107 -35
  72. data/lib/gooddata/models/project_metadata.rb +12 -12
  73. data/lib/gooddata/models/report.rb +31 -30
  74. data/lib/gooddata/models/report_data_result.rb +22 -19
  75. data/lib/gooddata/models/report_definition.rb +101 -80
  76. data/lib/gooddata/version.rb +5 -3
  77. data/lib/templates/bricks/brick.rb.erb +3 -3
  78. data/lib/templates/bricks/main.rb.erb +3 -2
  79. data/lib/templates/project/Goodfile.erb +2 -2
  80. data/lib/templates/project/model/model.rb.erb +19 -19
  81. data/spec/data/.gooddata +4 -0
  82. data/spec/helpers/blueprint_helper.rb +2 -2
  83. data/spec/helpers/cli_helper.rb +28 -0
  84. data/spec/helpers/connection_helper.rb +2 -2
  85. data/spec/integration/command_projects_spec.rb +1 -1
  86. data/spec/integration/create_from_template_spec.rb +12 -0
  87. data/spec/integration/full_project_spec.rb +2 -2
  88. data/spec/integration/partial_md_export_import_spec.rb +36 -0
  89. data/spec/logging_in_logging_out_spec.rb +1 -1
  90. data/spec/spec_helper.rb +29 -2
  91. data/spec/unit/cli/cli_spec.rb +3 -3
  92. data/spec/unit/cli/commands/cmd_api_spec.rb +21 -4
  93. data/spec/unit/cli/commands/cmd_auth_spec.rb +2 -4
  94. data/spec/unit/cli/commands/cmd_process_spec.rb +20 -4
  95. data/spec/unit/cli/commands/cmd_profile_spec.rb +9 -4
  96. data/spec/unit/cli/commands/cmd_project_spec.rb +53 -4
  97. data/spec/unit/cli/commands/cmd_run_ruby_spec.rb +2 -4
  98. data/spec/unit/cli/commands/cmd_scaffold_spec.rb +14 -4
  99. data/spec/unit/commands/command_api_spec.rb +21 -2
  100. data/spec/unit/commands/command_auth_spec.rb +62 -1
  101. data/spec/unit/commands/command_dataset_spec.rb +31 -3
  102. data/spec/unit/commands/command_process_spec.rb +75 -1
  103. data/spec/unit/commands/command_profile_spec.rb +7 -1
  104. data/spec/unit/commands/command_projects_spec.rb +1 -1
  105. data/spec/unit/commands/command_scaffold_spec.rb +46 -1
  106. data/spec/unit/core/connection_spec.rb +1 -0
  107. data/spec/unit/data/guesser_spec.rb +54 -0
  108. data/spec/unit/helpers_spec.rb +47 -0
  109. data/spec/unit/model/schema_builder_spec.rb +2 -0
  110. data/spec/unit/model/tools_spec.rb +89 -0
  111. data/test/test_upload.rb +39 -15
  112. metadata +98 -75
  113. data/test/test_commands.rb +0 -85
  114. data/test/test_guessing.rb +0 -46
  115. data/test/test_model.rb +0 -81
  116. data/test/test_rest_api_basic.rb +0 -41
@@ -1,24 +1,25 @@
1
+ # encoding: UTF-8
2
+
1
3
  module GoodData::Command
2
4
  class Runners
3
-
4
5
  def self.run_ruby_locally(brick_dir, options={})
5
6
  pid = options[:project_id]
6
- fail "You have to specify a project ID" if pid.nil?
7
- fail "You have to specify directory of the brick run" if brick_dir.nil?
8
- fail "You specified file as a birck run directory. You have to specify directory." if File.exist?(brick_dir) && !File.directory?(brick_dir)
7
+ fail 'You have to specify a project ID' if pid.nil?
8
+ fail 'You have to specify directory of the brick run' if brick_dir.nil?
9
+ fail 'You specified file as a birck run directory. You have to specify directory.' if File.exist?(brick_dir) && !File.directory?(brick_dir)
9
10
 
10
11
  params = options[:expanded_params] || {}
11
12
 
12
13
  GoodData.connection.connect!
13
- sst = GoodData.connection.cookies[:cookies]["GDCAuthSST"]
14
+ sst = GoodData.connection.cookies[:cookies]['GDCAuthSST']
14
15
  pwd = Pathname.new(Dir.pwd)
15
16
  logger_stream = STDOUT
16
17
 
17
18
  server_uri = URI(options[:server]) unless options[:server].nil?
18
- scheme = server_uri.nil? ? "" : server_uri.scheme
19
- hostname = server_uri.nil? ? "" : server_uri.host
19
+ scheme = server_uri.nil? ? '' : server_uri.scheme
20
+ hostname = server_uri.nil? ? '' : server_uri.host
20
21
 
21
- script_body = <<-script_body
22
+ script_body = <<-script_body
22
23
  require 'fileutils'
23
24
  FileUtils::cd(\"#{pwd+brick_dir}\") do\
24
25
  require 'bundler/setup'
@@ -33,12 +34,11 @@ script_body = <<-script_body
33
34
  }.merge(#{params})
34
35
  eval(File.read(\"./main.rb\"))
35
36
  end
36
- script_body
37
+ script_body
37
38
 
38
39
  Bundler.with_clean_env do
39
- system("ruby", "-e", script_body)
40
+ system('ruby', '-e', script_body)
40
41
  end
41
42
  end
42
-
43
43
  end
44
44
  end
@@ -1,61 +1,63 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'erubis'
4
+ require 'fileutils'
5
+
1
6
  module GoodData::Command
2
7
  class Scaffold
3
- class << self
8
+ TEMPLATES_PATH = Pathname(__FILE__) + '../../../templates'
4
9
 
10
+ class << self
11
+ # Scaffolds new project
12
+ # TODO: Add option for custom output dir
5
13
  def project(name)
6
- require 'erubis'
7
- require 'fileutils'
8
-
9
- templates_path = Pathname(__FILE__) + "../../../templates"
14
+ fail ArgumentError, 'No name specified' if name.nil?
10
15
 
11
16
  FileUtils.mkdir(name)
12
17
  FileUtils.cd(name) do
13
18
 
14
- FileUtils.mkdir("model")
15
- FileUtils.cd("model") do
16
- input = File.read(templates_path + 'project/model/model.rb.erb')
19
+ FileUtils.mkdir('model')
20
+ FileUtils.cd('model') do
21
+ input = File.read(TEMPLATES_PATH + 'project/model/model.rb.erb')
17
22
  eruby = Erubis::Eruby.new(input)
18
- File.open("model.rb", 'w') do |f|
23
+ File.open('model.rb', 'w') do |f|
19
24
  f.write(eruby.result(:name => name))
20
25
  end
21
26
  end
22
27
 
23
- FileUtils.mkdir("data")
24
- FileUtils.cd("data") do
25
- FileUtils.cp(Dir.glob(templates_path + 'project/data/*.csv'), ".")
28
+ FileUtils.mkdir('data')
29
+ FileUtils.cd('data') do
30
+ FileUtils.cp(Dir.glob(TEMPLATES_PATH + 'project/data/*.csv'), '.')
26
31
  end
27
32
 
28
- input = File.read(templates_path + 'project/Goodfile.erb')
33
+ input = File.read(TEMPLATES_PATH + 'project/Goodfile.erb')
29
34
  eruby = Erubis::Eruby.new(input)
30
- File.open("Goodfile", 'w') do |f|
35
+ File.open('Goodfile', 'w') do |f|
31
36
  f.write(eruby.result())
32
37
  end
33
38
  end
34
39
  end
35
40
 
41
+ # Scaffolds new brick
42
+ # TODO: Add option for custom output dir
36
43
  def brick(name)
37
-
38
- require 'erubis'
39
- require 'fileutils'
40
-
41
- templates_path = Pathname(__FILE__) + "../../../templates"
42
-
44
+ fail ArgumentError, 'No name specified' if name.nil?
45
+
43
46
  FileUtils.mkdir(name)
44
47
  FileUtils.cd(name) do
45
- input = File.read(templates_path + 'bricks/brick.rb.erb')
48
+ input = File.read(TEMPLATES_PATH + 'bricks/brick.rb.erb')
46
49
  eruby = Erubis::Eruby.new(input)
47
- File.open("brick.rb", 'w') do |f|
50
+ File.open('brick.rb', 'w') do |f|
48
51
  f.write(eruby.result())
49
52
  end
50
-
51
- input = File.read(templates_path + 'bricks/main.rb.erb')
53
+
54
+ input = File.read(TEMPLATES_PATH + 'bricks/main.rb.erb')
52
55
  eruby = Erubis::Eruby.new(input)
53
- File.open("main.rb", 'w') do |f|
56
+ File.open('main.rb', 'w') do |f|
54
57
  f.write(eruby.result())
55
58
  end
56
59
  end
57
60
  end
58
-
59
61
  end
60
62
  end
61
63
  end
@@ -1,10 +1,11 @@
1
- require 'json'
1
+ # encoding: UTF-8
2
+
3
+ require 'multi_json'
2
4
  require 'rest-client'
3
5
 
4
- require File.join(File.dirname(__FILE__), 'version')
6
+ require_relative 'version'
5
7
 
6
8
  module GoodData
7
-
8
9
  # # GoodData HTTP wrapper
9
10
  #
10
11
  # Provides a convenient HTTP wrapper for talking with the GoodData API.
@@ -27,7 +28,6 @@ module GoodData
27
28
  # To send a HTTP request use either the get, post or delete methods documented below.
28
29
  #
29
30
  class Connection
30
-
31
31
  DEFAULT_URL = 'https://secure.gooddata.com'
32
32
  LOGIN_PATH = '/gdc/account/login'
33
33
  TOKEN_PATH = '/gdc/account/token'
@@ -46,7 +46,7 @@ module GoodData
46
46
  # end
47
47
  #
48
48
  def retryable(options = {}, &block)
49
- opts = { :tries => 1, :on => Exception }.merge(options)
49
+ opts = {:tries => 1, :on => Exception}.merge(options)
50
50
 
51
51
  retry_exception, retries = opts[:on], opts[:tries]
52
52
 
@@ -67,15 +67,14 @@ module GoodData
67
67
  # @param password The GoodData account password
68
68
  #
69
69
  def initialize(username, password, options = {})
70
- @status = :not_connected
71
- @username = username
72
- @password = password
73
- @url = options[:server] || DEFAULT_URL
70
+ @status = :not_connected
71
+ @username = username
72
+ @password = password
73
+ @url = options[:server] || DEFAULT_URL
74
74
  @auth_token = options[:gdc_temporary_token]
75
- @options = options
75
+ @options = options
76
76
 
77
77
  @server = create_server_connection(@url, @options)
78
-
79
78
  end
80
79
 
81
80
  # Returns the user JSON object of the currently logged in GoodData user account.
@@ -158,7 +157,7 @@ module GoodData
158
157
 
159
158
  # Get the cookies associated with the current connection.
160
159
  def cookies
161
- @cookies ||= { :cookies => {} }
160
+ @cookies ||= {:cookies => {}}
162
161
  end
163
162
 
164
163
  # Set the cookies used when communicating with the GoodData API.
@@ -189,13 +188,12 @@ module GoodData
189
188
  # /uploads/ resources are special in that they use a different
190
189
  # host and a basic authentication.
191
190
  def upload(file, options={})
192
-
193
191
  ensure_connection
194
192
 
195
193
  dir = options[:directory] || ''
196
194
  staging_uri = options[:staging_url].to_s
197
195
  url = dir.empty? ? staging_uri : URI.join(staging_uri, "#{dir}/").to_s
198
-
196
+
199
197
  # Make a directory, if needed
200
198
  unless dir.empty? then
201
199
  method = :get
@@ -203,46 +201,46 @@ module GoodData
203
201
  begin
204
202
  # first check if it does exits
205
203
  RestClient::Request.execute({
206
- :method => method,
207
- :url => url,
208
- :timeout => @options[:timeout],
209
- :headers => {
210
- :user_agent => GoodData.gem_version_string
211
- }}.merge(cookies)
204
+ :method => method,
205
+ :url => url,
206
+ :timeout => @options[:timeout],
207
+ :headers => {
208
+ :user_agent => GoodData.gem_version_string
209
+ }}.merge(cookies)
212
210
  )
213
211
  rescue RestClient::Exception => e
214
212
  if e.http_code == 404 then
215
213
  method = :mkcol
216
214
  GoodData.logger.debug "#{method}: #{url}"
217
215
  RestClient::Request.execute({
218
- :method => method,
219
- :url => url,
220
- :timeout => @options[:timeout],
221
- :headers => {
222
- :user_agent => GoodData.gem_version_string
223
- }}.merge(cookies)
216
+ :method => method,
217
+ :url => url,
218
+ :timeout => @options[:timeout],
219
+ :headers => {
220
+ :user_agent => GoodData.gem_version_string
221
+ }}.merge(cookies)
224
222
  )
225
223
  end
226
224
  end
227
225
  end
228
226
 
229
- payload = options[:stream] ? "file" : File.read(file)
230
- filename = options[:filename] || options[:stream] ? "randome-filename.txt" : File.basename(file)
227
+ payload = options[:stream] ? 'file' : File.read(file)
228
+ filename = options[:filename] || options[:stream] ? 'randome-filename.txt' : File.basename(file)
231
229
 
232
230
  # Upload the file
233
231
  # puts "uploading the file #{URI.join(url, filename).to_s}"
234
232
  req = RestClient::Request.new({
235
- :method => :put,
236
- :url => URI.join(url, filename).to_s,
237
- :timeout => @options[:timeout],
238
- :headers => {
239
- :user_agent => GoodData.gem_version_string,
240
- },
241
- :payload => payload,
242
- :raw_response => true,
243
- :user => @username,
244
- :password => @password
245
- })
233
+ :method => :put,
234
+ :url => URI.join(url, filename).to_s,
235
+ :timeout => @options[:timeout],
236
+ :headers => {
237
+ :user_agent => GoodData.gem_version_string,
238
+ },
239
+ :payload => payload,
240
+ :raw_response => true,
241
+ :user => @username,
242
+ :password => @password
243
+ })
246
244
  # .merge(cookies))
247
245
  resp = req.execute
248
246
  true
@@ -252,11 +250,11 @@ module GoodData
252
250
  staging_uri = options[:staging_url].to_s
253
251
  url = staging_uri + what
254
252
  req = RestClient::Request.new({
255
- :method => 'GET',
256
- :url => url,
257
- :user => @username,
258
- :password => @password
259
- })
253
+ :method => 'GET',
254
+ :url => url,
255
+ :user => @username,
256
+ :password => @password
257
+ })
260
258
 
261
259
  if where.is_a?(String)
262
260
  File.open(where, 'w') do |f|
@@ -277,8 +275,8 @@ module GoodData
277
275
  end
278
276
 
279
277
  def disconnect
280
- if connected? && GoodData.connection.user["state"]
281
- GoodData.delete(GoodData.connection.user["state"])
278
+ if connected? && GoodData.connection.user['state']
279
+ GoodData.delete(GoodData.connection.user['state'])
282
280
  @status = :not_connected
283
281
  end
284
282
  end
@@ -287,12 +285,12 @@ module GoodData
287
285
 
288
286
  def create_server_connection(url, options)
289
287
  RestClient::Resource.new url,
290
- :timeout => options[:timeout],
291
- :headers => {
292
- :content_type => :json,
293
- :accept => [ :json, :zip ],
294
- :user_agent => GoodData::gem_version_string,
295
- }
288
+ :timeout => options[:timeout],
289
+ :headers => {
290
+ :content_type => :json,
291
+ :accept => [:json, :zip],
292
+ :user_agent => GoodData::gem_version_string,
293
+ }
296
294
  end
297
295
 
298
296
  def ensure_connection
@@ -300,7 +298,7 @@ module GoodData
300
298
  end
301
299
 
302
300
  def connect
303
- GoodData.logger.info "Connecting to GoodData..."
301
+ GoodData.logger.info 'Connecting to GoodData...'
304
302
  @status = :connecting
305
303
  authenticate
306
304
  end
@@ -313,7 +311,7 @@ module GoodData
313
311
  'remember' => 1
314
312
  }
315
313
  }
316
- GoodData.logger.debug "Logging in..."
314
+ GoodData.logger.debug 'Logging in...'
317
315
  @user = post(LOGIN_PATH, credentials, :dont_reauth => true)['userLogin']
318
316
  refresh_token :dont_reauth => true # avoid infinite loop if refresh_token fails with 401
319
317
 
@@ -334,19 +332,19 @@ module GoodData
334
332
  return response if options[:process] == false
335
333
 
336
334
  if content_type == "application/json" || content_type == "application/json;charset=UTF-8" then
337
- result = response.to_str == '""' ? {} : JSON.parse(response.to_str)
335
+ result = response.to_str == '""' ? {} : MultiJson.load(response.to_str)
338
336
  GoodData.logger.debug "Response: #{result.inspect}"
339
- elsif content_type == "application/zip" then
337
+ elsif content_type == 'application/zip' then
340
338
  result = response
341
- GoodData.logger.debug "Response: a zipped stream"
339
+ GoodData.logger.debug 'Response: a zipped stream'
342
340
  elsif response.headers[:content_length].to_s == '0'
343
341
  result = nil
344
- GoodData.logger.debug "Response: Empty response possibly 204"
342
+ GoodData.logger.debug 'Response: Empty response possibly 204'
345
343
  elsif response.code == 204
346
344
  result = nil
347
- GoodData.logger.debug "Response: 204 no content"
345
+ GoodData.logger.debug 'Response: 204 no content'
348
346
  else
349
- raise "Unsupported response content type '%s':\n%s" % [ content_type, response.to_str[0..127] ]
347
+ raise "Unsupported response content type '%s':\n%s" % [content_type, response.to_str[0..127]]
350
348
  end
351
349
  result
352
350
  rescue RestClient::Exception => e
@@ -356,7 +354,7 @@ module GoodData
356
354
  end
357
355
 
358
356
  def refresh_token(options = {})
359
- GoodData.logger.debug "Getting authentication token..."
357
+ GoodData.logger.debug 'Getting authentication token...'
360
358
  begin
361
359
  get TOKEN_PATH, :dont_reauth => true # avoid infinite loop GET fails with 401
362
360
  rescue RestClient::Unauthorized
@@ -366,20 +364,15 @@ module GoodData
366
364
  end
367
365
 
368
366
  def scrub_params(params, keys)
369
- keys = keys.reduce([]) {|memo, k| memo.concat([k.to_s, k.to_sym])}
367
+ keys = keys.reduce([]) { |memo, k| memo.concat([k.to_s, k.to_sym]) }
370
368
 
371
369
  new_params = Marshal.load(Marshal.dump(params))
372
370
  GoodData::Helpers.hash_dfs(new_params) do |k, key|
373
371
  keys.each do |key_to_scrub|
374
- begin
375
- k[key_to_scrub] = ("*" * k[key_to_scrub].length) if k && k.has_key?(key_to_scrub) && k[key_to_scrub]
376
- rescue
377
- binding.pry
378
- end
372
+ k[key_to_scrub] = ('*' * k[key_to_scrub].length) if k && k.has_key?(key_to_scrub) && k[key_to_scrub]
379
373
  end
380
374
  end
381
375
  new_params
382
376
  end
383
-
384
377
  end
385
378
  end
@@ -1,7 +1,6 @@
1
- # require File.join(File.dirname(__FILE__), "")
1
+ # encoding: UTF-8
2
2
 
3
3
  module GoodData
4
-
5
4
  # Core of GoodData Gem
6
5
  class Core
7
6
  end
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+ require 'pathname'
3
+
4
+ base = Pathname(__FILE__).dirname.expand_path
5
+ Dir.glob(base + '*.rb').each do |file|
6
+ require file
7
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'date'
4
+
5
+ require_relative '../extract'
6
+ require_relative '../exceptions/command_failed'
7
+
8
+ module GoodData
9
+ module Data
10
+ ##
11
+ # Utility class to guess data types of a data stream by looking at first couple of rows
12
+ #
13
+ class Guesser
14
+ TYPES_PRIORITY = [:connection_point, :fact, :date, :attribute]
15
+ attr_reader :headers
16
+
17
+ class << self
18
+ def sort_types(types)
19
+ types.sort do |x, y|
20
+ TYPES_PRIORITY.index(x) <=> TYPES_PRIORITY.index(y)
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize(reader)
26
+ @reader = reader
27
+ @headers = reader.shift.map! { |h| h.to_s } or raise 'Empty data set'
28
+ @pros = {}; @cons = {}; @seen = {}
29
+ @headers.map do |h|
30
+ @cons[h.to_s] = {}
31
+ @pros[h.to_s] = {}
32
+ @seen[h.to_s] = {}
33
+ end
34
+ end
35
+
36
+ def guess(limit)
37
+ count = 0
38
+ while row = @reader.shift
39
+ break unless row && !row.empty? && count < limit
40
+ raise '%i fields in row %i, %i expected' % [row.size, count + 1, @headers.size] if row.size != @headers.size
41
+ row.each_with_index do |value, j|
42
+ header = @headers[j]
43
+ number = check_number(header, value)
44
+ date = check_date(header, value)
45
+ store_guess header, {@pros => :attribute} unless number || date
46
+ hash_increment @seen[header], value
47
+ end
48
+ count += 1
49
+ end
50
+ # fields with unique values are connection point candidates
51
+ @seen.each do |header, values|
52
+ store_guess header, {@pros => :connection_point} if values.size == count
53
+ end
54
+ guess_result
55
+ end
56
+
57
+ private
58
+
59
+ def guess_result
60
+ result = {}
61
+ @headers.each do |header|
62
+ result[header] = Guesser::sort_types @pros[header].keys.select { |type| @cons[header][type].nil? }
63
+ end
64
+ result
65
+ end
66
+
67
+ def hash_increment(hash, key)
68
+ if hash[key]
69
+ hash[key] += 1
70
+ else
71
+ hash[key] = 1
72
+ end
73
+ end
74
+
75
+ def check_number(header, value)
76
+ if value.nil? || value =~ /^[\+-]?\d*(\.\d*)?$/
77
+ return store_guess(header, @pros => [:fact, :attribute])
78
+ end
79
+ store_guess header, {@cons => :fact}
80
+ end
81
+
82
+ def check_date(header, value)
83
+ return store_guess(header, @pros => [:date, :attribute, :fact]) if value.nil? || value == '0000-00-00'
84
+ begin
85
+ DateTime.parse value
86
+ return store_guess(header, @pros => [:date, :attribute])
87
+ rescue ArgumentError;
88
+ end
89
+ store_guess header, {@cons => :date}
90
+ end
91
+
92
+ ##
93
+ # Stores a guess about given header.
94
+ #
95
+ # Returns true if the @pros key is present, false otherwise
96
+ #
97
+ # === Parameters
98
+ #
99
+ # * +header+ - A header name
100
+ # * +guess+ - A hash with optional @pros and @cons keys
101
+ #
102
+ def store_guess(header, guess)
103
+ result = !guess[@pros].nil?
104
+ [@pros, @cons].each do |hash|
105
+ if guess[hash] then
106
+ guess[hash] = [guess[hash]] unless guess[hash].is_a? Array
107
+ guess[hash].each { |type| hash_increment hash[header], type }
108
+ end
109
+ end
110
+ result
111
+ end
112
+ end
113
+ end
114
+ end