cuca 0.03 → 0.04
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/CHANGELOG +39 -0
- data/application_skeleton/conf/config.rb +24 -5
- data/application_skeleton/public/dispatch.cgi +2 -15
- data/application_skeleton/public/dispatch.fcgi +4 -15
- data/lib/cuca.rb +5 -1
- data/lib/cuca/app.rb +97 -170
- data/lib/cuca/cgi_emu.rb +9 -0
- data/lib/cuca/cgi_fix.rb +10 -0
- data/lib/cuca/config.rb +67 -0
- data/lib/cuca/const.rb +1 -1
- data/lib/cuca/controller.rb +67 -26
- data/lib/cuca/layout.rb +1 -0
- data/lib/cuca/session.rb +7 -3
- data/lib/cuca/sessionpage.rb +1 -0
- data/lib/cuca/stdlib/arform.rb +12 -1
- data/lib/cuca/stdlib/listwidget/dblist.rb +2 -1
- data/lib/cuca/stdlib/listwidget/list.rb +5 -4
- data/lib/cuca/test/helpers.rb +3 -1
- data/lib/cuca/urlmap-original.rb +324 -0
- data/lib/cuca/urlmap.rb +68 -13
- data/lib/cuca/urlmap2.rb +22 -0
- data/lib/cuca/widget.rb +6 -6
- data/tests/all.rb +8 -0
- data/tests/app.rb +37 -0
- data/tests/controller.rb +163 -0
- data/tests/generator_markaby.rb +123 -0
- data/tests/generator_view.rb +76 -0
- data/tests/mimetypes.rb +20 -0
- data/tests/test_app/README +1 -0
- data/tests/test_app/app/_views/test_template.rhtml +1 -0
- data/tests/test_app/app/agent/index.rb +0 -0
- data/tests/test_app/app/agent/list.rb +0 -0
- data/tests/test_app/app/user/__username/index.rb +0 -0
- data/tests/test_app/app/user/list.rb +0 -0
- data/tests/test_app/conf/config.rb +0 -0
- data/tests/test_app/conf/environment.rb +0 -0
- data/tests/test_app/log/messages +1 -0
- data/tests/urlmap.rb +96 -0
- data/tests/widget.rb +153 -0
- metadata +33 -4
data/lib/cuca/cgi_emu.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# little fake cgi class that allows automated testing of cgi application
|
2
2
|
|
3
3
|
class CGIEmu < CGI
|
4
|
+
|
5
|
+
attr_reader :out_params
|
6
|
+
attr_reader :out_content
|
7
|
+
|
4
8
|
class EnvTable
|
5
9
|
def initialize(options)
|
6
10
|
@test_path_info = options['PATH_INFO'] || '/'
|
@@ -63,5 +67,10 @@ class CGIEmu < CGI
|
|
63
67
|
super()
|
64
68
|
end
|
65
69
|
|
70
|
+
def out(params, &block)
|
71
|
+
@out_params = params
|
72
|
+
@out_content = block.call
|
73
|
+
end
|
74
|
+
|
66
75
|
end
|
67
76
|
|
data/lib/cuca/cgi_fix.rb
CHANGED
@@ -55,4 +55,14 @@ class CGI # :nodoc:
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
# cgi implementation test
|
59
|
+
def cgidump
|
60
|
+
s = ""
|
61
|
+
s << "-----ENV------<br>"
|
62
|
+
self.env_table.each_pair { |k,v| s << "#{k} => #{v}<br>" }
|
63
|
+
s << "--------------<br>"
|
64
|
+
s << "Server Software: #{self.server_software}<br>"
|
65
|
+
s << "PATH INFO: #{self.path_info}<br>"
|
66
|
+
end
|
67
|
+
|
58
68
|
end
|
data/lib/cuca/config.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module Cuca
|
2
|
+
|
3
|
+
# == Configure the application
|
4
|
+
# App::Config is normally called from conf/environment.rb . The attributes below the framework
|
5
|
+
# will make use of, but you can always define own ones.
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
#
|
9
|
+
# Cuca::App.configure do |conf|
|
10
|
+
# conf.include_directories = %{_widgets _special_widgets}
|
11
|
+
# conf.database = 'mydb' # application specific
|
12
|
+
# end
|
13
|
+
# === Attributes:
|
14
|
+
#
|
15
|
+
# * include_directories
|
16
|
+
# An array of directories that will be automatically included if you call an action.
|
17
|
+
# If one of the directories is found in the path where the action file is located it will require
|
18
|
+
# all files found. (default: %w{_controllers _widgets _layouts} )
|
19
|
+
# Optionally it's possible to autoload these support files. To do this you must pass an
|
20
|
+
# array of hashes to the method with the following keys:
|
21
|
+
# - :dir The name of the directory (e.g. _controllers)
|
22
|
+
# - :class_naming A proc that will take one parameter and returns the name of the class
|
23
|
+
# found within the file. It will get the filename as parameter.
|
24
|
+
# example :class_naming => Proc.new { |file_name| file_name.capitalize+'Controller' }
|
25
|
+
# * log_level - default 3 (future?)
|
26
|
+
# * magic_prefix - default: '__default_' (see Cuca::Controller)
|
27
|
+
# * display_errors - default: true , display errors in browser (swith off within production systems)
|
28
|
+
class Config < Hash
|
29
|
+
def method_missing(m, *params)
|
30
|
+
met = m.id2name
|
31
|
+
|
32
|
+
# raise NoMethodError
|
33
|
+
if met[met.size-1].chr == '=' then
|
34
|
+
self[met[0..met.size-2]] = params[0]
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
38
|
+
if met[-2..-1] == '<<' then
|
39
|
+
self[met[0..met.size-3]] ||= []
|
40
|
+
self[met[0..met.size-3]] << params[0]
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
return self[met] unless self[met].nil?
|
45
|
+
|
46
|
+
raise NoMethodError
|
47
|
+
end
|
48
|
+
|
49
|
+
# some default stuff
|
50
|
+
def initialize
|
51
|
+
self['include_directories'] = %w{_controllers _widgets _layouts}
|
52
|
+
self['log_level'] = 3
|
53
|
+
self['magic_prefix'] = '__default_'
|
54
|
+
self['session_key'] = 'cuca_session'
|
55
|
+
self['session_prefix'] = 'cuca.'
|
56
|
+
self['session_valid'] = 3600*24
|
57
|
+
self['view_directory'] = 'app/_views' # where to find views for the view/erb generator
|
58
|
+
self['http_404'] = '404.html'
|
59
|
+
self['http_500'] = '500.html'
|
60
|
+
self['default_mime_type'] = 'text/html'
|
61
|
+
self['display_errors'] = true
|
62
|
+
self['http_static_content_expires'] = 300 # expires http header for static content (only if served by the dispatcher)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
data/lib/cuca/const.rb
CHANGED
data/lib/cuca/controller.rb
CHANGED
@@ -22,7 +22,7 @@ end
|
|
22
22
|
# the output method you must implement run or post or get or any combination.
|
23
23
|
#
|
24
24
|
# Additionally you can also define a wrapping layout (using the layout function) and you can
|
25
|
-
# run code before a certain action is called using
|
25
|
+
# run code before and after a certain action is called using before/after_filter's.
|
26
26
|
#
|
27
27
|
# == Naming
|
28
28
|
#
|
@@ -95,8 +95,10 @@ end
|
|
95
95
|
#
|
96
96
|
# def get
|
97
97
|
# mab do
|
98
|
-
# h1 "Welcome to the Fancy Page called #{@page_title}"
|
99
|
-
# p { text "Welcome to my"
|
98
|
+
# h1 { "Welcome to the Fancy Page called #{@page_title}" }
|
99
|
+
# p { text "Welcome to my"
|
100
|
+
# b { "paragraph" }
|
101
|
+
# }
|
100
102
|
# end
|
101
103
|
# end
|
102
104
|
# end
|
@@ -142,50 +144,72 @@ class Controller < Widget
|
|
142
144
|
|
143
145
|
|
144
146
|
private
|
145
|
-
def self.define_filter_method(chain_name, method_name)
|
147
|
+
def self.define_filter_method(chain_name, method_name, priority=50)
|
146
148
|
begin
|
147
149
|
filters = self.send(chain_name)
|
148
|
-
filters = "#{filters};#{method_name}"
|
150
|
+
filters = "#{filters};#{method_name}|#{priority.to_s}"
|
149
151
|
define_attr_method(chain_name, filters)
|
150
152
|
rescue NoMethodError => e
|
151
|
-
define_attr_method(chain_name, method_name)
|
153
|
+
define_attr_method(chain_name, "#{method_name}|#{priority.to_s}")
|
152
154
|
end
|
153
155
|
end
|
154
156
|
|
155
157
|
# One or more before filter can set by an application Controller
|
156
158
|
# The instance methods will be run before the action get ran (get/post/run)
|
159
|
+
# If you have many filters and need order you can set the priority option.
|
160
|
+
# Lower priorities will be ran first.
|
157
161
|
public
|
158
|
-
def self.before_filter(method)
|
159
|
-
define_filter_method(:def_before_filter, method)
|
162
|
+
def self.before_filter(method, priority = 50)
|
163
|
+
define_filter_method(:def_before_filter, method, priority)
|
160
164
|
end
|
161
165
|
|
162
166
|
# Priority before filters will be ran before any before filters
|
163
|
-
# this should be only used internally
|
167
|
+
# this should be only used internally or for core extension
|
164
168
|
public
|
165
169
|
def self.priority_before_filter(method)
|
166
170
|
define_filter_method(:def_priority_before_filter, method)
|
167
171
|
end
|
168
172
|
|
173
|
+
# Priority after filters will be ran after any after filters
|
174
|
+
# this should be only used internally or for core extension
|
175
|
+
# also this gets called no matter if anyone raises :stop method
|
176
|
+
public
|
177
|
+
def self.priority_after_filter(method)
|
178
|
+
define_filter_method(:def_priority_after_filter, method)
|
179
|
+
end
|
180
|
+
|
169
181
|
# after_filter - get run after the controller action has be executed
|
182
|
+
# If you have many filters and need order you can set the priority option.
|
183
|
+
# Lower priorities will be ran first.
|
170
184
|
public
|
171
|
-
def self.after_filter(method)
|
172
|
-
define_filter_method(:def_after_filter, method)
|
185
|
+
def self.after_filter(method, priority = 50)
|
186
|
+
define_filter_method(:def_after_filter, method, priority)
|
173
187
|
end
|
174
188
|
|
175
189
|
private
|
176
190
|
def run_filters(chain_name, debug_filter_names)
|
177
|
-
|
178
|
-
return if
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
191
|
+
filter_str = self.class.run_attr_method(chain_name.to_s)
|
192
|
+
return if filter_str.nil?
|
193
|
+
|
194
|
+
filters = []
|
195
|
+
filter_str.split(';').each do |f|
|
196
|
+
next if f.strip == ''
|
197
|
+
mp = f.split('|')
|
198
|
+
filters << [mp[0], mp[1]]
|
199
|
+
end
|
200
|
+
|
201
|
+
sorted_filters = filters.sort { |a,b| a[1] <=> b[1] }
|
202
|
+
|
203
|
+
sorted_filters.each do |f|
|
204
|
+
break if @cancel_execution
|
205
|
+
filter = f[0]
|
206
|
+
begin
|
207
|
+
if (!self.methods.include?(filter)) then
|
208
|
+
http_status "SERVER_ERROR"
|
209
|
+
raise ApplicationException.new("Filter not found in chain #{debug_filter_names}: #{filter}")
|
210
|
+
end
|
211
|
+
ret = self.send(filter.intern)
|
212
|
+
@cancel_execution = true if ret == false
|
189
213
|
rescue BreakControllerException => bc
|
190
214
|
handle_exception(bc)
|
191
215
|
end
|
@@ -204,6 +228,10 @@ class Controller < Widget
|
|
204
228
|
public
|
205
229
|
def run_after_filters
|
206
230
|
run_filters(:def_after_filter, 'After Filters')
|
231
|
+
ce = @cancel_execution
|
232
|
+
@cancel_execution = false
|
233
|
+
run_filters(:def_priority_after_filter, 'Priority After Filters')
|
234
|
+
@cancel_execution = ce
|
207
235
|
end
|
208
236
|
|
209
237
|
|
@@ -235,13 +263,24 @@ class Controller < Widget
|
|
235
263
|
|
236
264
|
if e.flags.has_key?(:error) then
|
237
265
|
@_layout = false
|
266
|
+
http_status "SERVER_ERROR"
|
238
267
|
clear
|
239
268
|
@error_message = e.flags[:error]
|
240
269
|
@cancel_execution = true
|
241
|
-
|
270
|
+
trace = ''
|
271
|
+
if Cuca::App.config['display_errors'] then
|
272
|
+
e.backtrace.each do |b|
|
273
|
+
trace<< "<br/>#{b}"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
mab { html { body { h2 "Error"; text @error_message; br; text trace }}}
|
242
277
|
end
|
243
278
|
end
|
244
279
|
|
280
|
+
def action_name
|
281
|
+
$app.urlmap.action
|
282
|
+
end
|
283
|
+
|
245
284
|
private
|
246
285
|
# Overwrite this method to handle get and post events
|
247
286
|
def run
|
@@ -261,7 +300,7 @@ class Controller < Widget
|
|
261
300
|
# exceptions. what within [post,get,run]
|
262
301
|
# TOTHINKABOUT: run before filter from this method?
|
263
302
|
public
|
264
|
-
def _do(what, subcall)
|
303
|
+
def _do(what, subcall = nil)
|
265
304
|
return if @cancel_execution
|
266
305
|
|
267
306
|
method_name = what
|
@@ -270,6 +309,8 @@ class Controller < Widget
|
|
270
309
|
self.send(method_name) if self.methods.include?(method_name)
|
271
310
|
rescue BreakControllerException => e
|
272
311
|
handle_exception(e)
|
312
|
+
rescue ApplicationException => e
|
313
|
+
http_status "SERVER_ERROR"
|
273
314
|
end
|
274
315
|
end
|
275
316
|
|
@@ -301,6 +342,7 @@ class Controller < Widget
|
|
301
342
|
layout_class = Object::const_get(lname)
|
302
343
|
rescue => e
|
303
344
|
# fixme - if layout loading fails we should display some error
|
345
|
+
raise ApplicationException.new("Could not load layout: #{lname}: #{e}")
|
304
346
|
return nil
|
305
347
|
end
|
306
348
|
end
|
@@ -318,4 +360,3 @@ end
|
|
318
360
|
|
319
361
|
|
320
362
|
end # Module
|
321
|
-
|
data/lib/cuca/layout.rb
CHANGED
@@ -47,6 +47,7 @@ class Layout < Widget
|
|
47
47
|
# the controller will create the layout. The controller will also set the content_for_layout
|
48
48
|
# assign besides other assigns from the controller.
|
49
49
|
def initialize(params = {}, &block)
|
50
|
+
raise ArgumentError.new("Layout requires :assigns for layout content") if params[:assigns].nil?
|
50
51
|
params[:assigns].each_pair do |k,v|
|
51
52
|
instance_variable_set("@#{k.to_s}", v)
|
52
53
|
end
|
data/lib/cuca/session.rb
CHANGED
@@ -71,6 +71,7 @@ module Cuca
|
|
71
71
|
class Session
|
72
72
|
attr_reader :flash
|
73
73
|
attr_reader :page
|
74
|
+
attr_reader :cgi
|
74
75
|
|
75
76
|
private
|
76
77
|
def make_session
|
@@ -90,7 +91,7 @@ class Session
|
|
90
91
|
# returns true/false if a session exists
|
91
92
|
def exists?
|
92
93
|
begin
|
93
|
-
p = @
|
94
|
+
p = @session_parameters.clone
|
94
95
|
p['new_session'] = false
|
95
96
|
session = CGI::Session.new(cgi, p)
|
96
97
|
rescue ArgumentError
|
@@ -106,11 +107,13 @@ class Session
|
|
106
107
|
'database_manager' => CGI::Session::PStore,
|
107
108
|
'session_key' => App::config["session_key"],
|
108
109
|
'session_path' => '/',
|
110
|
+
# 'new_session' => false,
|
109
111
|
'session_expires' => Time.now + App::config["session_valid"].to_i,
|
110
112
|
'prefix' => App::config["session_prefix"] }
|
111
113
|
|
112
114
|
make_session
|
113
115
|
|
116
|
+
|
114
117
|
@flash = SessionFlash.new(self)
|
115
118
|
@page = SessionPage.new(self)
|
116
119
|
end
|
@@ -143,14 +146,15 @@ class Controller
|
|
143
146
|
# close it afterwards.
|
144
147
|
def self.use_session
|
145
148
|
priority_before_filter('ses_initialize_session')
|
146
|
-
|
149
|
+
priority_after_filter('ses_close_session')
|
147
150
|
end
|
148
151
|
|
149
152
|
def ses_initialize_session
|
150
|
-
$session = Session.new($cgi)
|
153
|
+
$session = Session.new($app.cgi)
|
151
154
|
end
|
152
155
|
def ses_close_session
|
153
156
|
$session.close
|
157
|
+
# $session = nil
|
154
158
|
end
|
155
159
|
end
|
156
160
|
|
data/lib/cuca/sessionpage.rb
CHANGED
data/lib/cuca/stdlib/arform.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'cuca/stdlib/form'
|
2
|
+
require 'cuca/stdlib/formerrors'
|
2
3
|
require 'cuca/generator/markaby'
|
3
4
|
|
4
5
|
# == Form's for ActiveRecord
|
@@ -42,13 +43,22 @@ class ARFormWidget < FormWidget
|
|
42
43
|
@form_erros = {}
|
43
44
|
p = request_parameters.dup
|
44
45
|
p.delete(@submit_name)
|
46
|
+
|
47
|
+
if @model.new_record? then
|
48
|
+
@disabled_on_create.each { |d| p.delete(d) }
|
49
|
+
@hidden_on_create.each { |d| p.delete(d) }
|
50
|
+
else
|
51
|
+
@disabled_on_update.each { |d| p.delete(d) }
|
52
|
+
@hidden_on_update.each { |d| p.delete(d) }
|
53
|
+
end
|
54
|
+
|
45
55
|
|
46
56
|
# don't save empty passwords!!
|
47
57
|
@password_fields ||= []
|
48
58
|
@password_fields.each do |pwf|
|
49
59
|
p.delete(pwf) if p[pwf].chomp.empty?
|
50
60
|
end
|
51
|
-
|
61
|
+
$stderr.puts p.inspect
|
52
62
|
@model.attributes = p
|
53
63
|
|
54
64
|
return true if @model.valid?
|
@@ -187,6 +197,7 @@ end
|
|
187
197
|
r << "<form action='#{@post_to}' method='POST'>\n"
|
188
198
|
r << "<table>"
|
189
199
|
@model.class.columns.each do |col|
|
200
|
+
next if field_hidden?(col.name)
|
190
201
|
k = col.name
|
191
202
|
v = @model.send(k.intern) # this allows us to overwrite accessors
|
192
203
|
r << "<tr><td>#{k}</td><td>#{fe(col.type,k,v)}</td></tr>"
|
@@ -78,9 +78,10 @@ class DBListWidget < BaseList
|
|
78
78
|
ret
|
79
79
|
end
|
80
80
|
findstuff[:select] = sel.compact.join(',')
|
81
|
-
|
81
|
+
$stderr.puts "Find-Stuff: #{findstuff.inspect}"
|
82
82
|
@data = @model_class.find(:all, findstuff)
|
83
83
|
@data = normalize_result(@data)
|
84
|
+
|
84
85
|
@total_rows= @model_class.count(:conditions => where_clause(query_def), :joins => @joins)
|
85
86
|
|
86
87
|
# $stderr.puts "Query: #{@data.inspect} - #{query_def.order_by.inspect}"
|
@@ -154,6 +154,7 @@ end
|
|
154
154
|
%>
|
155
155
|
</td>
|
156
156
|
<% end %>
|
157
|
+
<td></td>
|
157
158
|
|
158
159
|
<form name="<%=@list_name%>_form" method="POST">
|
159
160
|
<tr>
|
@@ -163,11 +164,11 @@ end
|
|
163
164
|
<% if c[:searchable] != false then %>
|
164
165
|
<input style='float:left;' type="text" name="<%=ftag%>" value="<%=@query_def.filters[c[:id]]%>" >
|
165
166
|
<% end %>
|
166
|
-
<% if (@columns.last[:id] == c[:id]) then %>
|
167
|
-
<input style='float:right;' type="submit" value="ok">
|
168
|
-
<% end %>
|
169
167
|
</td>
|
170
|
-
<% end %>
|
168
|
+
<% end %>
|
169
|
+
|
170
|
+
<td><input style='float:right;' type="submit" value="filter"></td>
|
171
|
+
|
171
172
|
</tr>
|
172
173
|
</form>
|
173
174
|
|
data/lib/cuca/test/helpers.rb
CHANGED
@@ -11,10 +11,12 @@ module Helpers
|
|
11
11
|
# init the application. call this from the setup method.
|
12
12
|
def init(app_path = '/', params = {})
|
13
13
|
require 'cuca/cgi_emu'
|
14
|
-
@
|
14
|
+
@cgi = CGIEmu.new({'PATH_INFO' => app_path, 'QUERY_PARAMS' => params})
|
15
|
+
@app = Cuca::App.new(@cgi)
|
15
16
|
@app.load_support_files
|
16
17
|
end
|
17
18
|
|
19
|
+
|
18
20
|
# this will create a widget instance with params and block
|
19
21
|
# as passed to this function. Also it will pass current instance
|
20
22
|
# variables to the assigns.
|
@@ -0,0 +1,324 @@
|
|
1
|
+
|
2
|
+
if __FILE__ == $0 then
|
3
|
+
require 'rubygems'
|
4
|
+
require 'cuca'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Cuca
|
8
|
+
|
9
|
+
# URLMap will throw this in case we can't find a controller
|
10
|
+
# file for a URL.
|
11
|
+
class RoutingError < StandardError # :nodoc:
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# == URLMap
|
16
|
+
#
|
17
|
+
# URLMap is used internally to match a URL to a controller file.
|
18
|
+
# Call with ds = URLMap.new('/path/to/app', 'path/from/url')
|
19
|
+
# URLMap.new(base_path, url)
|
20
|
+
#
|
21
|
+
# You can then fetch the following values:
|
22
|
+
#
|
23
|
+
# * script - path to the controller file
|
24
|
+
# * url - unmodified URL as passed to initializer
|
25
|
+
# * assigns - hash with variable assigns from the url (magick prefixes)
|
26
|
+
# * subcall - name of subcall or nil if a normal call was made
|
27
|
+
# * action - Action name (Note: action.capitalize+"Controller" is your controller class name)
|
28
|
+
# * base_url - Base URL to the action (e.g.: /user/someone/show is -> /user/someone/)
|
29
|
+
# * base_path - Unmodified base_path
|
30
|
+
# * action_path - Path to the action script
|
31
|
+
# * action_path_full - Full path to action script
|
32
|
+
# * path_tree - an array of each directory on the way from /path/to/app to the script
|
33
|
+
# (to look for include_directories in - see Cuca::Config)
|
34
|
+
# * action_module - The module the action should be loaded into (to avoid name conflicts, depends on action_path)
|
35
|
+
#
|
36
|
+
#
|
37
|
+
# == Match on other URL
|
38
|
+
#
|
39
|
+
# A widget/controller can make use of the URLMap object to scan on other directories (example to find out if a
|
40
|
+
# link url will be withing the same controller).
|
41
|
+
#
|
42
|
+
# See match? / submatch? and usubmatch?
|
43
|
+
#
|
44
|
+
#
|
45
|
+
# == Notes
|
46
|
+
#
|
47
|
+
# URL's ending with '/' will be scanned for default index files.
|
48
|
+
#
|
49
|
+
# URL's where last part (action) starts with '-' will be scanned for
|
50
|
+
# subcalls
|
51
|
+
#
|
52
|
+
# If no script is found or any other error it will raise a RoutingError exception
|
53
|
+
#
|
54
|
+
#
|
55
|
+
# == Example
|
56
|
+
#
|
57
|
+
# u = URLMap.new('/home/bones/src/cuca_app/app', 'customer/southwind_lda/show'
|
58
|
+
#
|
59
|
+
# u.script => '/home/bones/src/cuca_app/app/customer/__customer/show.rb'
|
60
|
+
# u.action => 'show'
|
61
|
+
# u.base_url => '/customer/__customer/'
|
62
|
+
# u.assigns => { 'customer' => 'southwind_lda' }
|
63
|
+
# u.action_path => 'customer/southwind_lda/'
|
64
|
+
#
|
65
|
+
class URLMap
|
66
|
+
attr_reader :url # current url
|
67
|
+
attr_reader :assigns
|
68
|
+
attr_reader :script
|
69
|
+
attr_reader :subcall
|
70
|
+
attr_reader :base_url
|
71
|
+
attr_reader :base_path
|
72
|
+
attr_reader :action
|
73
|
+
attr_reader :action_path
|
74
|
+
attr_reader :action_path_full
|
75
|
+
attr_reader :action_module
|
76
|
+
attr_reader :path_tree
|
77
|
+
|
78
|
+
DEF_ACT = Cuca::App::config['magic_action_prefix'] || '__'
|
79
|
+
DEF_IDX = [ 'index', 'default' ]
|
80
|
+
|
81
|
+
private
|
82
|
+
def scan_file(base, file)
|
83
|
+
|
84
|
+
if (file == '') then # check for default index file
|
85
|
+
DEF_IDX.each do |idxfile|
|
86
|
+
if File.exist?("#{base}/#{idxfile}.rb")
|
87
|
+
@action = idxfile
|
88
|
+
return "#{idxfile}.rb"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
raise RoutingError.new("No default index file found in #{base}")
|
92
|
+
end
|
93
|
+
|
94
|
+
@action = file
|
95
|
+
|
96
|
+
# check if a regular file exists:
|
97
|
+
# puts "Checking file on #{check}"
|
98
|
+
return (file+".rb") if File.exist?("#{base}/#{file}.rb")
|
99
|
+
|
100
|
+
# check if the subcall file exists:
|
101
|
+
if (file[0].chr == '-') then
|
102
|
+
(action,subcall) = file.scan(/^\-(.*)\-(.*)$/).flatten
|
103
|
+
if action.nil? || subcall.nil? || action.strip.empty? then
|
104
|
+
raise RoutingError.new("Bad format on subcall: #{file}")
|
105
|
+
end
|
106
|
+
raise RoutingError.new("Script not found for subcall: #{file}: #{action}.rb") if !File.exist?("#{base}/#{action}.rb")
|
107
|
+
@subcall = subcall
|
108
|
+
@action = action
|
109
|
+
return "#{action}.rb"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# scan_dir will look within a realdirectory for an unparsed url 'file' and return
|
115
|
+
# it's real path
|
116
|
+
private
|
117
|
+
def scan_dir(base, file)
|
118
|
+
|
119
|
+
striped = "#{base}/#{file}"[@base_path.length..-1]
|
120
|
+
mount = Cuca::App.config['mount'] || {}
|
121
|
+
$stderr.puts "SCAN DIR: #{striped}"
|
122
|
+
$stderr.puts "MOUNTS: #{mount.inspect}"
|
123
|
+
|
124
|
+
|
125
|
+
if mount["#{striped}/"] then
|
126
|
+
$stderr.puts "Found mount point, returning: #{mount["#{striped}/"]}"
|
127
|
+
return mount["#{striped}/"]
|
128
|
+
end
|
129
|
+
|
130
|
+
if File.directory?("#{base}/#{file}") then
|
131
|
+
return file.empty? ? base : "#{base}/#{file}" # avoid returning double //
|
132
|
+
end
|
133
|
+
|
134
|
+
d = Dir["#{base}/#{DEF_ACT}*"].collect { |f| f.split('/').last }
|
135
|
+
|
136
|
+
# puts "Directory not found, checking for default in #{base} - #{file}"
|
137
|
+
|
138
|
+
# puts d.inspect
|
139
|
+
#
|
140
|
+
|
141
|
+
raise RoutingError.new("Multiple default actions defined in #{base}") if d.size > 1
|
142
|
+
raise RoutingError.new("Routing Error in #{base}") if d.empty?
|
143
|
+
|
144
|
+
|
145
|
+
@assigns[d[0][DEF_ACT.size..-1]] = file
|
146
|
+
"#{base}/#{d[0]}"
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
private
|
151
|
+
def make_module(path)
|
152
|
+
const_name = "Appmod_#{path.gsub(/[\/\\]/, '_')}"
|
153
|
+
|
154
|
+
if Cuca::Objects::const_defined?(const_name.intern) then
|
155
|
+
return Cuca::Objects::const_get(const_name.intern)
|
156
|
+
end
|
157
|
+
|
158
|
+
m = Module.new
|
159
|
+
Cuca::Objects::const_set(const_name.intern, m)
|
160
|
+
return m
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# scan will match an URI to a script and set assigns. (called from initialize)
|
165
|
+
private
|
166
|
+
def scan
|
167
|
+
files = @path_info.split('/')
|
168
|
+
|
169
|
+
files << '' if @path_info[@path_info.size-1].chr == '/' # add empty element if we point to a directory
|
170
|
+
|
171
|
+
# files now contains something like:
|
172
|
+
# [users, show, martin, contacts]
|
173
|
+
|
174
|
+
# puts files.inspect
|
175
|
+
real_path = @base_path.dup
|
176
|
+
|
177
|
+
# scan directory
|
178
|
+
files.each_index do |idx|
|
179
|
+
next if idx >= (files.size-1) # skip last element
|
180
|
+
r = scan_dir(real_path, files[idx])
|
181
|
+
raise RoutingError.new("Routing Error at #{real_path} - #{files[idx]}") if !r
|
182
|
+
@path_tree << r
|
183
|
+
real_path = r
|
184
|
+
end
|
185
|
+
|
186
|
+
@url = @path_info
|
187
|
+
@base_url = "#{files[0..-2].join('/')}/"
|
188
|
+
@action_path = real_path[@base_path.length..-1]
|
189
|
+
@action_path_full = real_path
|
190
|
+
@action_module = make_module(@action_path)
|
191
|
+
|
192
|
+
# scan file (last element)
|
193
|
+
r = scan_file(real_path, files.last)
|
194
|
+
|
195
|
+
raise RoutingError.new("Routing Error - script not found at #{real_path} - #{files.last}") if !r
|
196
|
+
|
197
|
+
real_path = "#{real_path}/#{r}"
|
198
|
+
|
199
|
+
@script = File.expand_path(real_path)
|
200
|
+
# @path_tree = _tree(@base_path, @script)
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
# match will check if the supplied url maches with a script
|
206
|
+
# returns boolean
|
207
|
+
#
|
208
|
+
# Example:
|
209
|
+
# URLMap('/path/to/app', '/customer/southwind_lda/').match?('/path/to/app/customer/__custid/index.rb') => true
|
210
|
+
public
|
211
|
+
def match?(script)
|
212
|
+
m_script = @script
|
213
|
+
p_script = File.expand_path(script)
|
214
|
+
|
215
|
+
# $stderr.puts "URLMap::match - #{m_script} - #{p_script}"
|
216
|
+
return (m_script == p_script)
|
217
|
+
rescue RoutingError
|
218
|
+
false
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
# this will match if the current script can be found within a path
|
223
|
+
# from the parameters.
|
224
|
+
#
|
225
|
+
# Example:
|
226
|
+
# URLMap('/path/to/app', '/customer/southwind_lda/').submatch?('/customer/__custid/') => true
|
227
|
+
public
|
228
|
+
def submatch?(some_path)
|
229
|
+
# $stderr.puts "Submatch: #{some_path} with #{@script} - #{(@script.length < some_path.length).inspect} #{@script.include?(some_path)}"
|
230
|
+
return false if @script.length < some_path.length
|
231
|
+
return @script.include?(some_path)
|
232
|
+
end
|
233
|
+
|
234
|
+
# this will match the current script to a part of a url (link):
|
235
|
+
#
|
236
|
+
# Example:
|
237
|
+
# URLMap('/path/to/app', '/customer/southwind_lda/').submatch?('/customer/other_customer/') => true
|
238
|
+
public
|
239
|
+
def usubmatch?(some_path)
|
240
|
+
@path_info.include?(some_path)
|
241
|
+
end
|
242
|
+
|
243
|
+
# FIXME: needed?
|
244
|
+
public
|
245
|
+
def has_script?(script)
|
246
|
+
return !(@script == '')
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
def initialize(base_path, path_info, default_actions = ['index'])
|
251
|
+
@path_info = path_info
|
252
|
+
@base_path = File.expand_path(base_path)
|
253
|
+
@script = ''
|
254
|
+
@subcall = nil
|
255
|
+
@default_actions = default_actions
|
256
|
+
@assigns = {}
|
257
|
+
@action = ''
|
258
|
+
@action_path = ''
|
259
|
+
@path_tree = [base_path]
|
260
|
+
scan
|
261
|
+
self
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
#
|
270
|
+
# Testings:
|
271
|
+
#
|
272
|
+
|
273
|
+
if __FILE__ == $0 then
|
274
|
+
require 'app'
|
275
|
+
|
276
|
+
BASE = '/home/bones/src/cuca/app'
|
277
|
+
URL = 'user/martin/somewhere/notexist/'
|
278
|
+
|
279
|
+
puts "Testing on '#{BASE}' - '#{URL}'"
|
280
|
+
|
281
|
+
module Cuca
|
282
|
+
ds = URLMap.new(BASE, URL)
|
283
|
+
begin
|
284
|
+
rescue RoutingError => e
|
285
|
+
puts "E: Invalid request #{$!}"
|
286
|
+
end
|
287
|
+
|
288
|
+
|
289
|
+
|
290
|
+
puts "Match with: #{ds.match?('/home/bones/src/cuca/app/user/__default_username/index.rb')}"
|
291
|
+
puts "Submatch with /user/__username/ #{ds.submatch?('/user/__username/')}"
|
292
|
+
puts "Submatch with '/user/' #{ds.submatch?('/user/')}"
|
293
|
+
puts "USubmatch with '/user/' #{ds.usubmatch?('/user/martin')}"
|
294
|
+
puts
|
295
|
+
puts "Script is: #{ds.script}"
|
296
|
+
puts "Assigns are: #{ds.assigns.inspect}"
|
297
|
+
puts "Subcall: #{ds.subcall.inspect}"
|
298
|
+
puts "Action: #{ds.action}"
|
299
|
+
puts "Action Path: #{ds.action_path}"
|
300
|
+
puts "Action Path Full: #{ds.action_path_full}"
|
301
|
+
puts "Action Module: #{ds.action_module.inspect}"
|
302
|
+
puts "Path tree: #{ds.path_tree.inspect}"
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
|
309
|
+
|
310
|
+
|
311
|
+
|
312
|
+
|
313
|
+
# URL: "/user/martin/show"
|
314
|
+
# DIR: "/user/__userid/show'
|
315
|
+
# MOUNT: "/user/__userid/" =>>> "/plugin/user" contains 'show'
|
316
|
+
# MOUNT: "/user/__userid/" =>>> "/plugin/user2" contains 'see'
|
317
|
+
|
318
|
+
|
319
|
+
|
320
|
+
|
321
|
+
|
322
|
+
|
323
|
+
|
324
|
+
|