skydb 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +165 -1
  3. data/lib/skydb.rb +18 -61
  4. data/lib/skydb/client.rb +186 -186
  5. data/lib/skydb/event.rb +47 -76
  6. data/lib/skydb/property.rb +34 -67
  7. data/lib/skydb/table.rb +121 -41
  8. data/lib/skydb/version.rb +1 -1
  9. data/test/integration/client_test.rb +88 -0
  10. data/test/test_helper.rb +3 -51
  11. data/test/unit/client_test.rb +135 -32
  12. metadata +17 -278
  13. data/bin/sky +0 -89
  14. data/lib/ext/hash.rb +0 -11
  15. data/lib/ext/string.rb +0 -11
  16. data/lib/ext/treetop.rb +0 -19
  17. data/lib/skydb/action.rb +0 -76
  18. data/lib/skydb/import.rb +0 -7
  19. data/lib/skydb/import/importer.rb +0 -435
  20. data/lib/skydb/import/transforms/apache.yml +0 -4
  21. data/lib/skydb/import/transforms/sky.yml +0 -28
  22. data/lib/skydb/import/transforms/snowplow.yml +0 -1
  23. data/lib/skydb/import/translator.rb +0 -119
  24. data/lib/skydb/message.rb +0 -146
  25. data/lib/skydb/message/add_action.rb +0 -53
  26. data/lib/skydb/message/add_event.rb +0 -72
  27. data/lib/skydb/message/add_property.rb +0 -55
  28. data/lib/skydb/message/create_table.rb +0 -64
  29. data/lib/skydb/message/delete_table.rb +0 -66
  30. data/lib/skydb/message/get_action.rb +0 -55
  31. data/lib/skydb/message/get_actions.rb +0 -38
  32. data/lib/skydb/message/get_properties.rb +0 -38
  33. data/lib/skydb/message/get_property.rb +0 -55
  34. data/lib/skydb/message/get_table.rb +0 -74
  35. data/lib/skydb/message/get_tables.rb +0 -43
  36. data/lib/skydb/message/lookup.rb +0 -79
  37. data/lib/skydb/message/lua/aggregate.rb +0 -63
  38. data/lib/skydb/message/multi.rb +0 -57
  39. data/lib/skydb/message/next_actions.rb +0 -55
  40. data/lib/skydb/message/ping.rb +0 -32
  41. data/lib/skydb/property/type.rb +0 -40
  42. data/lib/skydb/query.rb +0 -183
  43. data/lib/skydb/query/after_condition.rb +0 -104
  44. data/lib/skydb/query/ast/selection_field_syntax_node.rb +0 -26
  45. data/lib/skydb/query/ast/selection_fields_syntax_node.rb +0 -16
  46. data/lib/skydb/query/ast/selection_group_syntax_node.rb +0 -16
  47. data/lib/skydb/query/ast/selection_groups_syntax_node.rb +0 -16
  48. data/lib/skydb/query/condition.rb +0 -113
  49. data/lib/skydb/query/on_condition.rb +0 -53
  50. data/lib/skydb/query/selection.rb +0 -398
  51. data/lib/skydb/query/selection_field.rb +0 -99
  52. data/lib/skydb/query/selection_fields_grammar.treetop +0 -46
  53. data/lib/skydb/query/selection_fields_parse_error.rb +0 -30
  54. data/lib/skydb/query/selection_group.rb +0 -78
  55. data/lib/skydb/query/selection_groups_grammar.treetop +0 -31
  56. data/lib/skydb/query/selection_groups_parse_error.rb +0 -30
  57. data/lib/skydb/query/validation_error.rb +0 -8
  58. data/lib/skydb/timestamp.rb +0 -22
  59. data/test/integration/query_test.rb +0 -102
  60. data/test/unit/event_test.rb +0 -32
  61. data/test/unit/import/importer_test.rb +0 -208
  62. data/test/unit/import/translator_test.rb +0 -88
  63. data/test/unit/message/add_action_message_test.rb +0 -34
  64. data/test/unit/message/add_event_message_test.rb +0 -35
  65. data/test/unit/message/add_property_message_test.rb +0 -41
  66. data/test/unit/message/create_table_message_test.rb +0 -34
  67. data/test/unit/message/delete_table_message_test.rb +0 -34
  68. data/test/unit/message/get_action_message_test.rb +0 -34
  69. data/test/unit/message/get_actions_message_test.rb +0 -18
  70. data/test/unit/message/get_properties_message_test.rb +0 -18
  71. data/test/unit/message/get_property_message_test.rb +0 -34
  72. data/test/unit/message/get_table_message_test.rb +0 -19
  73. data/test/unit/message/get_tables_message_test.rb +0 -18
  74. data/test/unit/message/lookup_message_test.rb +0 -27
  75. data/test/unit/message/lua_aggregate_message_test.rb +0 -19
  76. data/test/unit/message/multi_message_test.rb +0 -22
  77. data/test/unit/message/next_action_message_test.rb +0 -34
  78. data/test/unit/message/ping_message_test.rb +0 -18
  79. data/test/unit/message_test.rb +0 -15
  80. data/test/unit/query/after_test.rb +0 -89
  81. data/test/unit/query/on_test.rb +0 -71
  82. data/test/unit/query/selection_test.rb +0 -273
  83. data/test/unit/query_test.rb +0 -182
  84. data/test/unit/skydb_test.rb +0 -20
data/bin/sky DELETED
@@ -1,89 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- $:.unshift(File.join(File.dirname(File.expand_path(__FILE__)), '..', 'lib'))
4
-
5
- require 'rubygems'
6
- require 'skydb'
7
- require 'skydb/import'
8
- require 'commander/import'
9
-
10
- program :name, 'Sky'
11
- program :version, SkyDB::VERSION
12
- program :description, 'A multi-purpose utilty for the Sky database.'
13
-
14
- SkyDB.debug = true
15
-
16
-
17
- ################################################################################
18
- # Import
19
- ################################################################################
20
-
21
- command :import do |c|
22
- c.syntax = 'sky import FILE'
23
- c.description = 'Imports data from a text file into a Sky table.'
24
- c.option('--processes NUM', 'The number of processes to use.')
25
- c.option('--table STRING', 'The name of the table to import to.')
26
- c.option('--format STRING', 'The YAML format file to import with.')
27
- c.option('--file-type STRING', 'The type of file being imported (tsv,json,csv,apache_log).')
28
- c.option('--headers STRING', 'A comma-delimited list of headers to use.')
29
- c.option('--append', 'Appends to an existing database if one exists.')
30
- c.option('--overwrite', 'Overwrites an existing database if one exists.')
31
- c.when_called do|args, options|
32
- abort("You cannot specify --append and --overwrite at the same time.") if options.append && options.overwrite
33
-
34
- # Check if the server is running.
35
- if !SkyDB.ping
36
- puts "Sky is not currently running on #{SkyDB.client.host}:#{SkyDB.client.port}."
37
- exit(1)
38
- end
39
-
40
- # Setup importer.
41
- importer = SkyDB::Import::Importer.new()
42
- importer.table_name = options.table || ask("Table: ")
43
- importer.headers = options.headers.nil? ? nil : options.headers.split(/,/)
44
- importer.file_type = options.file_type.nil? ? nil : options.file_type.to_sym
45
- importer.processes = options.processes.nil? ? 1 : options.processes.to_i
46
-
47
- # Load transform files by name.
48
- formats = options.format || ask("Format: ")
49
- formats.split(',').each do |format|
50
- begin
51
- importer.load_transform_file(format)
52
- rescue SkyDB::Import::Importer::TransformNotFound => e
53
- puts "ERROR: #{e.message}\n\n"
54
- exit(1)
55
- end
56
- end
57
-
58
- # Check if table exists on server already.
59
- table = SkyDB.get_table(importer.table_name)
60
-
61
- # If table does not exists, then ask if it should be created.
62
- if table.nil?
63
- print "'#{importer.table_name}' does not exist. Create it? [Yn] "
64
- c = STDIN.gets.chomp.upcase
65
- exit(0) unless c == '' || c[0] == 'Y'
66
- SkyDB.create_table(SkyDB::Table.new(importer.table_name))
67
-
68
- # If it does exist, ask if the import should append.
69
- else
70
- if !options.append
71
- c = nil
72
- if options.overwrite
73
- c = 'O'
74
- else
75
- print "'#{importer.table_name}' already exists. Append or overwrite? [Ao] "
76
- c = STDIN.gets.chomp.upcase[0]
77
- end
78
-
79
- if c == 'O'
80
- SkyDB.delete_table(SkyDB::Table.new(importer.table_name))
81
- SkyDB.create_table(SkyDB::Table.new(importer.table_name))
82
- end
83
- end
84
- end
85
-
86
- # Import!
87
- importer.import(args)
88
- end
89
- end
@@ -1,11 +0,0 @@
1
- class Hash
2
- # Performs a deep conversion of string keys to symbol keys.
3
- def _symbolize_keys!
4
- keys.select {|key| key.is_a?(String)}.each do |key|
5
- self[key]._symbolize_keys! if self[key].is_a?(Hash)
6
- self[(key.to_sym rescue key) || key] = self.delete(key)
7
- end
8
-
9
- return self
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- class String
2
- def to_hex
3
- bytes.map do |ch|
4
- if ch.chr.index(/^[a-z_]$/i)
5
- ch.chr
6
- else
7
- '\x%02x' % ch
8
- end
9
- end.join('')
10
- end
11
- end
@@ -1,19 +0,0 @@
1
- module Treetop
2
- # Searches the syntax node hierarchy for elements that match a given class.
3
- def self.search(node, type)
4
- # If this is a matching node then return it.
5
- if node.is_a?(type)
6
- return [node]
7
-
8
- # Otherwise search children.
9
- elsif !node.elements.nil?
10
- ret = []
11
- node.elements.each do |element|
12
- ret = ret.concat(Treetop.search(element, type))
13
- end
14
- return ret
15
- end
16
-
17
- return []
18
- end
19
- end
@@ -1,76 +0,0 @@
1
- class SkyDB
2
- class Action
3
- ##########################################################################
4
- #
5
- # Constructor
6
- #
7
- ##########################################################################
8
-
9
- # Initializes the action.
10
- def initialize(options={})
11
- self.id = options[:id]
12
- self.name = options[:name]
13
- end
14
-
15
-
16
- ##########################################################################
17
- #
18
- # Attributes
19
- #
20
- ##########################################################################
21
-
22
- ##################################
23
- # ID
24
- ##################################
25
-
26
- # The action identifier.
27
- attr_reader :id
28
-
29
- def id=(value)
30
- @id = value.to_i
31
- end
32
-
33
-
34
- ##################################
35
- # Name
36
- ##################################
37
-
38
- # The name of the action.
39
- attr_reader :name
40
-
41
- def name=(value)
42
- @name = value.to_s
43
- end
44
-
45
-
46
- ##########################################################################
47
- #
48
- # Methods
49
- #
50
- ##########################################################################
51
-
52
- # Encodes the action into MsgPack format.
53
- def to_msgpack
54
- return {id:id, name:name}.to_msgpack
55
- end
56
-
57
- # Serializes the query object into a JSON string.
58
- def to_json(*a); to_hash.to_json(*a); end
59
-
60
- # Encodes the action into JSON format.
61
- def to_hash(*a)
62
- {
63
- 'id' => id,
64
- 'name' => name
65
- }.delete_if {|k,v| v == '' || v == 0}
66
- end
67
-
68
- # Deserializes the selection field object from a hash.
69
- def from_hash(hash, *a)
70
- return nil if hash.nil?
71
- self.id = hash['id'].to_i
72
- self.name = hash['name']
73
- return self
74
- end
75
- end
76
- end
@@ -1,7 +0,0 @@
1
- require 'skydb/import/importer'
2
- require 'skydb/import/translator'
3
-
4
- class SkyDB
5
- class Import
6
- end
7
- end
@@ -1,435 +0,0 @@
1
- require 'yaml'
2
- require 'csv'
3
- require 'yajl'
4
- require 'zlib'
5
- require 'bzip2'
6
- require 'open-uri'
7
- require 'ruby-progressbar'
8
- require 'apachelogregex'
9
- require 'useragent'
10
-
11
- class SkyDB
12
- class Import
13
- class Importer
14
- ##########################################################################
15
- #
16
- # Errors
17
- #
18
- ##########################################################################
19
-
20
- class UnsupportedFileType < StandardError; end
21
- class TransformNotFound < StandardError; end
22
-
23
-
24
- ##########################################################################
25
- #
26
- # Constructor
27
- #
28
- ##########################################################################
29
-
30
- # Initializes the importer.
31
- def initialize(options={})
32
- @translators = []
33
-
34
- self.client = options[:client] || SkyDB.client
35
- self.table_name = options[:table_name]
36
- self.format = options[:format]
37
- self.files = options[:files] || []
38
- self.processes = options[:processes] || 1
39
- end
40
-
41
-
42
- ##########################################################################
43
- #
44
- # Attributes
45
- #
46
- ##########################################################################
47
-
48
- # The number of processes to use.
49
- attr_accessor :processes
50
-
51
- # The client to access the Sky server with.
52
- attr_accessor :client
53
-
54
- # The name of the table to import into.
55
- attr_accessor :table_name
56
-
57
- # The format file to use for translating the input data.
58
- attr_accessor :format
59
-
60
- # A list of translators to use to convert input rows into output rows.
61
- attr_reader :translators
62
-
63
- # A list of files to input from.
64
- attr_accessor :files
65
-
66
- # A list of header names to use for CSV files. Using this option will
67
- # treat the CSV input as not having a header row.
68
- attr_accessor :headers
69
-
70
- # The file type of file being imported can be one of
71
- # :csv, :tsv, :json, :apache_log
72
- attr_accessor :file_type
73
-
74
-
75
- ##########################################################################
76
- #
77
- # Methods
78
- #
79
- ##########################################################################
80
-
81
- ##################################
82
- # Import
83
- ##################################
84
-
85
- # Imports records from a list of files.
86
- #
87
- # @param [Array] a list of files to import.
88
- def import(files, options={})
89
- files = [files] unless files.is_a?(Array)
90
- options[:progress_bar] = true unless options.has_key?(:progress_bar)
91
- progress_bar = nil
92
-
93
- # Set the table to import into.
94
- SkyDB.table_name = table_name
95
-
96
- # Initialize progress bar.
97
- count = files.inject(0) do |cnt,file|
98
- # disable progress bar if using compressed files
99
- if Dir.glob(file).detect{|f|['.gz','.bz2'].include?(File.extname(f).downcase)}
100
- options[:progress_bar] = false
101
- break
102
- end
103
- cnt + %x{wc -l #{file}|tail -1}.split.first.to_i
104
- end
105
- progress_bar = ::ProgressBar.create(:total => count, :format => '|%B| %P%%') if (options[:progress_bar] and self.processes == 1)
106
-
107
- # Loop over each of the files.
108
- files_expanded = files.inject([]) {|fs,fg| fs.concat(Dir[File.expand_path(fg)].delete_if{|f| File.directory?(f)}); fs}
109
- file_groups =
110
- if processes > 1
111
- files_per_group = (files_expanded.size/Float(self.processes)).ceil
112
- files_expanded.each_slice(files_per_group).to_a
113
- else
114
- [files_expanded]
115
- end
116
- process_ids = []
117
-
118
- for i in (0...processes)
119
- process_ids << fork do
120
- SkyDB.multi(:max_count => 1000) do
121
- file_groups[i].each do |file|
122
- # puts "process[#{i}] -> #{file}"
123
- each_record(file, options) do |input|
124
- # Convert input line to a symbolized hash.
125
- output = translate(input)
126
- output._symbolize_keys!
127
-
128
- # p output
129
-
130
- if output[:object_id].nil?
131
- progress_bar.clear() unless progress_bar.nil?
132
- $stderr.puts "[ERROR] Object id required on line #{$.}"
133
- elsif output[:timestamp].nil?
134
- progress_bar.clear() unless progress_bar.nil?
135
- $stderr.puts "[ERROR] Invalid timestamp on line #{$.}"
136
- else
137
- # Convert hash to an event and send to Sky.
138
- event = SkyDB::Event.new(output)
139
- SkyDB.add_event(event)
140
- end
141
-
142
- # Update progress bar.
143
- progress_bar.increment() unless progress_bar.nil?
144
- end
145
- end
146
- end
147
- end
148
- end
149
- process_ids.each { |process_id| Process.waitpid(process_id) }
150
-
151
- # Finish progress bar.
152
- progress_bar.finish() unless progress_bar.nil? || progress_bar.finished?
153
-
154
- return nil
155
- end
156
-
157
-
158
- ##################################
159
- # File Iteration
160
- ##################################
161
-
162
- def file_foreach(file, &block)
163
- case File.extname(file).downcase
164
- when '.bz2'
165
- Bzip2::Reader.foreach(file) do |line|
166
- yield line
167
- end
168
- when '.gz'
169
- Zlib::GzipReader.open(file) do |f|
170
- f.each_line(file) do |line|
171
- yield line
172
- end
173
- end
174
- else
175
- File.foreach(file) do |line|
176
- yield line
177
- end
178
- end
179
- end
180
-
181
-
182
- ##################################
183
- # Iteration
184
- ##################################
185
-
186
- # Executes a block for each record in a given file. A record is defined
187
- # by the file's type (:csv, :tsv, :json).
188
- #
189
- # @param [String] file the path to the file to iterate over.
190
- def each_record(file, options)
191
- # Determine file type automatically if not passed in.
192
- if self.file_type.nil?
193
- self.file_type =
194
- case File.extname(file)
195
- when '.tsv' then :tsv
196
- when '.txt' then :tsv
197
- when '.json' then :json
198
- when '.csv' then :csv
199
- when '.log' then :apache_log
200
- end
201
- warn("[import] Determining file type: #{self.file_type || '???'}")
202
- end
203
-
204
- # Process the record by file type.
205
- case self.file_type
206
- when :csv then each_text_record(file, ",", options, &Proc.new)
207
- when :tsv then each_text_record(file, "\t", options, &Proc.new)
208
- when :json then each_json_record(file, options, &Proc.new)
209
- when :apache_log then each_apache_log_record(file, options, &Proc.new)
210
- else raise SkyDB::Import::Importer::UnsupportedFileType.new("File type not supported by importer: #{file_type || File.extname(file)}")
211
- end
212
-
213
- return nil
214
- end
215
-
216
- # Executes a block for each line of a delimited flat file format
217
- # (CSV, TSV).
218
- #
219
- # @param [String] file the path to the file to iterate over.
220
- # @param [String] col_sep the column separator.
221
- def each_text_record(file, col_sep, options)
222
- # Process each line of the CSV file.
223
- CSV.foreach(file, :headers => headers.nil?, :col_sep => col_sep) do |row|
224
- record = nil
225
-
226
- # If headers were not specified then use the ones from the
227
- # CSV file and just convert the row to a hash.
228
- if headers.nil?
229
- record = row.to_hash
230
-
231
- # If headers were specified then manually convert the row
232
- # using the headers provided.
233
- else
234
- record = {}
235
- headers.each_with_index do |header, index|
236
- record[header] = row[index]
237
- end
238
- end
239
-
240
- # Skip over blank rows.
241
- next if record.values.reject{|v| v == '' || v.nil? }.length == 0
242
-
243
- yield(record)
244
- end
245
- end
246
-
247
- # Executes a block for each line of a JSON file.
248
- #
249
- # @param [String] file the path to the file to iterate over.
250
- def each_json_record(file, options)
251
- io = open(file)
252
-
253
- # Process each line of the JSON file.
254
- Yajl::Parser.parse(io) do |record|
255
- yield(record)
256
- end
257
- end
258
-
259
- # Executes a block for each line of a standard Apache log file.
260
- #
261
- # @param [String] file the path to the file to iterate over.
262
- def each_apache_log_record(file, options)
263
- format = options[:format] || '%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"'
264
- parser = ApacheLogRegex.new(format)
265
-
266
- file_foreach(file) do |line|
267
- begin
268
- hash = parser.parse!(line)
269
- m, method, url = *hash['%r'].to_s.match(/^(\w+) ([^ ]+)/)
270
- uri = URI.parse("http://localhost#{path}") rescue nil
271
- record = {
272
- :ip_address => hash['%h'],
273
- :timestamp => DateTime.strptime(hash['%t'].gsub(/\[|\]/, ''), "%d/%b/%Y:%H:%M:%S %z"),
274
- :method => method,
275
- :url => url,
276
- :status_code => hash['%s'],
277
- :size => hash['%b'],
278
- }
279
- record[:user_identifier] = hash['%l'] unless hash['%l'] == '-'
280
- record[:user_id] = hash['%u'] unless hash['%u'] == '-'
281
-
282
- # Extract the parts of the URI.
283
- if !uri.nil?
284
- record[:path] = uri.path
285
- record[:query_string] = uri.query
286
- record[:query] = CGI::parse(uri.query) rescue {}
287
- record[:fragment] = uri.fragment
288
- end
289
-
290
- # Extract the referrer if there is one.
291
- if !hash['%{Referer}i'].nil? && hash['%{Referer}i'] != '-'
292
- record[:referer] = hash['%{Referer}i']
293
- referer_uri = URI.parse(record[:referer]) rescue nil
294
- if !referer_uri.nil?
295
- record[:referer_host] = referer_uri.host
296
- record[:referer_path] = referer_uri.path
297
- record[:referer_query_string] = referer_uri.query
298
- record[:referer_query] = CGI::parse(referer_uri.query) rescue {}
299
- end
300
- end
301
-
302
- # Extract specific user agent information.
303
- if !hash['%{User-Agent}i'].nil?
304
- user_agent = UserAgent.parse(hash['%{User-Agent}i'])
305
- record[:user_agent] = hash['%{User-Agent}i']
306
- record[:ua_name] = user_agent.browser.to_s unless user_agent.browser.nil?
307
- record[:ua_version] = user_agent.version.to_s unless user_agent.version.nil?
308
- record[:ua_platform] = user_agent.platform.to_s unless user_agent.platform.nil?
309
- record[:ua_os] = user_agent.os.to_s unless user_agent.os.nil?
310
- record[:ua_mobile] = user_agent.mobile?
311
- end
312
-
313
- # Skip junk log entries.
314
- next if method == "HEAD" || method == "OPTIONS"
315
-
316
- yield(record)
317
-
318
- rescue ApacheLogRegex::ParseError => e
319
- $stderr.puts "[ERROR] Unable to parse line #{$.} in #{file} (#{e.message})"
320
- end
321
- end
322
- end
323
-
324
-
325
- ##################################
326
- # Translation
327
- ##################################
328
-
329
- # Translates an input hash into an output hash using the translators.
330
- #
331
- # @param [Hash] the input hash.
332
- #
333
- # @return [Hash] the output hash.
334
- def translate(input)
335
- output = {:action => {}, :data => {}}
336
-
337
- translators.each do |translator|
338
- translator.translate(input, output)
339
- end
340
-
341
- output.delete(:action) if output[:action].keys.length == 0
342
- output.delete(:data) if output[:data].keys.length == 0
343
- return output
344
- end
345
-
346
-
347
- ##################################
348
- # Transform Management
349
- ##################################
350
-
351
- # Parses and appends the contents of a transform file to the importer.
352
- #
353
- # @param [String] the YAML formatted transform file.
354
- def load_transform(content)
355
- # Parse the transform file.
356
- transform = {'fields' => {}}.merge(YAML.load(content))
357
-
358
- # Load any libraries requested by the format file.
359
- if transform['require'].is_a?(Array)
360
- transform['require'].each do |library_name|
361
- require library_name
362
- end
363
- end
364
-
365
- # Load individual field translations.
366
- load_transform_fields(transform['fields'])
367
-
368
- # Load a free-form translate function if specified.
369
- if !transform['translate'].nil?
370
- @translators << Translator.new(
371
- :translate_function => transform['translate']
372
- )
373
- end
374
-
375
- return nil
376
- end
377
-
378
- # Loads a hash of transforms.
379
- #
380
- # @param [Hash] the hash of transform info.
381
- # @param [Array] the path of fields.
382
- def load_transform_fields(fields, path=nil)
383
- # Convert each field to a translator.
384
- fields.each_pair do |key, value|
385
- translator = Translator.new(:output_field => (path.nil? ? key : path.clone.concat([key])))
386
-
387
- # Load a regular transform.
388
- if value.is_a?(String)
389
- # If the line is wrapped in curly braces then generate a translate function.
390
- m, code = *value.match(/^\s*\{(.*)\}\s*$/)
391
- if !m.nil?
392
- translator.translate_function = code
393
-
394
- # Otherwise it's a colon-separated field describing the input field and data type.
395
- else
396
- input_field, format = *value.strip.split(":")
397
- translator.input_field = input_field
398
- translator.format = format
399
- end
400
-
401
- # If this field is a hash then load it as a nested transform.
402
- elsif value.is_a?(Hash)
403
- load_transform_fields(value, path.to_a.clone.flatten.concat([key]))
404
-
405
- else
406
- raise "Invalid data type for '#{key}' in transform file: #{value.class}"
407
- end
408
-
409
- # Append to the list of translators.
410
- @translators << translator
411
- end
412
- end
413
-
414
-
415
- # Parses and appends the contents of a transform file to the importer.
416
- #
417
- # @param [String] the filename to load from.
418
- def load_transform_file(filename)
419
- transforms_path = File.expand_path(File.join(File.dirname(__FILE__), 'transforms'))
420
- named_transform_path = File.join(transforms_path, "#{filename}.yml")
421
-
422
- # If it's just a word then find it in the gem.
423
- if filename.index(/^\w+$/)
424
- raise TransformNotFound.new("Named transform not available: #{filename} (#{named_transform_path})") unless File.exists?(named_transform_path)
425
- return load_transform(IO.read(named_transform_path))
426
-
427
- # Otherwise load it from the present working directory.
428
- else
429
- raise TransformNotFound.new("Transform file not found: #{filename}") unless File.exists?(filename)
430
- return load_transform(IO.read(filename))
431
- end
432
- end
433
- end
434
- end
435
- end