ginjo-rfm 2.1.7 → 3.0.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.
- checksums.yaml +15 -0
- data/CHANGELOG.md +45 -16
- data/README.md +251 -274
- data/lib/rfm.rb +42 -20
- data/lib/rfm/VERSION +1 -1
- data/lib/rfm/base.rb +63 -196
- data/lib/rfm/database.rb +15 -16
- data/lib/rfm/layout.rb +244 -271
- data/lib/rfm/metadata/datum.rb +45 -0
- data/lib/rfm/metadata/field.rb +33 -13
- data/lib/rfm/metadata/field_control.rb +57 -25
- data/lib/rfm/metadata/layout_meta.rb +38 -0
- data/lib/rfm/metadata/resultset_meta.rb +66 -0
- data/lib/rfm/metadata/value_list_item.rb +7 -6
- data/lib/rfm/record.rb +54 -74
- data/lib/rfm/resultset.rb +63 -112
- data/lib/rfm/server.rb +6 -172
- data/lib/rfm/utilities/config.rb +100 -55
- data/lib/rfm/utilities/connection.rb +209 -0
- data/lib/rfm/utilities/core_ext.rb +14 -1
- data/lib/rfm/utilities/factory.rb +68 -65
- data/lib/rfm/utilities/sax_parser.rb +1039 -0
- metadata +154 -206
- data/lib/rfm/utilities/fmpxmlresult.rb +0 -167
- data/lib/rfm/utilities/fmresultset.rb +0 -153
- data/lib/rfm/utilities/xml_parser.rb +0 -124
- data/lib/rfm/xml_mini/hpricot.rb +0 -133
- data/lib/rfm/xml_mini/ox_sax.rb +0 -91
- data/lib/rfm/xml_mini/rexml_sax.rb +0 -81
data/lib/rfm/resultset.rb
CHANGED
@@ -38,15 +38,21 @@ module Rfm
|
|
38
38
|
class Resultset < Array
|
39
39
|
include Config
|
40
40
|
|
41
|
-
attr_reader :layout, :
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
attr_reader :layout, :meta, :calling_object
|
42
|
+
# attr_reader :layout, :database, :server, :calling_object, :doc
|
43
|
+
# attr_reader :field_meta, :portal_meta, :include_portals, :datasource
|
44
|
+
# attr_reader :date_format, :time_format, :timestamp_format
|
45
|
+
# attr_reader :total_count, :foundset_count, :table
|
45
46
|
#def_delegators :layout, :db, :database
|
46
|
-
alias_method :db, :database
|
47
|
+
# alias_method :db, :database
|
48
|
+
def_delegators :meta, :field_meta, :portal_meta, :date_format, :time_format, :timestamp_format, :total_count, :foundset_count, :fetch_size, :table, :error, :field_names, :field_keys, :portal_names
|
49
|
+
|
47
50
|
|
48
51
|
class << self
|
49
|
-
|
52
|
+
def load_data(data)
|
53
|
+
Rfm::Connection
|
54
|
+
Rfm::SaxParser.parse(data, :fmresultset, Rfm::Resultset.new)
|
55
|
+
end
|
50
56
|
end
|
51
57
|
|
52
58
|
# Initializes a new ResultSet object. You will probably never do this your self (instead, use the Layout
|
@@ -70,122 +76,67 @@ module Rfm
|
|
70
76
|
# * *portals* is a hash (with table occurrence names for keys and Field objects for values). If your
|
71
77
|
# layout contains portals, you can find out what fields they contain here. Again, if it's the data you're
|
72
78
|
# after, you want to look at the Record object.
|
73
|
-
def initialize(*args) #
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
config :parent=>'caller'
|
78
|
-
config sanitize_config(options, {}, true)
|
79
|
-
|
80
|
-
xml_response = args[0] || options[:xml_response]
|
81
|
-
doc = XmlParser.parse(xml_response, :namespace=>false, :parser=>(state[:parser] rescue nil))
|
82
|
-
|
83
|
-
error = doc.error
|
84
|
-
check_for_errors(error, (server.state[:raise_on_401] rescue nil))
|
85
|
-
|
86
|
-
@doc = doc
|
87
|
-
@caller = args[1] || options[:caller]
|
88
|
-
@layout = (@caller.class.ancestors.include? Rfm::Layout::LayoutModule) ? @caller : options[:layout_object]
|
89
|
-
@database = (@layout.database rescue nil) || (@caller.class == Rfm::Database ? @caller : options[:database_object])
|
90
|
-
@server = (@database.server rescue nil) || (@caller.class == Rfm::Server ? @caller : options[:server_object])
|
91
|
-
@field_meta ||= Rfm::CaseInsensitiveHash.new
|
92
|
-
@portal_meta ||= Rfm::CaseInsensitiveHash.new
|
93
|
-
@include_portals = args[2] || options[:include_portals]
|
94
|
-
|
95
|
-
@datasource = doc.datasource
|
96
|
-
meta = doc.meta
|
97
|
-
resultset = doc.resultset
|
79
|
+
def initialize(*args) # parent, layout
|
80
|
+
config *args
|
81
|
+
self.meta
|
82
|
+
end # initialize
|
98
83
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
(layout.table = @table) if layout and layout.table_no_load.blank?
|
108
|
-
|
109
|
-
parse_fields(doc)
|
110
|
-
|
111
|
-
# This will always load portal meta, even if :include_portals was not specified.
|
112
|
-
# See Record for control of portal data loading.
|
113
|
-
parse_portals(doc)
|
114
|
-
|
115
|
-
# These were added for loading resultset from file
|
116
|
-
# Kind of a hack. This should ideally condense down to just another option on the main @layout = ...
|
117
|
-
# unless @layout
|
118
|
-
# @layout = @datasource['layout']
|
119
|
-
# @layout.instance_variable_set '@database', @datasource['database']
|
120
|
-
# @layout.instance_eval do
|
121
|
-
# def database
|
122
|
-
# @database
|
123
|
-
# end
|
124
|
-
# end
|
125
|
-
# end
|
126
|
-
|
127
|
-
return if doc.records.blank?
|
128
|
-
Rfm::Record.build_records(doc.records, self, @field_meta, @layout)
|
84
|
+
def config(*args)
|
85
|
+
super do |params|
|
86
|
+
(@layout = params[:objects][0]) if params &&
|
87
|
+
params[:objects] &&
|
88
|
+
params[:objects][0] &&
|
89
|
+
params[:objects][0].is_a?(Rfm::Layout)
|
90
|
+
end
|
129
91
|
end
|
130
92
|
|
131
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
93
|
+
# This method was added for situations where a layout was not provided at resultset instantiation,
|
94
|
+
# such as when loading a resultset from an xml file.
|
95
|
+
def layout
|
96
|
+
@layout ||= (Layout.new(meta.layout, self) if meta.respond_to? :layout)
|
97
|
+
end
|
98
|
+
|
99
|
+
def database
|
100
|
+
layout.database
|
101
|
+
end
|
102
|
+
|
103
|
+
alias_method :db, :database
|
104
|
+
|
105
|
+
def server
|
106
|
+
database.server
|
107
|
+
end
|
108
|
+
|
109
|
+
def meta
|
110
|
+
# Access the meta inst var here.
|
111
|
+
@meta ||= Metadata::ResultsetMeta.new
|
138
112
|
end
|
139
|
-
|
140
|
-
|
141
|
-
|
113
|
+
|
114
|
+
def handle_new_record(attributes)
|
115
|
+
r = Rfm::Record.new(self, attributes, {})
|
116
|
+
self << r
|
117
|
+
r
|
142
118
|
end
|
143
119
|
|
144
|
-
def
|
145
|
-
|
120
|
+
def end_datasource_element_callback(cursor)
|
121
|
+
%w(date_format time_format timestamp_format).each{|f| convert_date_time_format(eval(f))}
|
146
122
|
end
|
147
123
|
|
148
|
-
|
149
|
-
|
150
|
-
def check_for_errors(code, raise_401)
|
151
|
-
raise Rfm::Error.getError(code) if code != 0 && (code != 401 || raise_401)
|
152
|
-
end
|
124
|
+
private
|
153
125
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
name = layout.field_mapping[field.name] || field.name rescue field.name
|
159
|
-
@field_meta[name] = Rfm::Metadata::Field.new(field)
|
160
|
-
end
|
161
|
-
(layout.field_names = field_names) if layout and layout.field_names_no_load.blank?
|
162
|
-
end
|
163
|
-
|
164
|
-
def parse_portals(doc)
|
165
|
-
return if doc.portals.blank?
|
166
|
-
doc.portals.each do |relatedset|
|
167
|
-
next if relatedset.blank?
|
168
|
-
table, fields = relatedset.table, {}
|
169
|
-
|
170
|
-
relatedset.fields.each do |field|
|
171
|
-
name = field.name.to_s.gsub(Regexp.new(table + '::'), '')
|
172
|
-
fields[name] = Rfm::Metadata::Field.new(field)
|
173
|
-
end
|
126
|
+
def check_for_errors(code=@meta['error'].to_i, raise_401=state[:raise_401])
|
127
|
+
#puts ["\nRESULTSET#check_for_errors", code, raise_401]
|
128
|
+
raise Rfm::Error.getError(code) if code != 0 && (code != 401 || raise_401)
|
129
|
+
end
|
174
130
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
# fm_format.gsub!('HH', '%H')
|
185
|
-
# fm_format.gsub!('mm', '%M')
|
186
|
-
# fm_format.gsub!('ss', '%S')
|
187
|
-
# fm_format
|
188
|
-
# end
|
131
|
+
def convert_date_time_format(fm_format)
|
132
|
+
fm_format.gsub!('MM', '%m')
|
133
|
+
fm_format.gsub!('dd', '%d')
|
134
|
+
fm_format.gsub!('yyyy', '%Y')
|
135
|
+
fm_format.gsub!('HH', '%H')
|
136
|
+
fm_format.gsub!('mm', '%M')
|
137
|
+
fm_format.gsub!('ss', '%S')
|
138
|
+
fm_format
|
139
|
+
end
|
189
140
|
|
190
141
|
end
|
191
142
|
end
|
data/lib/rfm/server.rb
CHANGED
@@ -107,7 +107,6 @@ module Rfm
|
|
107
107
|
# * *name* is the name of this database
|
108
108
|
# * *state* is a hash of all server options used to initialize this server
|
109
109
|
class Server
|
110
|
-
#extend Config
|
111
110
|
include Config
|
112
111
|
|
113
112
|
|
@@ -196,31 +195,8 @@ module Rfm
|
|
196
195
|
# :root_cert_path => '/usr/cert_file/'
|
197
196
|
# })
|
198
197
|
def initialize(*args)
|
199
|
-
config
|
200
|
-
|
201
|
-
config sanitize_config(options, {}, true)
|
202
|
-
config(:host => (options[:strings].delete_at(0) || options[:host]) )
|
203
|
-
raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Server has no host name. Attempted name '#{config[:host]}'.") if config[:host].to_s == ''
|
204
|
-
|
205
|
-
@defaults = {
|
206
|
-
:host => 'localhost',
|
207
|
-
:port => 80,
|
208
|
-
:ssl => true,
|
209
|
-
:root_cert => true,
|
210
|
-
:root_cert_name => '',
|
211
|
-
:root_cert_path => '/',
|
212
|
-
:account_name => '',
|
213
|
-
:password => '',
|
214
|
-
:log_actions => false,
|
215
|
-
:log_responses => false,
|
216
|
-
:log_parser => false,
|
217
|
-
:warn_on_redirect => true,
|
218
|
-
:raise_on_401 => false,
|
219
|
-
:timeout => 60,
|
220
|
-
:ignore_bad_data => false,
|
221
|
-
:grammar => 'fmresultset'
|
222
|
-
} #.merge(options)
|
223
|
-
|
198
|
+
config(*args)
|
199
|
+
raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Server has no host name. Attempted name '#{state[:host]}'.") if state[:host].to_s == ''
|
224
200
|
@databases = Rfm::Factory::DbFactory.new(self)
|
225
201
|
end
|
226
202
|
|
@@ -245,9 +221,11 @@ module Rfm
|
|
245
221
|
# Legacy Rfm method to get/create databases from server object
|
246
222
|
alias_method :db, :databases
|
247
223
|
|
248
|
-
def
|
249
|
-
|
224
|
+
def config(*args)
|
225
|
+
super(:capture_strings_with=>[:host, :account_name, :password])
|
226
|
+
super(*args)
|
250
227
|
end
|
228
|
+
|
251
229
|
|
252
230
|
def host_name; state[:host]; end
|
253
231
|
def scheme; state[:ssl] ? "https" : "http"; end
|
@@ -280,150 +258,6 @@ module Rfm
|
|
280
258
|
# },
|
281
259
|
# { :max_records => 20 }
|
282
260
|
# )
|
283
|
-
def connect(account_name, password, action, args, options = {})
|
284
|
-
grammar_option = options.delete(:grammar)
|
285
|
-
post = args.merge(expand_options(options)).merge({action => ''})
|
286
|
-
grammar = select_grammar(post, :grammar=>grammar_option)
|
287
|
-
http_fetch(host_name, port, "/fmi/xml/#{grammar}.xml", account_name, password, post)
|
288
|
-
end
|
289
|
-
|
290
|
-
def load_layout(layout)
|
291
|
-
post = {'-db' => layout.db.name, '-lay' => layout.name, '-view' => ''}
|
292
|
-
resp = http_fetch(host_name, port, "/fmi/xml/FMPXMLLAYOUT.xml", layout.db.account_name, layout.db.password, post)
|
293
|
-
#remove_namespace(resp.body)
|
294
|
-
end
|
295
|
-
|
296
|
-
def select_grammar(post, options={})
|
297
|
-
grammar = state(options)[:grammar] || 'fmresultset'
|
298
|
-
if grammar.to_s.downcase == 'auto'
|
299
|
-
post.keys.find(){|k| %w(-find -findall -dbnames -layoutnames -scriptnames).include? k.to_s} ? "FMPXMLRESULT" : "fmresultset"
|
300
|
-
else
|
301
|
-
grammar
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
# Removes namespace from fmpxmllayout, so xpath will work
|
306
|
-
def remove_namespace(xml)
|
307
|
-
xml.gsub(/xmlns=\"[^\"]*\"/, '')
|
308
|
-
end
|
309
|
-
|
310
|
-
private
|
311
|
-
|
312
|
-
def http_fetch(host_name, port, path, account_name, password, post_data, limit=10)
|
313
|
-
raise Rfm::CommunicationError.new("While trying to reach the Web Publishing Engine, RFM was redirected too many times.") if limit == 0
|
314
|
-
|
315
|
-
if state[:log_actions] == true
|
316
|
-
#qs = post_data.collect{|key,val| "#{CGI::escape(key.to_s)}=#{CGI::escape(val.to_s)}"}.join("&")
|
317
|
-
qs_unescaped = post_data.collect{|key,val| "#{key.to_s}=#{val.to_s}"}.join("&")
|
318
|
-
#warn "#{@scheme}://#{@host_name}:#{@port}#{path}?#{qs}"
|
319
|
-
warn "#{scheme}://#{host_name}:#{port}#{path}?#{qs_unescaped}"
|
320
|
-
end
|
321
|
-
|
322
|
-
request = Net::HTTP::Post.new(path)
|
323
|
-
request.basic_auth(account_name, password)
|
324
|
-
request.set_form_data(post_data)
|
325
|
-
|
326
|
-
response = Net::HTTP.new(host_name, port)
|
327
|
-
#ADDED LONG TIMEOUT TIMOTHY TING 05/12/2011
|
328
|
-
response.open_timeout = response.read_timeout = state[:timeout]
|
329
|
-
if state[:ssl]
|
330
|
-
response.use_ssl = true
|
331
|
-
if state[:root_cert]
|
332
|
-
response.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
333
|
-
response.ca_file = File.join(state[:root_cert_path], state[:root_cert_name])
|
334
|
-
else
|
335
|
-
response.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
response = response.start { |http| http.request(request) }
|
340
|
-
if state[:log_responses] == true
|
341
|
-
response.to_hash.each { |key, value| warn "#{key}: #{value}" }
|
342
|
-
warn response.body
|
343
|
-
end
|
344
|
-
|
345
|
-
case response
|
346
|
-
when Net::HTTPSuccess
|
347
|
-
response
|
348
|
-
when Net::HTTPRedirection
|
349
|
-
if state[:warn_on_redirect]
|
350
|
-
warn "The web server redirected to " + response['location'] +
|
351
|
-
". You should revise your connection hostname or fix your server configuration if possible to improve performance."
|
352
|
-
end
|
353
|
-
newloc = URI.parse(response['location'])
|
354
|
-
http_fetch(newloc.host, newloc.port, newloc.request_uri, account_name, password, post_data, limit - 1)
|
355
|
-
when Net::HTTPUnauthorized
|
356
|
-
msg = "The account name (#{account_name}) or password provided is not correct (or the account doesn't have the fmxml extended privilege)."
|
357
|
-
raise Rfm::AuthenticationError.new(msg)
|
358
|
-
when Net::HTTPNotFound
|
359
|
-
msg = "Could not talk to FileMaker because the Web Publishing Engine is not responding (server returned 404)."
|
360
|
-
raise Rfm::CommunicationError.new(msg)
|
361
|
-
else
|
362
|
-
msg = "Unexpected response from server: #{response.code} (#{response.class.to_s}). Unable to communicate with the Web Publishing Engine."
|
363
|
-
raise Rfm::CommunicationError.new(msg)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
def expand_options(options)
|
368
|
-
result = {}
|
369
|
-
field_mapping = options.delete(:field_mapping) || {}
|
370
|
-
#puts field_mapping.to_yaml
|
371
|
-
options.each do |key,value|
|
372
|
-
case key.to_sym
|
373
|
-
when :max_portal_rows
|
374
|
-
result['-relatedsets.max'] = value
|
375
|
-
result['-relatedsets.filter'] = 'layout'
|
376
|
-
when :max_records
|
377
|
-
result['-max'] = value
|
378
|
-
when :skip_records
|
379
|
-
result['-skip'] = value
|
380
|
-
when :sort_field
|
381
|
-
if value.kind_of? Array
|
382
|
-
raise Rfm::ParameterError.new(":sort_field can have at most 9 fields, but you passed an array with #{value.size} elements.") if value.size > 9
|
383
|
-
value.each_index { |i| result["-sortfield.#{i+1}"] = field_mapping[value[i]] || value[i] }
|
384
|
-
else
|
385
|
-
result["-sortfield.1"] = field_mapping[value] || value
|
386
|
-
end
|
387
|
-
when :sort_order
|
388
|
-
if value.kind_of? Array
|
389
|
-
raise Rfm::ParameterError.new(":sort_order can have at most 9 fields, but you passed an array with #{value.size} elements.") if value.size > 9
|
390
|
-
value.each_index { |i| result["-sortorder.#{i+1}"] = value[i] }
|
391
|
-
else
|
392
|
-
result["-sortorder.1"] = value
|
393
|
-
end
|
394
|
-
when :post_script
|
395
|
-
if value.class == Array
|
396
|
-
result['-script'] = value[0]
|
397
|
-
result['-script.param'] = value[1]
|
398
|
-
else
|
399
|
-
result['-script'] = value
|
400
|
-
end
|
401
|
-
when :pre_find_script
|
402
|
-
if value.class == Array
|
403
|
-
result['-script.prefind'] = value[0]
|
404
|
-
result['-script.prefind.param'] = value[1]
|
405
|
-
else
|
406
|
-
result['-script.presort'] = value
|
407
|
-
end
|
408
|
-
when :pre_sort_script
|
409
|
-
if value.class == Array
|
410
|
-
result['-script.presort'] = value[0]
|
411
|
-
result['-script.presort.param'] = value[1]
|
412
|
-
else
|
413
|
-
result['-script.presort'] = value
|
414
|
-
end
|
415
|
-
when :response_layout
|
416
|
-
result['-lay.response'] = value
|
417
|
-
when :logical_operator
|
418
|
-
result['-lop'] = value
|
419
|
-
when :modification_id
|
420
|
-
result['-modid'] = value
|
421
|
-
else
|
422
|
-
raise Rfm::ParameterError.new("Invalid option: #{key}")
|
423
|
-
end
|
424
|
-
end
|
425
|
-
return result
|
426
|
-
end
|
427
261
|
|
428
262
|
end
|
429
263
|
end
|
data/lib/rfm/utilities/config.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
module Rfm
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
#
|
3
|
+
# Top level config hash accepts any defined config parameters,
|
4
|
+
# or group-name keys pointing to config subsets.
|
5
|
+
# The subsets can be any grouping of defined config parameters, as a hash.
|
6
|
+
# See CONFIG_KEYS for defined config parameters.
|
7
|
+
#
|
8
|
+
module Config
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
CONFIG_KEYS = %w(
|
5
12
|
file_name
|
6
13
|
file_path
|
7
14
|
parser
|
@@ -26,17 +33,11 @@ module Rfm
|
|
26
33
|
parent
|
27
34
|
grammar
|
28
35
|
field_mapping
|
36
|
+
capture_strings_with
|
37
|
+
logger
|
29
38
|
)
|
30
39
|
|
31
|
-
CONFIG_DONT_STORE = %w(strings using parents symbols objects)
|
32
|
-
|
33
|
-
# Top level config hash accepts any defined config parameters,
|
34
|
-
# or group-name keys pointing to config subsets.
|
35
|
-
# The subsets can be any grouping of defined config parameters, as a hash.
|
36
|
-
# See CONFIG_KEYS for defined config parameters.
|
37
|
-
#
|
38
|
-
module Config
|
39
|
-
require 'yaml'
|
40
|
+
CONFIG_DONT_STORE = %w(strings using parents symbols objects) #capture_strings_with)
|
40
41
|
|
41
42
|
extend self
|
42
43
|
@config = {}
|
@@ -57,17 +58,17 @@ module Rfm
|
|
57
58
|
# Factory.layout 'my_layout', :my_group # to get a layout from settings in :my_group
|
58
59
|
#
|
59
60
|
def config(*args, &block)
|
60
|
-
opt = args.rfm_extract_options!
|
61
61
|
@config ||= {}
|
62
|
-
|
62
|
+
return @config if args.empty?
|
63
|
+
config_write(*args, &block)
|
63
64
|
@config
|
64
65
|
end
|
65
66
|
|
66
67
|
# Sets @config just as above config method, but clears @config first.
|
67
68
|
def config_clear(*args)
|
68
|
-
opt = args.rfm_extract_options!
|
69
69
|
@config = {}
|
70
|
-
|
70
|
+
return @config if args.empty?
|
71
|
+
config_write(*args)
|
71
72
|
@config
|
72
73
|
end
|
73
74
|
|
@@ -84,21 +85,15 @@ module Rfm
|
|
84
85
|
# get_config :layout => 'my_layout
|
85
86
|
#
|
86
87
|
def get_config(*arguments)
|
88
|
+
#puts caller_locations(1,1)[0]
|
87
89
|
args = arguments.clone
|
88
90
|
@config ||= {}
|
89
|
-
|
90
|
-
strings =
|
91
|
-
symbols =
|
92
|
-
objects =
|
93
|
-
args.each do |arg|
|
94
|
-
case true
|
95
|
-
when arg.is_a?(String) ; strings << arg
|
96
|
-
when arg.is_a?(Symbol) ; symbols << arg
|
97
|
-
else objects.unshift arg
|
98
|
-
end
|
99
|
-
end
|
91
|
+
options = config_extract_options!(*args)
|
92
|
+
strings = options[:strings].rfm_force_array || []
|
93
|
+
symbols = options[:symbols].rfm_force_array.concat(options[:hash][:use].rfm_force_array) || []
|
94
|
+
objects = options[:objects].rfm_force_array || []
|
100
95
|
|
101
|
-
rslt = config_merge_with_parent(symbols).merge(
|
96
|
+
rslt = config_merge_with_parent(symbols).merge(options[:hash])
|
102
97
|
#using = rslt[:using].rfm_force_array
|
103
98
|
sanitize_config(rslt, CONFIG_DONT_STORE, false)
|
104
99
|
rslt[:using].delete ""
|
@@ -106,12 +101,16 @@ module Rfm
|
|
106
101
|
rslt.merge(:strings=>strings, :objects=>objects)
|
107
102
|
end
|
108
103
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
104
|
+
def state(*args)
|
105
|
+
return @_state if args.empty? && !@state.nil? && (RUBY_VERSION[0,1].to_i > 1 ? (caller_locations(1,1) == @_last_state_caller) : false)
|
106
|
+
@_state = get_config(*args)
|
107
|
+
(@_last_state_caller = caller_locations(1,1)) if RUBY_VERSION[0,1].to_i > 1
|
108
|
+
@_state
|
109
|
+
end
|
110
|
+
|
111
|
+
def log
|
112
|
+
Rfm.log
|
113
|
+
end
|
115
114
|
|
116
115
|
protected
|
117
116
|
|
@@ -130,38 +129,66 @@ module Rfm
|
|
130
129
|
end.inject({}){|h,a| h.merge(a)}
|
131
130
|
) || {}
|
132
131
|
end
|
133
|
-
|
132
|
+
|
133
|
+
# Get the top-level configuration from yml file and RFM_CONFIG
|
134
|
+
def get_config_base
|
135
|
+
get_config_file.merge((defined?(RFM_CONFIG) and RFM_CONFIG.is_a?(Hash)) ? RFM_CONFIG : {})
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get the parent configuration object according to @config[:parent]
|
139
|
+
def get_config_parent
|
140
|
+
@config ||= {}
|
141
|
+
parent = case
|
142
|
+
when @config[:parent].is_a?(String); eval(@config[:parent])
|
143
|
+
when !@config[:parent].nil? && @config[:parent].is_a?(Rfm::Config); @config[:parent]
|
144
|
+
else eval('Rfm::Config')
|
145
|
+
end
|
146
|
+
end
|
134
147
|
|
135
148
|
# Merge args into @config, as :use=>[arg1, arg2, ...]
|
136
149
|
# Then merge optional config hash into @config.
|
137
|
-
# Pass in a block to use with
|
138
|
-
def config_write(opt, args)
|
139
|
-
|
140
|
-
|
141
|
-
@config.merge!(
|
142
|
-
|
150
|
+
# Pass in a block to use with parsed config in args.
|
151
|
+
def config_write(*args) #(opt, args)
|
152
|
+
options = config_extract_options!(*args)
|
153
|
+
options[:symbols].each{|a| @config.merge!(:use=>a.to_sym){|h,v1,v2| [v1].flatten << v2 }}
|
154
|
+
@config.merge!(options[:hash]).reject! {|k,v| CONFIG_DONT_STORE.include? k.to_s}
|
155
|
+
#options[:hash][:capture_strings_with].rfm_force_array.each do |label|
|
156
|
+
@config[:capture_strings_with].rfm_force_array.each do |label|
|
157
|
+
string = options[:strings].delete_at(0)
|
158
|
+
(@config[label] = string) if string && !string.empty?
|
159
|
+
end
|
160
|
+
parent = (options[:objects].delete_at(0) || options[:hash][:parent])
|
161
|
+
(@config[:parent] = parent) if parent
|
162
|
+
yield(options) if block_given?
|
143
163
|
end
|
144
164
|
|
145
165
|
# Get composite config from all levels, processing :use parameters at each level
|
146
166
|
def config_merge_with_parent(filters=nil)
|
147
|
-
|
148
|
-
|
167
|
+
@config ||= {}
|
168
|
+
|
169
|
+
# Get upstream compilation
|
170
|
+
upstream = if (self != Rfm::Config)
|
171
|
+
#puts [self, @config[:parent], get_config_parent].join(', ')
|
172
|
+
get_config_parent.config_merge_with_parent
|
149
173
|
else
|
150
|
-
|
174
|
+
get_config_base
|
151
175
|
end.clone
|
152
|
-
|
153
|
-
|
154
|
-
|
176
|
+
|
177
|
+
upstream[:using] ||= []
|
178
|
+
upstream[:parents] ||= ['file', 'RFM_CONFIG']
|
155
179
|
|
156
180
|
filters = (@config[:use].rfm_force_array | filters.rfm_force_array).compact
|
157
|
-
|
158
|
-
|
181
|
+
|
182
|
+
rslt = config_filter(upstream, filters).merge(config_filter(@config, filters))
|
183
|
+
|
159
184
|
rslt[:using].concat((@config[:use].rfm_force_array | filters).compact.flatten) #.join
|
160
185
|
rslt[:parents] << @config[:parent].to_s
|
161
186
|
|
162
187
|
rslt.delete :parent
|
163
188
|
|
164
|
-
rslt
|
189
|
+
rslt || {}
|
190
|
+
# rescue
|
191
|
+
# puts "Config#config_merge_with_parent for '#{self.class}' falied with #{$1}"
|
165
192
|
end
|
166
193
|
|
167
194
|
# Returns a configuration hash overwritten by :use filters in the hash
|
@@ -169,15 +196,33 @@ module Rfm
|
|
169
196
|
def config_filter(conf, filters=nil)
|
170
197
|
conf = conf.clone
|
171
198
|
filters = (conf[:use].rfm_force_array | filters.rfm_force_array).compact
|
172
|
-
filters.each{|f| next unless conf[f]; conf.merge!(conf[f] || {})} if !filters.
|
199
|
+
filters.each{|f| next unless conf[f]; conf.merge!(conf[f] || {})} if (!filters.nil? && !filters.empty?)
|
173
200
|
conf.delete(:use)
|
174
201
|
conf
|
175
202
|
end
|
176
203
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
204
|
+
# Extract arguments into strings, symbols, objects, hash.
|
205
|
+
def config_extract_options!(*args)
|
206
|
+
strings, symbols, objects = [], [], []
|
207
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
208
|
+
args.each do |a|
|
209
|
+
case
|
210
|
+
when a.is_a?(String); strings << a
|
211
|
+
when a.is_a?(Symbol); symbols << a
|
212
|
+
else objects << a
|
213
|
+
end
|
214
|
+
end
|
215
|
+
{:strings=>strings, :symbols=>symbols, :objects=>objects, :hash=>options}
|
216
|
+
end
|
217
|
+
|
218
|
+
# Remove un-registered keys from a configuration hash.
|
219
|
+
# Keep should be a list of strings representing keys to keep.
|
220
|
+
def sanitize_config(conf={}, keep=[], dupe=false)
|
221
|
+
(conf = conf.clone) if dupe
|
222
|
+
conf.reject!{|k,v| (!CONFIG_KEYS.include?(k.to_s) or [{},[],''].include?(v)) and !keep.include? k.to_s }
|
223
|
+
conf
|
224
|
+
end
|
225
|
+
|
181
226
|
|
182
227
|
end # module Config
|
183
228
|
|