gd_es 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ = es
2
+
3
+ Describe your project here
4
+
5
+ :include:es.rdoc
6
+
data/bin/es ADDED
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env ruby
2
+ # 1.9 adds realpath to resolve symlinks; 1.8 doesn't
3
+ # have this method, so we add it so we get resolved symlinks
4
+ # and compatibility
5
+ unless File.respond_to? :realpath
6
+ class File #:nodoc:
7
+ def self.realpath path
8
+ return realpath(File.readlink(path)) if symlink?(path)
9
+ path
10
+ end
11
+ end
12
+ end
13
+ $: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
14
+ require 'rubygems'
15
+ require 'gli'
16
+ require 'es_version'
17
+ require 'gooddata'
18
+ require 'pp'
19
+ require 'logger'
20
+ require 'es'
21
+ require 'date'
22
+ require 'chronic'
23
+ require 'fastercsv'
24
+
25
+ include GLI
26
+
27
+ PID = ENV['PID']
28
+ ES_NAME = ENV['ES_NAME']
29
+ LOGIN = ENV['LOGIN']
30
+ PASSWORD = ENV['PASSWORD']
31
+
32
+ program_desc 'ES generator - Should help you with working with Event Store'
33
+ version Es::VERSION
34
+
35
+ desc 'Turn on HTTP logger'
36
+ arg_name 'log'
37
+ switch [:l,:logger]
38
+
39
+ desc 'GD server'
40
+ arg_name 'server'
41
+ flag [:s,:server]
42
+
43
+ desc 'WEBDAV server'
44
+ arg_name 'webdav'
45
+ flag [:w,:webdav]
46
+
47
+
48
+ desc 'Creates ES'
49
+ command :create do |c|
50
+ c.action do |global_options,options,args|
51
+ Es::Commands::create({
52
+ :pid => PID,
53
+ :es_name => ES_NAME
54
+ })
55
+ end
56
+ end
57
+
58
+ desc 'Delete ES'
59
+ command :delete do |c|
60
+ c.action do |global_options,options,args|
61
+ Es::Commands::delete({
62
+ :pid => PID,
63
+ :es_name => ES_NAME
64
+ })
65
+ end
66
+ end
67
+
68
+
69
+ desc 'Show all types that are supported.'
70
+ command :types do |c|
71
+ c.action do |global_options,options,args|
72
+ Es::Commands::get_types.each {|t| puts t}
73
+ end
74
+ end
75
+
76
+ desc 'Init ES'
77
+ command :init do |c|
78
+ c.desc 'Execute only for one entity.'
79
+ c.default_value false
80
+ c.flag [:o, :only]
81
+
82
+ c.desc 'Init also IsDeleted and DeletedAt columns.'
83
+ c.default_value false
84
+ c.switch [:d, :deleted]
85
+
86
+ c.desc 'Verbose mode'
87
+ c.default_value false
88
+ c.switch [:v, :verbose]
89
+
90
+ c.desc 'Base files directory.'
91
+ c.default_value nil
92
+ c.flag [:b, :basedir]
93
+
94
+ c.action do |global_options,options,args|
95
+ options[:pid] = PID
96
+ options[:es_name] = ES_NAME
97
+ Es::Commands::init(options)
98
+ end
99
+ end
100
+
101
+ desc 'Load data'
102
+ command :load do |c|
103
+ c.desc 'Execute only for one entity.'
104
+ c.default_value false
105
+ c.flag [:o, :only]
106
+
107
+ c.desc 'Print the task in the ugly oneliner mode for use in legacy tools. Does not run the actual extract.'
108
+ c.default_value false
109
+ c.switch [:j, :json]
110
+
111
+ c.desc 'Verbose mode'
112
+ c.default_value false
113
+ c.switch [:v, :verbose]
114
+
115
+ c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
116
+ c.default_value nil
117
+ c.flag [:b, :basedir]
118
+
119
+ c.action do |global_options,options,args|
120
+ options[:filenames] = args
121
+ options[:pattern] = "gen_load*.json"
122
+ options[:pid] = PID
123
+ options[:es_name] = ES_NAME
124
+ Es::Commands::load(options)
125
+ end
126
+ end
127
+
128
+ desc 'Load Deleted Records'
129
+ command :load_deleted do |c|
130
+
131
+ c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
132
+ c.default_value nil
133
+ c.flag [:b, :basedir]
134
+
135
+ c.desc 'Compatibility mode. If set to true deleted records will be loaded old style with type isDeleted. Otherwise deleted records will be loaded with type attribute and DeletedAt field will be added.'
136
+ c.default_value false
137
+ c.switch [:c, :compatibility]
138
+
139
+ c.action do |global_options,options,args|
140
+ options[:filenames] = args
141
+ options[:pattern] = "gen_load*.json"
142
+ options[:pid] = PID
143
+ options[:es_name] = ES_NAME
144
+ Es::Commands::load_deleted(options)
145
+ end
146
+ end
147
+
148
+ desc 'Extract'
149
+ command :extract do |c|
150
+
151
+ c.desc 'Execute only for one entity.'
152
+ c.default_value false
153
+ c.flag [:o, :only]
154
+
155
+ c.desc 'Verbose mode'
156
+ c.default_value false
157
+ c.switch [:v, :verbose]
158
+
159
+ c.desc 'Print the task in the ugly oneliner mode for use in legacy tools. Does not run the actual extract.'
160
+ c.default_value true
161
+ c.switch [:j, :json]
162
+
163
+ c.desc 'Run as usual but output the task definition in pretty print for debugging.'
164
+ c.default_value true
165
+ c.switch [:d, :debug]
166
+
167
+ c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
168
+ c.default_value nil
169
+ c.flag [:b, :basedir]
170
+
171
+ c.desc 'Extract files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern extract*.json'
172
+ c.default_value nil
173
+ c.flag [:e, :extractdir]
174
+
175
+ c.desc 'Business date'
176
+ c.default_value nil
177
+ c.flag [:n, :business_date]
178
+
179
+ c.action do |global_options,options,args|
180
+ options[:args] = args
181
+ options[:pid] = PID
182
+ options[:es_name] = ES_NAME
183
+ business_date = nil
184
+ begin
185
+ business_date = Date.strptime(options[:business_date], "%Y-%m-%d").to_time unless options[:business_date].nil?
186
+ rescue ArgumentError => e
187
+ begin
188
+ business_date = Time.at(Integer(options[:business_date]))
189
+ rescue ArgumentError => e
190
+ fail "Business date cannot be parsed from #{options[:business_date]} either as a date in format YYYY-MM-DD or an epoch timestamp"
191
+ end
192
+ end
193
+
194
+ options[:now] = business_date
195
+
196
+ Es::Commands::extract(options)
197
+ end
198
+ end
199
+
200
+ desc 'Generate extract config template'
201
+ command :generate_extract do |c|
202
+ c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
203
+ c.default_value nil
204
+ c.flag [:b, :basedir]
205
+
206
+ c.action do |global_options,options,args|
207
+ Es::Commands::generate_extract(options)
208
+ end
209
+ end
210
+
211
+ desc 'Generate base conifg template'
212
+ command :generate_base do |c|
213
+
214
+ c.desc 'Name of the entity. If inputdir is also specified all generated base files will have this entity set. If you want to set entity for each file according to the file name do not provide this parameter.'
215
+ c.default_value nil
216
+ c.flag [:e, :entity]
217
+
218
+ c.desc 'Input file.'
219
+ c.default_value nil
220
+ c.flag [:i, :input]
221
+
222
+ c.desc 'Output filename. If not provided it print to STDOUT.'
223
+ c.default_value nil
224
+ c.flag [:o, :output]
225
+
226
+ c.desc 'Input files directory. If specified it will ignore specific file and it will pick up all files in this directory with pattern *.csv'
227
+ c.default_value nil
228
+ c.flag [:s, :inputdir]
229
+
230
+ c.desc 'Base files directory. If this and inputdir are specified base JSONs will be generated into the directory.'
231
+ c.default_value nil
232
+ c.flag [:b, :basedir]
233
+
234
+ c.action do |global_options,options,args|
235
+ Es::Commands::generate_base(options)
236
+ end
237
+ end
238
+
239
+ desc 'Truncate entity. Truncation means that you specify a time all events in that entit larger than the time will be thrown away.'
240
+ command :truncate do |c|
241
+
242
+ c.desc 'Name of the entity.'
243
+ c.default_value nil
244
+ c.flag [:e, :entity]
245
+
246
+ c.desc 'Timestamp in epoch to which the ES will be truncated.'
247
+ c.default_value nil
248
+ c.flag [:t, :timestamp]
249
+
250
+ c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
251
+ c.default_value nil
252
+ c.flag [:b, :basedir]
253
+
254
+ c.action do |global_options,options,args|
255
+ options[:load_filenames] = args
256
+ options[:pid] = PID
257
+ options[:es_name] = ES_NAME
258
+ options[:basedir_pattern] = "gen_load*.json"
259
+ Es::Commands::truncate(options)
260
+ end
261
+ end
262
+
263
+ desc 'Initial load column'
264
+ command :load_column do |c|
265
+
266
+ c.desc 'Verbose mode'
267
+ c.default_value false
268
+ c.switch [:v, :verbose]
269
+
270
+ c.desc 'Run as usual but output the task definition in pretty print for debugging.'
271
+ c.default_value true
272
+ c.switch [:d, :debug]
273
+
274
+ c.desc 'Name of the column.'
275
+ c.default_value nil
276
+ c.flag [:n, :name]
277
+
278
+ c.desc 'Name of the entity.'
279
+ c.default_value nil
280
+ c.flag [:e, :entity]
281
+
282
+ c.desc 'Type of the column.'
283
+ c.default_value nil
284
+ c.flag [:t, :type]
285
+
286
+ c.desc 'Base config filename.'
287
+ c.default_value nil
288
+ c.flag [:b, :base]
289
+
290
+ c.desc 'Input data filename.'
291
+ c.default_value nil
292
+ c.flag [:i, :input]
293
+
294
+ c.desc 'Name of recordid column'
295
+ c.default_value nil
296
+ c.flag :rid
297
+
298
+ c.action do |global_options,options,args|
299
+ options[:pid] = PID
300
+ options[:es_name] = ES_NAME
301
+ Es::Commands::load_column(options)
302
+ end
303
+ end
304
+
305
+ desc 'Shows more info about'
306
+ command :inspect do |c|
307
+
308
+ c.action do |global_options,options,args|
309
+
310
+ what = args.first
311
+ filename = args[1]
312
+
313
+ case what
314
+ when "load"
315
+ fail "Specify a file with base config" if filename.nil?
316
+ base_config_file = Es::Helpers.load_config(filename)
317
+ base = Es::Load.parse(base_config_file)
318
+ base.entities.each do |entity|
319
+ puts entity.to_table
320
+ end
321
+ end
322
+ end
323
+ end
324
+
325
+
326
+ pre do |global,command,options,args|
327
+ next true if command.nil?
328
+ # Pre logic here
329
+ # Return true to proceed; false to abourt and not call the
330
+ # chosen command
331
+ # Use skips_pre before a command to skip this block
332
+ # on that command only
333
+ fail "PID env variable should be specified" if PID.nil? || PID.empty?
334
+ fail "ES_NAME env variable should be specified" if ES_NAME.nil? || ES_NAME.empty?
335
+ fail "LOGIN env variable should be specified" if LOGIN.nil? || LOGIN.empty?
336
+ fail "PASSWORD env variable should be specified" if PASSWORD.nil? || PASSWORD.empty?
337
+
338
+ GoodData.logger = Logger.new(STDOUT) if global[:logger]
339
+ GD_SERVER = global[:server]
340
+ GD_WEBDAV = global[:webdav]
341
+ begin
342
+ GoodData.connect LOGIN, PASSWORD, GD_SERVER, {
343
+ :timeout => 60,
344
+ :webdav_server => GD_WEBDAV
345
+ }
346
+ rescue RestClient::BadRequest => e
347
+ puts "Login Failed"
348
+ exit 1
349
+ end
350
+ true
351
+ end
352
+
353
+ post do |global,command,options,args|
354
+ # Post logic here
355
+ # Use skips_post before a command to skip this
356
+ # block on that command only
357
+ end
358
+
359
+ on_error do |exception|
360
+ pp exception.backtrace
361
+ if exception.is_a?(SystemExit) && exception.status == 0
362
+ false
363
+ else
364
+ # pp exception.inspect
365
+ puts exception.message.color(:red)
366
+ false
367
+ end
368
+
369
+ # Error logic here
370
+ # return false to skip default error handling
371
+ # false
372
+ # true
373
+ end
374
+
375
+ exit GLI.run(ARGV)
data/es.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = es
2
+
3
+ Generate this with
4
+ es rdoc
5
+ After you have described your command line interface
@@ -0,0 +1,406 @@
1
+ module Es
2
+ module Commands
3
+
4
+ def self.create(options)
5
+ pid = options[:pid]
6
+ es_name = options[:es_name]
7
+
8
+ begin
9
+ GoodData.post "/gdc/projects/#{pid}/eventStore/stores", {:store => {:storeId => es_name}}
10
+ rescue RestClient::BadRequest
11
+ puts "Seems like eventstore with name #{es_name} already exists"
12
+ exit 1
13
+ end
14
+ end
15
+
16
+ def self.delete(options)
17
+ pid = options[:pid]
18
+ es_name = options[:es_name]
19
+ GoodData.delete "/gdc/projects/#{pid}/eventStore/stores/#{es_name}"
20
+ end
21
+
22
+ def self.get_types
23
+ Es::Field::FIELD_TYPES
24
+ end
25
+
26
+ def self.init(options)
27
+ pid = options[:pid]
28
+ es_name = options[:es_name]
29
+ base_dir = options[:basedir]
30
+ deleted = options[:deleted]
31
+ only = options[:only]
32
+
33
+ max_timestamp = 2147483647
34
+
35
+ fail "Provide path to the loading configuration" if base_dir.nil?
36
+ base_filenames = Dir::glob("#{base_dir}/gen_load*.json")
37
+
38
+ base_entities = base_filenames.reduce([]) do |memo, filename|
39
+ fail "File #{filename} cannot be found" unless File.exist?(filename)
40
+ load_config = Es::Helpers.load_config(filename)
41
+ load = Es::Load.parse(load_config)
42
+ memo.concat(load.entities)
43
+ end
44
+ hyper_load = Es::Load.new(base_entities)
45
+ entity_names = hyper_load.entities.map {|e| e.name}.uniq
46
+
47
+ entity_names.each do |entity_name|
48
+ next if only && entity_name != only
49
+ entity = hyper_load.get_merged_entity_for(entity_name)
50
+
51
+ Tempfile.open(entity.name) do |tmp_file|
52
+ header_row = []
53
+ content_row = []
54
+ entity.fields.each do |field|
55
+ header_row << field.name
56
+ content_row << max_timestamp if field.is_timestamp?
57
+ content_row << 1 if field.is_recordid?
58
+ content_row << "" if !field.is_recordid? && !field.is_timestamp?
59
+ end
60
+ if deleted
61
+ header_row << "IsDeleted" << "DeletedAt"
62
+ content_row << "" << ""
63
+ entity.add_field(Es::Field.new('IsDeleted', 'attribute')) unless entity.has_field?('IsDeleted')
64
+ entity.add_field(Es::Field.new('DeletedAt', 'time')) unless entity.has_field?('DeletedAt')
65
+ end
66
+ tmp_file.puts(header_row.join(","))
67
+ tmp_file.puts(content_row.join(","))
68
+ tmp_file.flush
69
+
70
+ entity.file = tmp_file.path
71
+ # create temp file, link it to entity
72
+ web_dav_file = Es::Helpers.load_destination_dir(pid, entity) + '/' + Es::Helpers.destination_file(entity)
73
+
74
+ if options[:verbose]
75
+ puts "Entity #{entity.name}".bright
76
+ puts "Will load from #{entity.file} to #{web_dav_file}"
77
+ puts JSON::pretty_generate(entity.to_load_fragment(pid))
78
+ end
79
+ entity.load(pid, es_name)
80
+ puts "Done" if options[:verbose]
81
+ end
82
+ if options[:verbose]
83
+ puts "Truncating loaded dummy data for #{entity.name}. Using timestamp #{max_timestamp - 1}."
84
+ end
85
+ entity.truncate(pid, es_name, (max_timestamp - 1))
86
+ end
87
+ end
88
+
89
+ def self.truncate(options)
90
+ pid = options[:pid]
91
+ es_name = options[:es_name]
92
+ entity_name = options[:entity]
93
+ timestamp = options[:timestamp]
94
+ filenames = options[:load_filenames]
95
+ basedir_pattern = options[:basedir_pattern] || "*.json"
96
+ logger = options[:logger]
97
+
98
+ base_dir = options[:basedir]
99
+
100
+ fail "You need to specify timestamp" if timestamp.nil?
101
+ if base_dir.nil?
102
+ # fail "You need to specify entity name" if entity_name.nil?
103
+ fail "You need to specify base filename" if filenames.empty?
104
+ else
105
+ # puts "would grab files like this #{"#{base_dir}/gen_load*.json"}"
106
+ filenames = Dir::glob("#{base_dir}/#{basedir_pattern}")
107
+ end
108
+
109
+ filenames.each do |base_filename|
110
+
111
+ base_config_file = Es::Helpers.load_config(base_filename)
112
+ base = Es::Load.parse(base_config_file)
113
+
114
+ base.entities.each do |entity|
115
+ next if !entity_name.nil? and entity_name != entity.name
116
+ logger.info "truncating entity \"#{entity.name}\"} at timestamp #{timestamp} that is #{Time.at(timestamp).to_s}" if logger
117
+ entity.truncate(pid, es_name, timestamp)
118
+ end
119
+ end
120
+ end
121
+
122
+ def self.load(options)
123
+ pid = options[:pid]
124
+ es_name = options[:es_name]
125
+ filenames = options[:filenames]
126
+ base_dir = options[:basedir]
127
+ pattern = options[:pattern] || "*.json"
128
+ only = options[:only]
129
+ logger = options[:logger]
130
+
131
+ if base_dir.nil?
132
+ fail "Provide path to the loading configuration as a first argument" if filenames.empty?
133
+ else
134
+ # puts "would grab files like this #{"#{base_dir}/gen_json*.json"}"
135
+ filenames = Dir::glob("#{base_dir}/#{pattern}")
136
+ end
137
+
138
+ # for each config file
139
+ filenames.each do |filename|
140
+ fail "File #{filename} cannot be found" unless File.exist?(filename)
141
+ load_config_file = Es::Helpers.load_config(filename)
142
+ load = Es::Load.parse(load_config_file)
143
+
144
+ load.entities.each do |entity|
145
+ next if only && entity.name != only
146
+ next unless Es::Helpers.has_more_lines?(entity.file)
147
+
148
+ logger.info "Loading entity \"#{entity.name}\", fields #{entity.fields.map {|f| f.name}.join(', ')}" if logger
149
+ logger.info "Using json => #{entity.to_load_fragment(pid).to_json}" if logger
150
+
151
+ web_dav_file = Es::Helpers.load_destination_dir(pid, entity) + '/' + Es::Helpers.destination_file(entity)
152
+ if options[:verbose]
153
+ puts "Entity #{entity.name}".bright
154
+ puts "Configuration from #{filename}"
155
+ puts "Will load from #{entity.file} to #{web_dav_file}"
156
+ puts JSON::pretty_generate(entity.to_load_fragment(pid))
157
+ end
158
+ if options[:j]
159
+ puts "Entity #{entity.name}".bright unless options[:verbose]
160
+ puts "load the file #{entity.file} to destination #{web_dav_file} and run the specified as the task"
161
+ puts "======= Load JSON start"
162
+ puts entity.to_load_fragment(pid).to_json.color(:blue)
163
+ puts "======= Load JSON end"
164
+ puts
165
+ else
166
+ entity.load(pid, es_name)
167
+ puts "Done" if options[:verbose]
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ def self.load_deleted(options)
174
+ filenames = options[:filenames]
175
+ base_dir = options[:basedir]
176
+ pattern = options[:pattern] || "*.json"
177
+ pid = options[:pid]
178
+ es_name = options[:es_name]
179
+ logger = options[:logger]
180
+
181
+ if base_dir.nil?
182
+ fail "Provide path to the loading configuration as a first argument" if filenames.empty?
183
+ else
184
+ # puts "would grab files like this #{"#{base_dir}/gen_load*.json"}"
185
+ filenames = Dir::glob("#{base_dir}/#{pattern}")
186
+ end
187
+
188
+ compatibility_mode = options[:compatibility] || false
189
+ deleted_type = compatibility_mode ? "isDeleted" : "attribute"
190
+
191
+ filenames.each do |load_config_file|
192
+ load_config = Es::Helpers.load_config(load_config_file)
193
+ load = Es::Load.parse(load_config)
194
+
195
+ load.entities.each do |entity|
196
+ source_dir = File.dirname(entity.file)
197
+ deleted_filename = Es::Helpers.destination_file(entity, :deleted => true)
198
+ deleted_source = "#{source_dir}/#{deleted_filename}"
199
+ next unless File.exist? deleted_source
200
+ has_more_lines = Es::Helpers.has_more_lines?(deleted_source)
201
+ logger.info "Deleted records for entity #{entity.name} is not loaded since the file #{deleted_source} is empty." if logger && !has_more_lines
202
+ next unless has_more_lines
203
+
204
+ logger.info "Loading deleted records for entity #{entity.name} in with compatibility mode set to #{compatibility_mode}." if logger
205
+
206
+ e = Es::Entity.create_deleted_entity(entity.name, {:compatibility_mode => compatibility_mode, :file => deleted_source})
207
+ e.load(pid, es_name)
208
+
209
+ if !compatibility_mode
210
+ deleted_with_time = "#{source_dir}/#{deleted_filename}".gsub(/\.csv$/, '_del.csv')
211
+ FasterCSV.open(deleted_with_time, 'w') do |csv|
212
+ csv << ['Id', 'Timestamp', 'DeletedAt']
213
+ FasterCSV.foreach("#{source_dir}/#{deleted_filename}", :headers => true, :return_headers => false) do |row|
214
+ csv << row.values_at('Id', 'Timestamp', 'Timestamp')
215
+ end
216
+ end
217
+
218
+ e1 = Es::Entity.new(entity.name, {
219
+ :file => deleted_with_time,
220
+ :fields => [
221
+ Es::Field.new('Id', 'recordid'),
222
+ Es::Field.new('Timestamp', 'timestamp'),
223
+ Es::Field.new('DeletedAt', 'time')
224
+ ]
225
+ })
226
+ e1.load(pid, es_name)
227
+ end
228
+ end
229
+ end
230
+
231
+ end
232
+
233
+ def self.extract(options)
234
+ base_dir = options[:basedir]
235
+ extract_dir = options[:extractdir]
236
+ pid = options[:pid]
237
+ es_name = options[:es_name]
238
+ now = options[:now]
239
+ args = options[:args]
240
+ logger = options[:logger]
241
+
242
+ if base_dir.nil? && extract_dir.nil?
243
+ fail "Provide path to the loading configuration as a first argument" if args.first.nil?
244
+ load_config_files = [args.first]
245
+ fail "Provide path to the extract configuration as a second argument" if args[1].nil?
246
+ extract_config_files = [args[1]]
247
+ else
248
+ load_config_files = Dir::glob("#{base_dir}/gen_load*.json")
249
+ extract_config_files = Dir::glob("#{extract_dir}/gen_extract*.json")
250
+ end
251
+
252
+ # build one giant load config
253
+ load_entities = load_config_files.reduce([]) do |memo, filename|
254
+ fail "File #{filename} cannot be found" unless File.exist?(filename)
255
+ load_config = Es::Helpers.load_config(filename)
256
+ load = Es::Load.parse(load_config)
257
+ memo.concat(load.entities)
258
+ end
259
+ hyper_load = Es::Load.new(load_entities)
260
+
261
+ extract_config_files.each do |extract_config_file|
262
+ fail "File #{extract_config_file} cannot be found" unless File.exist?(extract_config_file)
263
+ extract_config = Es::Helpers.load_config(extract_config_file)
264
+ extract = Es::Extract.parse(extract_config, hyper_load, :now => now)
265
+
266
+ extract.entities.each do |entity|
267
+ next if options[:only] && entity.name != options[:only]
268
+
269
+ logger.info "Extracting entity \"#{entity.name}\", fields #{entity.fields.map {|f| f.name}.join(', ')}" if logger
270
+ logger.info "Using json => #{entity.to_extract_fragment(pid, :pretty => false).to_json}" if logger
271
+
272
+ if options[:verbose] || options[:json] || options[:debug] then
273
+ puts "Entity #{entity.name.bright}"
274
+ puts "Config from #{load_config_files.join(', ')} and #{extract_config_file}"
275
+ end
276
+
277
+ puts JSON.pretty_generate(entity.to_extract_fragment(pid)) if options[:debug]
278
+
279
+ if options[:json]
280
+ # puts "load the file #{entity.file} to destination #{web_dav_file} and run the specified as the task"
281
+ puts "======= Extract JSON start"
282
+ puts entity.to_extract_fragment(pid, :pretty => false).to_json.color(:blue)
283
+ puts "======= Extract JSON end"
284
+ puts
285
+ else
286
+ entity.extract(pid, es_name)
287
+ end
288
+ end
289
+ end
290
+ end
291
+
292
+
293
+ def self.generate_base(options)
294
+ entity = options[:entity]
295
+ input_filename = options[:input]
296
+ input_dir = options[:inputdir]
297
+ output_filename = options[:output]
298
+ base_dir = options[:basedir]
299
+
300
+ fail "You need to specify input file name or input dir" if input_filename.nil? && input_dir.nil?
301
+
302
+ if base_dir.nil?
303
+ input_filenames = [input_filename]
304
+ else
305
+ input_filenames = Dir::glob("#{input_dir}/*.csv")
306
+ end
307
+
308
+ input_filenames.each do |input_filename|
309
+
310
+ headers = nil
311
+ FasterCSV.foreach(input_filename, :headers => true, :return_headers => true) do |row|
312
+ if row.header_row?
313
+ headers = row.fields
314
+ break
315
+ end
316
+ end
317
+
318
+ entity_name = entity || File.basename(input_filename, ".csv")
319
+ load = Es::Load.new([
320
+ Es::Entity.new(entity_name, {
321
+ :file => input_filename,
322
+ :fields => headers.map do |field_name|
323
+ Es::Field.new(field_name, "none")
324
+ end
325
+ })
326
+ ])
327
+
328
+ config = JSON.pretty_generate(load.to_config)
329
+
330
+ if input_dir && base_dir
331
+ File.open(base_dir+"/gen_load_"+entity_name+".json", 'w') do |f|
332
+ f.write config
333
+ end
334
+ elsif input_filename && output_filename
335
+ File.open(output_filename, 'w') do |f|
336
+ f.write config
337
+ end
338
+ else
339
+ puts config
340
+ end
341
+ end
342
+ end
343
+
344
+ def self.generate_extract(options)
345
+ base_dir = options[:basedir]
346
+ fail "You need to specify base dir" if base_dir.nil?
347
+
348
+ base_filenames = Dir::glob("#{base_dir}/gen_load_*.json")
349
+ # build one giant load config
350
+ base_entities = base_filenames.reduce([]) do |memo, filename|
351
+ fail "File #{filename} cannot be found" unless File.exist?(filename)
352
+ load_config = Es::Helpers.load_config(filename)
353
+ load = Es::Load.parse(load_config)
354
+ memo.concat(load.entities)
355
+ end
356
+ hyper_load = Es::Load.new(base_entities)
357
+ entity_names = hyper_load.entities.map {|e| e.name}.uniq
358
+
359
+ entity_names.each do |entity_name|
360
+ entity = hyper_load.get_merged_entity_for(entity_name)
361
+
362
+ File.open(base_dir+"/gen_extract_"+entity.name+".json", 'w') do |f|
363
+ f.write JSON.pretty_generate(entity.to_extract_config)
364
+ end
365
+ end
366
+ end
367
+
368
+ def self.load_column(options)
369
+ file = options[:input]
370
+ name = options[:name]
371
+ type = options[:type]
372
+ entity = options[:entity]
373
+ base_filename = options[:base]
374
+ pid = options[:pid]
375
+ es_name = options[:es_name]
376
+ rid_name = options[:rid] || 'Id'
377
+
378
+ fail "You need to specify column name" if name.nil?
379
+ fail "You need to specify column type" if type.nil?
380
+ fail "You need to specify entity name" if entity.nil?
381
+ fail "You need to specify input file name" if file.nil?
382
+
383
+ base_config_file = Es::Helpers.load_config(base_filename)
384
+ base = Es::Load.parse(base_config_file)
385
+
386
+ load = Es::Load.new([
387
+ Es::Entity.new(entity, {
388
+ :file => file,
389
+ :fields => [
390
+ Es::Field.new('Timestamp', 'timestamp'),
391
+ Es::Field.new(rid_name, 'recordid'),
392
+ Es::Field.new(name, type)
393
+ ]
394
+ })
395
+ ])
396
+
397
+ base.get_entity(entity).add_field(Es::Field.new(name, type))
398
+ puts "Added field #{name}" if options[:verbose]
399
+ base.to_config_file(base_filename)
400
+
401
+ load.entities.first.load(pid, es_name)
402
+
403
+ end
404
+
405
+ end
406
+ end