rhodes-framework 1.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.
- data/.gitignore +2 -0
- data/History.txt +37 -0
- data/Manifest.txt +66 -0
- data/README.rdoc +2 -0
- data/Rakefile +50 -0
- data/lib/ServeME.rb +7 -0
- data/lib/TestServe.rb +9 -0
- data/lib/bsearch.rb +120 -0
- data/lib/builtinME.rb +626 -0
- data/lib/date/format.rb +1339 -0
- data/lib/date.rb +1792 -0
- data/lib/dateME.rb +24 -0
- data/lib/erb.rb +896 -0
- data/lib/find.rb +81 -0
- data/lib/rational.rb +19 -0
- data/lib/rationalME.rb +530 -0
- data/lib/rho/render.rb +51 -0
- data/lib/rho/rho.rb +255 -0
- data/lib/rho/rhoapplication.rb +36 -0
- data/lib/rho/rhocontact.rb +110 -0
- data/lib/rho/rhocontroller.rb +35 -0
- data/lib/rho/rhofsconnector.rb +32 -0
- data/lib/rho/rhosupport.rb +146 -0
- data/lib/rho/rhoviewhelpers.rb +130 -0
- data/lib/rho.rb +1 -0
- data/lib/rhodes-framework.rb +2 -0
- data/lib/rhodes.rb +9 -0
- data/lib/rhoframework.rb +38 -0
- data/lib/rhofsconnector.rb +1 -0
- data/lib/rhom/rhom.rb +58 -0
- data/lib/rhom/rhom_db_adapter.rb +185 -0
- data/lib/rhom/rhom_db_adapterME.rb +93 -0
- data/lib/rhom/rhom_object.rb +69 -0
- data/lib/rhom/rhom_object_factory.rb +309 -0
- data/lib/rhom/rhom_source.rb +60 -0
- data/lib/rhom.rb +1 -0
- data/lib/singleton.rb +137 -0
- data/lib/time.rb +489 -0
- data/lib/version.rb +8 -0
- data/res/sqlite3/constants.rb +49 -0
- data/res/sqlite3/database.rb +715 -0
- data/res/sqlite3/driver/dl/api.rb +154 -0
- data/res/sqlite3/driver/dl/driver.rb +307 -0
- data/res/sqlite3/driver/native/driver.rb +257 -0
- data/res/sqlite3/errors.rb +68 -0
- data/res/sqlite3/pragmas.rb +271 -0
- data/res/sqlite3/resultset.rb +176 -0
- data/res/sqlite3/sqlite3_api.rb +0 -0
- data/res/sqlite3/statement.rb +230 -0
- data/res/sqlite3/translator.rb +109 -0
- data/res/sqlite3/value.rb +57 -0
- data/res/sqlite3/version.rb +14 -0
- data/rhodes-framework.gemspec +18 -0
- data/rhodes.gemspec +18 -0
- data/spec/app_manifest.txt +4 -0
- data/spec/configs/account.rb +3 -0
- data/spec/configs/case.rb +3 -0
- data/spec/configs/employee.rb +3 -0
- data/spec/rho_controller_spec.rb +144 -0
- data/spec/rho_spec.rb +75 -0
- data/spec/rhom_object_factory_spec.rb +372 -0
- data/spec/rhom_spec.rb +45 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/stubs.rb +39 -0
- data/spec/syncdbtest.sqlite +0 -0
- metadata +202 -0
data/lib/rho/rho.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'rho/render'
|
3
|
+
require 'rho/rhoapplication'
|
4
|
+
require 'rhom'
|
5
|
+
require 'rhofsconnector'
|
6
|
+
|
7
|
+
module Rho
|
8
|
+
class RHO
|
9
|
+
APPLICATIONS = {}
|
10
|
+
|
11
|
+
def initialize(app_manifest_filename=nil)
|
12
|
+
puts "Calling RHO.initialize"
|
13
|
+
Rhom::RhomDbAdapter::open(Rho::RhoFSConnector::get_db_fullpathname)
|
14
|
+
if app_manifest_filename
|
15
|
+
process_model_dirs(app_manifest_filename)
|
16
|
+
else
|
17
|
+
process_model_dirs(Rho::RhoFSConnector::get_app_manifest_filename)
|
18
|
+
end
|
19
|
+
init_sources
|
20
|
+
end
|
21
|
+
|
22
|
+
# make sure we close the database file
|
23
|
+
def self.finalize
|
24
|
+
Rhom::RhomDbAdapter::close
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return the directories where we need to load configuration files
|
28
|
+
def process_model_dirs(app_manifest_filename=nil)
|
29
|
+
File.open(app_manifest_filename).each do |line|
|
30
|
+
require File.join(File.dirname(app_manifest_filename), line.chop)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# setup the sources table and model attributes for all applications
|
35
|
+
def init_sources
|
36
|
+
if defined? Rho::RhoConfig::sources
|
37
|
+
|
38
|
+
# quick and dirty way to get unique array of hashes
|
39
|
+
uniq_sources = Rho::RhoConfig::sources.values.inject([]) { |result,h|
|
40
|
+
result << h unless result.include?(h); result
|
41
|
+
}
|
42
|
+
|
43
|
+
# generate unique source list in database for sync
|
44
|
+
uniq_sources.each do |source|
|
45
|
+
|
46
|
+
src_id = source['source_id']
|
47
|
+
url = source['url']
|
48
|
+
if !self.source_initialized?(src_id)
|
49
|
+
Rhom::RhomDbAdapter::insert_into_table('sources',
|
50
|
+
{"source_id"=>src_id,"source_url"=>url})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def source_initialized?(source_id)
|
57
|
+
Rhom::RhomDbAdapter::select_from_table('sources','*', 'source_id'=>source_id).size > 0 ? true : false
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_app(appname)
|
61
|
+
if (APPLICATIONS[appname].nil?)
|
62
|
+
require RhoApplication::get_app_path(appname)+'application'
|
63
|
+
#APPLICATIONS[appname] = Object.const_get(appname+'Application').new
|
64
|
+
APPLICATIONS[appname] = Object.const_get('AppApplication').new
|
65
|
+
end
|
66
|
+
APPLICATIONS[appname]
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_start_path
|
70
|
+
Rho::RhoConfig.start_path
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_options_path
|
74
|
+
Rho::RhoConfig.options_path
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_rhobundle_zip_url
|
78
|
+
Rho::RhoConfig.rhobundle_zip_url
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_rhobundle_zip_pwd
|
82
|
+
Rho::RhoConfig.rhobundle_zip_pwd
|
83
|
+
end
|
84
|
+
|
85
|
+
def serve(req)
|
86
|
+
begin
|
87
|
+
puts 'inside RHO.serve...'
|
88
|
+
res = init_response
|
89
|
+
get_app(req['application']).send :serve, req, res
|
90
|
+
return send_response(res)
|
91
|
+
rescue Exception => e
|
92
|
+
return send_error(e)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def serve_hash(req)
|
97
|
+
begin
|
98
|
+
puts 'inside RHO.serve...'
|
99
|
+
res = init_response
|
100
|
+
get_app(req['application']).send :serve, req, res
|
101
|
+
return send_response_hash(res)
|
102
|
+
rescue Exception => e
|
103
|
+
return send_error(e,500,true)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def serve_index(index_name)
|
108
|
+
begin
|
109
|
+
puts 'inside RHO.serve_index: ' + index_name
|
110
|
+
res = init_response
|
111
|
+
res['request-body'] = RhoController::renderfile(index_name)
|
112
|
+
return send_response(res)
|
113
|
+
rescue Exception => e
|
114
|
+
return send_error(e)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def serve_index_hash(index_name)
|
119
|
+
begin
|
120
|
+
puts 'inside RHO.serve_index: ' + index_name
|
121
|
+
res = init_response
|
122
|
+
res['request-body'] = RhoController::renderfile(index_name)
|
123
|
+
return send_response_hash(res)
|
124
|
+
rescue Exception => e
|
125
|
+
return send_error(e.message, 500, true)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def init_response(status=200,message="OK",body="")
|
130
|
+
res = Hash.new
|
131
|
+
res['status'] = status
|
132
|
+
res['message'] = message
|
133
|
+
res['headers'] =
|
134
|
+
{
|
135
|
+
'Date' => Time.now.httpdate,
|
136
|
+
'Content-Type' => 'text/html',
|
137
|
+
'Content-Length' => 0,
|
138
|
+
'Connection' => 'close'
|
139
|
+
}
|
140
|
+
res['request-body'] = body
|
141
|
+
res
|
142
|
+
end
|
143
|
+
|
144
|
+
CR = "\x0d"
|
145
|
+
LF = "\x0a"
|
146
|
+
CRLF = "\x0d\x0a"
|
147
|
+
|
148
|
+
def send_response(res)
|
149
|
+
res['headers']['Content-Length'] = res['request-body'].nil? ? 0 : res['request-body'].length
|
150
|
+
data = "HTTP/1.1 #{res['status'].to_s} #{res['message']}" + CRLF
|
151
|
+
res['headers'].each{|key, value|
|
152
|
+
tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase }
|
153
|
+
data << "#{tmp}: #{value}" << CRLF
|
154
|
+
}
|
155
|
+
data << CRLF
|
156
|
+
data << res['request-body']
|
157
|
+
data
|
158
|
+
end
|
159
|
+
|
160
|
+
def send_response_hash(res)
|
161
|
+
resp = Hash.new
|
162
|
+
res['headers']['Content-Length'] = res['request-body'].nil? ? 0 : res['request-body'].length
|
163
|
+
res['headers'].each{|key, value|
|
164
|
+
tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase }
|
165
|
+
resp[tmp] = value
|
166
|
+
}
|
167
|
+
resp['request-body'] = res['request-body']
|
168
|
+
resp['status'] = res['status']
|
169
|
+
resp['message'] = res['message']
|
170
|
+
|
171
|
+
resp
|
172
|
+
end
|
173
|
+
|
174
|
+
def send_error(exception=nil,status=500,hash=false)
|
175
|
+
body=''
|
176
|
+
body << <<-_HTML_STRING_
|
177
|
+
<html>
|
178
|
+
<head>
|
179
|
+
<title>Server Error</title>
|
180
|
+
<meta name="viewport" content="width=320"/>
|
181
|
+
</head>
|
182
|
+
<body>
|
183
|
+
<p>
|
184
|
+
_HTML_STRING_
|
185
|
+
body << 'Error: ' << exception.message << "<br/>" if exception
|
186
|
+
body << 'Trace: ' << exception.backtrace.join("\n") if exception
|
187
|
+
body << <<-_HTML_STRING_
|
188
|
+
</p>
|
189
|
+
</body>
|
190
|
+
</html>
|
191
|
+
|
192
|
+
_HTML_STRING_
|
193
|
+
if ( hash )
|
194
|
+
send_response_hash(init_response(status,"Server error",body))
|
195
|
+
else
|
196
|
+
send_response(init_response(status,"Server error",body))
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end # RHO
|
200
|
+
|
201
|
+
# Generic configuration class which accepts hashes with unique keys
|
202
|
+
class RhoConfig
|
203
|
+
@@sources = {}
|
204
|
+
@@start_path = '/'
|
205
|
+
@@options_path = '/'
|
206
|
+
@@rhobundle_zip_url = nil
|
207
|
+
@@rhobundle_zip_pwd = nil
|
208
|
+
|
209
|
+
class << self
|
210
|
+
def sources
|
211
|
+
@@sources
|
212
|
+
end
|
213
|
+
|
214
|
+
def options_path
|
215
|
+
@@options_path
|
216
|
+
end
|
217
|
+
|
218
|
+
def options_path=(path=nil)
|
219
|
+
@@options_path = path if path
|
220
|
+
end
|
221
|
+
|
222
|
+
def start_path
|
223
|
+
@@start_path
|
224
|
+
end
|
225
|
+
|
226
|
+
def start_path=(path=nil)
|
227
|
+
@@start_path = path if path
|
228
|
+
end
|
229
|
+
|
230
|
+
def rhobundle_zip_url
|
231
|
+
@@rhobundle_zip_url
|
232
|
+
end
|
233
|
+
|
234
|
+
def rhobundle_zip_url=(url=nil)
|
235
|
+
@@rhobundle_zip_url = url
|
236
|
+
end
|
237
|
+
|
238
|
+
def rhobundle_zip_pwd
|
239
|
+
@@rhobundle_zip_pwd
|
240
|
+
end
|
241
|
+
|
242
|
+
def rhobundle_zip_pwd=(pwd=nil)
|
243
|
+
@@rhobundle_zip_pwd = pwd
|
244
|
+
end
|
245
|
+
|
246
|
+
def add_source(modelname, new_source=nil)
|
247
|
+
if new_source
|
248
|
+
unless @@sources[new_source]
|
249
|
+
@@sources[modelname] = new_source
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end # RhoConfig
|
255
|
+
end # Rho
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rhom'
|
2
|
+
require 'rhofsconnector'
|
3
|
+
|
4
|
+
module Rho
|
5
|
+
class RhoApplication
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
if @rhom.nil?
|
9
|
+
@rhom = Rhom::Rhom.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def get_app_path(appname)
|
16
|
+
Rho::RhoFSConnector::get_app_path(appname)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_base_app_path
|
20
|
+
Rho::RhoFSConnector::get_base_app_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_model_path(appname, modelname)
|
24
|
+
Rho::RhoFSConnector::get_model_path(appname, modelname)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def serve(req,res)
|
30
|
+
req[:modelpath] = self.class.get_model_path req['application'], req['model']
|
31
|
+
require req[:modelpath]+'controller'
|
32
|
+
res['request-body'] = (Object.const_get(req['model']+'Controller').new).send :serve, @rhom, req, res
|
33
|
+
end
|
34
|
+
|
35
|
+
end # RhoApplication
|
36
|
+
end # Rho
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'bsearch'
|
2
|
+
|
3
|
+
module Rho
|
4
|
+
class RhoContact
|
5
|
+
class << self
|
6
|
+
def find(param)
|
7
|
+
pb = Phonebook::openPhonebook
|
8
|
+
if pb.nil?
|
9
|
+
puts "Can't open phonebook"
|
10
|
+
return nil
|
11
|
+
elsif param == :all or param == 'all'
|
12
|
+
records = Phonebook::getallPhonebookRecords(pb)
|
13
|
+
Phonebook::closePhonebook(pb)
|
14
|
+
return records
|
15
|
+
else
|
16
|
+
record = Phonebook::getPhonebookRecord(pb,param)
|
17
|
+
Phonebook::closePhonebook(pb)
|
18
|
+
return record
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def create!(properties)
|
23
|
+
pb = Phonebook::openPhonebook
|
24
|
+
unless pb.nil?
|
25
|
+
record = Phonebook::createRecord(pb)
|
26
|
+
if record.nil?
|
27
|
+
puts "Can't find record " + properties['id']
|
28
|
+
else
|
29
|
+
properties.each do |key,value|
|
30
|
+
Phonebook::setRecordValue(record,key,value)
|
31
|
+
end
|
32
|
+
Phonebook::addRecord(pb,record)
|
33
|
+
end
|
34
|
+
Phonebook::closePhonebook(pb)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy(recordId)
|
39
|
+
pb = Phonebook::openPhonebook
|
40
|
+
unless pb.nil?
|
41
|
+
record = Phonebook::openPhonebookRecord(pb,recordId)
|
42
|
+
if record.nil?
|
43
|
+
puts "Can't find record " + recordId
|
44
|
+
else
|
45
|
+
Phonebook::deleteRecord(pb,record)
|
46
|
+
end
|
47
|
+
Phonebook::closePhonebook(pb)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_attributes(properties)
|
52
|
+
pb = Phonebook::openPhonebook
|
53
|
+
unless pb.nil?
|
54
|
+
record = Phonebook::openPhonebookRecord(pb,properties['id'])
|
55
|
+
if record.nil?
|
56
|
+
puts "Can't find record " + properties['id']
|
57
|
+
else
|
58
|
+
properties.each do |key,value|
|
59
|
+
Phonebook::setRecordValue(record,key,value)
|
60
|
+
end
|
61
|
+
Phonebook::saveRecord(pb,record)
|
62
|
+
end
|
63
|
+
Phonebook::closePhonebook(pb)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Examples of how to use select method:
|
68
|
+
#
|
69
|
+
# selected = Rho::RhoContact.select('first_name' => 'David') { |x| x[1]['last_name']=='Taylor' }
|
70
|
+
# ==> returns record(s) of the David Taylor
|
71
|
+
#
|
72
|
+
# selected = Rho::RhoContact.select('first_name' => 'Kate')
|
73
|
+
# ==> Returns all records of Kate
|
74
|
+
#
|
75
|
+
# selected = Rho::RhoContact.select('last_name' => 'User') do |x|
|
76
|
+
# x[1]['first_name']=='Test' and x[1]['company_name']=="rhomobile"
|
77
|
+
# end
|
78
|
+
# ==> returns all records of the Test User from the company rhomobile
|
79
|
+
#
|
80
|
+
def select(index, &block)
|
81
|
+
key, value = index.keys[0], index.values[0]
|
82
|
+
if @contacts.nil? or @key != key
|
83
|
+
@key, @contacts = key, find(:all).to_a.sort! {|x,y| x[1][key] <=> y[1][key] }
|
84
|
+
end
|
85
|
+
found = @contacts[@contacts.bsearch_range {|x| x[1][key] <=> value}]
|
86
|
+
unless found.nil? or block.nil?
|
87
|
+
return found.select(&block)
|
88
|
+
end
|
89
|
+
return found
|
90
|
+
end
|
91
|
+
|
92
|
+
def select_by_name(first_last_name, &block)
|
93
|
+
if @contacts.nil?
|
94
|
+
@contacts = find(:all).to_a.sort! do |x,y|
|
95
|
+
x[1]['first_name'] + " " + x[1]['last_name'] <=> y[1]['first_name'] + " " + y[1]['last_name']
|
96
|
+
end
|
97
|
+
end
|
98
|
+
range = @contacts.bsearch_range do |x|
|
99
|
+
x[1]['first_name'] + " " + x[1]['last_name'] <=> first_last_name
|
100
|
+
end
|
101
|
+
found = @contacts[range]
|
102
|
+
unless found.nil? or block.nil?
|
103
|
+
return found.select(&block)
|
104
|
+
end
|
105
|
+
return found
|
106
|
+
end
|
107
|
+
|
108
|
+
end #<< self
|
109
|
+
end # class RhoContact
|
110
|
+
end # module Rho
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rho/render'
|
2
|
+
require 'rho/rhosupport'
|
3
|
+
require 'rho/rhoviewhelpers'
|
4
|
+
|
5
|
+
module Rho
|
6
|
+
class RhoController
|
7
|
+
|
8
|
+
def default_action
|
9
|
+
return Hash['GET','show','PUT','update','POST','update',
|
10
|
+
'DELETE','delete'][@request['request-method']] unless @request['id'].nil?
|
11
|
+
return Hash['GET','index','POST','create'][@request['request-method']]
|
12
|
+
end
|
13
|
+
|
14
|
+
def serve(object_mapping,req,res)
|
15
|
+
@request, @response = req, res;
|
16
|
+
@object_mapping = object_mapping
|
17
|
+
@params = RhoSupport::query_params req
|
18
|
+
send req['action'].nil? ? default_action : req['action']
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns true if the request's header contains "XMLHttpRequest".
|
22
|
+
def xml_http_request?
|
23
|
+
not /XMLHttpRequest/i.match(@request['headers']['X-Requested-With']).nil?
|
24
|
+
end
|
25
|
+
alias xhr? :xml_http_request?
|
26
|
+
|
27
|
+
def redirect(url_params = {},options = {})
|
28
|
+
@response['status'] = options['status'] || 302
|
29
|
+
@response['headers']['Location'] = url_for(url_params)
|
30
|
+
@response['message'] = options['message'] || 'Moved temporarily'
|
31
|
+
return ''
|
32
|
+
end
|
33
|
+
|
34
|
+
end # RhoController
|
35
|
+
end # Rho
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module Rho
|
3
|
+
class RhoFSConnector
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def get_app_path(appname)
|
8
|
+
File.join(__rhoGetCurrentDir(), 'apps/'+appname+'/')
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_base_app_path
|
12
|
+
File.join(__rhoGetCurrentDir(), 'apps/')
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_app_manifest_filename
|
16
|
+
File.join(__rhoGetCurrentDir(), 'apps/app_manifest.txt')
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_model_path(appname, modelname)
|
20
|
+
File.join(__rhoGetCurrentDir(), 'apps/'+appname+'/'+modelname+'/')
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_db_fullpathname
|
24
|
+
if defined? SYNC_DB_FILE
|
25
|
+
File.join(SYNC_DB_FILE)
|
26
|
+
else
|
27
|
+
File.join(__rhoGetCurrentDir(), 'db/syncdb.sqlite')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end # RhoApplication
|
32
|
+
end # Rho
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Rho
|
2
|
+
module RhoSupport
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def url_encode(s)
|
7
|
+
s.to_s.dup.force_encoding("ASCII-8BIT").gsub(/[^a-zA-Z0-9_\-.]/n) {
|
8
|
+
sprintf("%%%02X", $&.unpack("C")[0])
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def _unescape(str, regex) str.gsub(regex){ $1.hex.chr } end
|
13
|
+
|
14
|
+
ESCAPED = /%([0-9a-fA-F]{2})/
|
15
|
+
|
16
|
+
def unescape_form(str)
|
17
|
+
_unescape(str.gsub(/\+/, " "), ESCAPED)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_query_parameters(query_string)
|
21
|
+
return {} if query_string.nil?
|
22
|
+
|
23
|
+
pairs = query_string.split('&').collect do |chunk|
|
24
|
+
next if chunk.empty?
|
25
|
+
key, value = chunk.split('=', 2)
|
26
|
+
next if key.empty?
|
27
|
+
value = value.nil? ? nil : unescape_form(value)
|
28
|
+
[ unescape_form(key), value ]
|
29
|
+
end.compact
|
30
|
+
|
31
|
+
UrlEncodedPairParser.new(pairs).result
|
32
|
+
end
|
33
|
+
|
34
|
+
def query_params(req)
|
35
|
+
params = {}
|
36
|
+
unless req['id'].nil?
|
37
|
+
params['id'] = req['id']
|
38
|
+
end
|
39
|
+
unless req['request-query'].nil? or req['request-query'].length == 0
|
40
|
+
params.merge!(parse_query_parameters(req['request-query']))
|
41
|
+
end
|
42
|
+
unless req['headers'].nil? or req['headers']['Content-Type'].nil?
|
43
|
+
if 'application/x-www-form-urlencoded'.eql? req['headers']['Content-Type']
|
44
|
+
params.merge!(parse_query_parameters(req['request-body']))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
puts "Params: " + params.to_s unless params.empty?
|
48
|
+
params
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class UrlEncodedPairParser < StringScanner #:nodoc:
|
53
|
+
attr_reader :top, :parent, :result
|
54
|
+
|
55
|
+
def initialize(pairs = [])
|
56
|
+
super('')
|
57
|
+
@result = {}
|
58
|
+
pairs.each { |key, value| parse(key, value) }
|
59
|
+
end
|
60
|
+
|
61
|
+
KEY_REGEXP = %r{([^\[\]=&]+)}
|
62
|
+
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
|
63
|
+
|
64
|
+
# Parse the query string
|
65
|
+
def parse(key, value)
|
66
|
+
self.string = key
|
67
|
+
@top, @parent = result, nil
|
68
|
+
|
69
|
+
# First scan the bare key
|
70
|
+
key = scan(KEY_REGEXP) or return
|
71
|
+
key = post_key_check(key)
|
72
|
+
|
73
|
+
# Then scan as many nestings as present
|
74
|
+
until eos?
|
75
|
+
r = scan(BRACKETED_KEY_REGEXP) or return
|
76
|
+
key = self[1]
|
77
|
+
key = post_key_check(key)
|
78
|
+
end
|
79
|
+
|
80
|
+
bind(key, value)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
# After we see a key, we must look ahead to determine our next action. Cases:
|
85
|
+
#
|
86
|
+
# [] follows the key. Then the value must be an array.
|
87
|
+
# = follows the key. (A value comes next)
|
88
|
+
# & or the end of string follows the key. Then the key is a flag.
|
89
|
+
# otherwise, a hash follows the key.
|
90
|
+
def post_key_check(key)
|
91
|
+
if scan(/\[\]/) # a[b][] indicates that b is an array
|
92
|
+
container(key, Array)
|
93
|
+
nil
|
94
|
+
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
|
95
|
+
container(key, Hash)
|
96
|
+
nil
|
97
|
+
else # End of key? We do nothing.
|
98
|
+
key
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add a container to the stack.
|
103
|
+
def container(key, klass)
|
104
|
+
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
|
105
|
+
value = bind(key, klass.new)
|
106
|
+
type_conflict! klass, value unless value.is_a?(klass)
|
107
|
+
push(value)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Push a value onto the 'stack', which is actually only the top 2 items.
|
111
|
+
def push(value)
|
112
|
+
@parent, @top = @top, value
|
113
|
+
end
|
114
|
+
|
115
|
+
# Bind a key (which may be nil for items in an array) to the provided value.
|
116
|
+
def bind(key, value)
|
117
|
+
if top.is_a? Array
|
118
|
+
if key
|
119
|
+
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
|
120
|
+
top[-1][key] = value
|
121
|
+
else
|
122
|
+
top << {key => value}.with_indifferent_access
|
123
|
+
push top.last
|
124
|
+
value = top[key]
|
125
|
+
end
|
126
|
+
else
|
127
|
+
top << value
|
128
|
+
end
|
129
|
+
elsif top.is_a? Hash
|
130
|
+
#key = CGI.unescape(key)
|
131
|
+
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
132
|
+
top[key] ||= value
|
133
|
+
return top[key]
|
134
|
+
else
|
135
|
+
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
136
|
+
end
|
137
|
+
|
138
|
+
return value
|
139
|
+
end
|
140
|
+
|
141
|
+
def type_conflict!(klass, value)
|
142
|
+
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end # RhoSupport
|
146
|
+
end # Rho
|