cuca 0.01
Sign up to get free protection for your applications and to get access to all the features.
- data/application_skeleton/README +21 -0
- data/application_skeleton/app/_controllers/application.rb +7 -0
- data/application_skeleton/app/_layouts/simple.rb +19 -0
- data/application_skeleton/app/_widgets/sourcecode.rb +21 -0
- data/application_skeleton/app/_widgets/test.rb +23 -0
- data/application_skeleton/app/demo.rb +64 -0
- data/application_skeleton/app/index.rb +39 -0
- data/application_skeleton/app/user/__default_username/index.rb +7 -0
- data/application_skeleton/conf/environment.rb +16 -0
- data/application_skeleton/log/access.log +1 -0
- data/application_skeleton/log/error.log +1 -0
- data/application_skeleton/log/messages +1 -0
- data/application_skeleton/public/css/style.css +27 -0
- data/application_skeleton/public/dispatch.cgi +31 -0
- data/application_skeleton/public/dispatch.fcgi +36 -0
- data/application_skeleton/public/img/cuca-seagull.png +0 -0
- data/application_skeleton/scripts/console +5 -0
- data/application_skeleton/scripts/console.rb +5 -0
- data/application_skeleton/scripts/server-lighttpd-fcgi.rb +116 -0
- data/application_skeleton/scripts/server-lighttpd.rb +109 -0
- data/application_skeleton/scripts/server-webrick.rb +26 -0
- data/application_skeleton/scripts/test.rb +8 -0
- data/application_skeleton/tests/widgets/link.rb +22 -0
- data/bin/cuca +43 -0
- data/lib/cuca/app.rb +317 -0
- data/lib/cuca/cgi_emu.rb +67 -0
- data/lib/cuca/cgi_fix.rb +58 -0
- data/lib/cuca/const.rb +3 -0
- data/lib/cuca/controller.rb +240 -0
- data/lib/cuca/generator/markaby.rb +80 -0
- data/lib/cuca/generator/view.rb +121 -0
- data/lib/cuca/layout.rb +62 -0
- data/lib/cuca/mimetypes.rb +89 -0
- data/lib/cuca/session.rb +143 -0
- data/lib/cuca/sessionflash.rb +56 -0
- data/lib/cuca/sessionpage.rb +41 -0
- data/lib/cuca/stdlib/arform.rb +208 -0
- data/lib/cuca/stdlib/arview.rb +16 -0
- data/lib/cuca/stdlib/form.rb +137 -0
- data/lib/cuca/stdlib/formerrors.rb +20 -0
- data/lib/cuca/stdlib/link.rb +37 -0
- data/lib/cuca/stdlib/list.rb +3 -0
- data/lib/cuca/stdlib/listwidget/dblist.rb +122 -0
- data/lib/cuca/stdlib/listwidget/list.rb +189 -0
- data/lib/cuca/stdlib/listwidget/querydef.rb +167 -0
- data/lib/cuca/stdlib/listwidget/staticdatalist.rb +79 -0
- data/lib/cuca/stdlib/slink.rb +30 -0
- data/lib/cuca/test/helpers.rb +42 -0
- data/lib/cuca/urlmap.rb +267 -0
- data/lib/cuca/widget.rb +212 -0
- data/lib/cuca.rb +68 -0
- metadata +141 -0
data/lib/cuca/app.rb
ADDED
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
|
4
|
+
module Cuca
|
5
|
+
|
6
|
+
# Sandbox is used internally run the action script defined by the controller
|
7
|
+
# In future this can be extended to implement some security features etc..
|
8
|
+
class Sandbox
|
9
|
+
def self.run(controller_class_name, mod, assigns, request_method, subcall)
|
10
|
+
self.class.send(:include, mod)
|
11
|
+
controller_class = mod::const_get(controller_class_name)
|
12
|
+
controller = controller_class.send(:new, :assigns=>assigns)
|
13
|
+
controller.run_before_filters
|
14
|
+
|
15
|
+
|
16
|
+
controller.send('_do'.intern, 'run', subcall)
|
17
|
+
case $cgi.request_method
|
18
|
+
when 'POST'
|
19
|
+
controller.send('_do'.intern, 'post', subcall)
|
20
|
+
when 'GET'
|
21
|
+
controller.send('_do'.intern, 'get', subcall)
|
22
|
+
end
|
23
|
+
return controller.send(:to_s)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# == Cuca Application
|
28
|
+
#
|
29
|
+
# A Cuca::App object will be created directly by the dispatcher - which again is the
|
30
|
+
# direct cgi or fastcgi script that get run by the webserver.
|
31
|
+
# Normally you just create a Cuca::App object and run the cgicall function with optional with
|
32
|
+
# a cgi object as paramenter.
|
33
|
+
# Before doing that you must set $cuca_path to the root of your appication structure.
|
34
|
+
|
35
|
+
class App
|
36
|
+
|
37
|
+
# == Configure the application
|
38
|
+
# App::Config is normally called from conf/environment.rb . The attributes below the framework
|
39
|
+
# will make use of, but you can always define own ones.
|
40
|
+
#
|
41
|
+
# == Example
|
42
|
+
#
|
43
|
+
# Cuca::App.configure do |conf|
|
44
|
+
# conf.include_directories = %{_widgets _special_widgets}
|
45
|
+
# conf.database = 'mydb' # application specific
|
46
|
+
# end
|
47
|
+
# === Attributes:
|
48
|
+
#
|
49
|
+
# * include_directories
|
50
|
+
# An array of directories that will be automatically included if you call an action.
|
51
|
+
# If one of the directories is found in the path where the action file is located it will require
|
52
|
+
# all files found. (default: %w{_controllers _widgets _layouts} )
|
53
|
+
# * log_level - default 3
|
54
|
+
# * magic_prefix - default: '__default_' (see Cuca::Controller)
|
55
|
+
class Config < Hash
|
56
|
+
def method_missing(m, *params)
|
57
|
+
met = m.id2name
|
58
|
+
raise NoMethodError if met[met.size-1].chr != '='
|
59
|
+
self[met[0..met.size-2]] = params[0]
|
60
|
+
end
|
61
|
+
|
62
|
+
# some default stuff
|
63
|
+
def initialize
|
64
|
+
self['include_directories'] = %w{_controllers _widgets _layouts}
|
65
|
+
self['log_level'] = 3
|
66
|
+
self['magic_prefix'] = '__default_'
|
67
|
+
self['directory_seperator'] = '/' # future?
|
68
|
+
self['session_key'] = 'cuca_session'
|
69
|
+
self['session_prefix'] = 'cuca.'
|
70
|
+
self['session_valid'] = 3600*24
|
71
|
+
self['view_directoru'] = 'app/_view' # where to find views for the view/erb generator
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_reader :conf, :cgi, :logger, :urlmap
|
78
|
+
|
79
|
+
@@app_config = Config.new
|
80
|
+
|
81
|
+
## Application configuration
|
82
|
+
public
|
83
|
+
def self.configure
|
84
|
+
yield @@app_config
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.config
|
88
|
+
@@app_config
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# cgi implementation test
|
93
|
+
private
|
94
|
+
def cgidump(cgi)
|
95
|
+
s = ""
|
96
|
+
s << "-----ENV------<br>"
|
97
|
+
cgi.env_table.each_pair { |k,v| s << "#{k} => #{v}<br>" }
|
98
|
+
s << "--------------<br>"
|
99
|
+
s << "Server Software: #{cgi.server_software}<br>"
|
100
|
+
s << "PATH INFO: #{cgi.path_info}<br>"
|
101
|
+
end
|
102
|
+
|
103
|
+
# Creates the @conf global variable - internal config
|
104
|
+
#
|
105
|
+
# APP_PATH = Path the the /app folder
|
106
|
+
# CGI = CGI object
|
107
|
+
# DEF_EXT = Default script extensions (.rb)
|
108
|
+
# DEF_ACTION = (like 'index')
|
109
|
+
# SCRIPT = Full path to the ruby script dealing with ACTION
|
110
|
+
# ACTION = Action called (=links to SCRIPT, eg 'show' or 'index')
|
111
|
+
# PATH_INFO = raw path info as from cgi
|
112
|
+
# PATH_TREE = array of path, without action
|
113
|
+
private
|
114
|
+
def mk_config
|
115
|
+
@conf = {}
|
116
|
+
|
117
|
+
# To have a look at ENV and cgi uncomment this
|
118
|
+
# @cgi.out { cgidump(@cgi) }
|
119
|
+
# raise "Debug stop"
|
120
|
+
|
121
|
+
|
122
|
+
# APP_PATH = Path the the /app folder
|
123
|
+
# @conf['APP_PATH'] = File.expand_path(File.dirname(__FILE__)+"/../app")
|
124
|
+
@conf['APP_PATH'] = $cuca_path + "/app"
|
125
|
+
|
126
|
+
# PUBLIC_PATH = Path to the /public folder
|
127
|
+
# @conf['PUBLIC_PATH'] = File.expand_path(File.dirname(__FILE__)+"/../public")
|
128
|
+
@conf['PUBLIC_PATH'] = $cuca_path + "/public"
|
129
|
+
|
130
|
+
# LOG_PATH = Path to logging directory
|
131
|
+
# @conf['LOG_PATH'] = File.expand_path(File.dirname(__FILE__)+"/../log")
|
132
|
+
@conf['LOG_PATH'] = $cuca_path + "/log"
|
133
|
+
|
134
|
+
# CGI = CGI object
|
135
|
+
@conf['CGI'] = @cgi
|
136
|
+
|
137
|
+
# DEF_EXT = Default script extensions
|
138
|
+
@conf['DEF_EXT'] = ".rb"
|
139
|
+
|
140
|
+
@conf['DEF_ACTION'] = 'index'
|
141
|
+
|
142
|
+
# Cookie name
|
143
|
+
@conf['COOKIE_NAME'] = 'cucahap'
|
144
|
+
|
145
|
+
# ACTION = Action called (=links to SCRIPT), eg 'index'
|
146
|
+
@conf['ACTION'] = @cgi.path_info.reverse[0].chr == '/' ? @conf['DEF_ACTION'] : @cgi.path_info.split('/').last.downcase
|
147
|
+
|
148
|
+
# PATH_INFO = raw path info as from cgi
|
149
|
+
@conf['PATH_INFO'] = @cgi.path_info
|
150
|
+
|
151
|
+
# PATH_TREE = path info as array - without action
|
152
|
+
# @conf['PATH_TREE'] = [''].concat(@conf['PATH_INFO'].split('/').collect { |e| (e==@conf['ACTION'] || e == '') ? nil : e }.compact)
|
153
|
+
|
154
|
+
|
155
|
+
require 'cuca/urlmap'
|
156
|
+
|
157
|
+
begin
|
158
|
+
mapping = URLMap.new(File.expand_path(@conf['APP_PATH']), @conf['PATH_INFO'])
|
159
|
+
@conf['SUBCALL'] = mapping.subcall
|
160
|
+
@conf['SCRIPT'] = mapping.script
|
161
|
+
@conf['ASSIGNS'] = mapping.assigns
|
162
|
+
@conf['ACTION'] = mapping.action
|
163
|
+
@urlmap = mapping
|
164
|
+
rescue RoutingError => r
|
165
|
+
@conf['SCRIPT'] = "#{@conf['PUBLIC_PATH']}/#{@conf['PATH_INFO']}"
|
166
|
+
return
|
167
|
+
# raise "RoutingError: #{r}"
|
168
|
+
end
|
169
|
+
|
170
|
+
# get the PATH_TREE for file inclusion!
|
171
|
+
|
172
|
+
c_ap = File.expand_path(@conf['APP_PATH'])
|
173
|
+
pt = @conf['SCRIPT'][c_ap.size..-1]
|
174
|
+
ac_script = @conf['SCRIPT'].split('/').last
|
175
|
+
@conf['PATH_TREE'] = [''].concat(pt.split('/').collect { |e| (e==ac_script || e == '') ? nil : e }.compact)
|
176
|
+
end
|
177
|
+
|
178
|
+
# will do a 'require' on all .rb files in path
|
179
|
+
private
|
180
|
+
def include_files(path)
|
181
|
+
return unless File.exist?(path)
|
182
|
+
pwd = Dir.pwd
|
183
|
+
Dir.chdir(path)
|
184
|
+
Dir['*.rb'].each do |f|
|
185
|
+
require "#{path}/#{f}"
|
186
|
+
end
|
187
|
+
Dir.chdir(pwd)
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
public
|
193
|
+
def initialize(cgi = nil)
|
194
|
+
|
195
|
+
$cgi = cgi || CGI.new
|
196
|
+
@cgi = $cgi
|
197
|
+
|
198
|
+
mk_config
|
199
|
+
|
200
|
+
@logger = Logger.new(@conf['LOG_PATH'] + "/messages")
|
201
|
+
$logger = @logger
|
202
|
+
$conf = @conf
|
203
|
+
$app = self
|
204
|
+
|
205
|
+
rescue RuntimeError => e
|
206
|
+
@cgi.out { "Error initializing the app: #{$!}" }
|
207
|
+
end
|
208
|
+
|
209
|
+
# this has to be public for testing scripts
|
210
|
+
public
|
211
|
+
def load_support_files # :nodoc:
|
212
|
+
# $stderr.puts "======== load support files ========="
|
213
|
+
# $stderr.puts "Inc Dir: #{App::config['include_directories'].inspect}"
|
214
|
+
# $stderr.puts "Path tr: #{@conf['PATH_TREE'].inspect}"
|
215
|
+
# $stderr.puts "====================================="
|
216
|
+
|
217
|
+
base_path = @conf['APP_PATH']
|
218
|
+
@conf['PATH_TREE'].each do |t|
|
219
|
+
base_path << "/#{t}"
|
220
|
+
(App::config['include_directories'] || []).each do |id|
|
221
|
+
include_files "#{base_path}/#{id}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# (App::config['include_directories'] || []).each do |id|
|
226
|
+
# @conf['PATH_TREE'].each { |t| include_files @conf['APP_PATH'] + '/'+t+"/#{id}/"}
|
227
|
+
# end
|
228
|
+
end
|
229
|
+
|
230
|
+
public
|
231
|
+
def cgicall
|
232
|
+
script = @conf['SCRIPT']
|
233
|
+
|
234
|
+
|
235
|
+
#
|
236
|
+
# 1st priority: Serve a file if it exists in the 'public' folder
|
237
|
+
#
|
238
|
+
file = @conf['PUBLIC_PATH'] + '/' + @conf['PATH_INFO']
|
239
|
+
if File.exists?(file) && File.ftype(file) == 'file' then
|
240
|
+
require 'cuca/mimetypes'
|
241
|
+
mt = MimeTypes.new
|
242
|
+
f = File.new(file)
|
243
|
+
extension = file.scan(/.*\.(.*)$/)[0][0] if file.include?('.')
|
244
|
+
extension ||= 'html'
|
245
|
+
mime = mt[extension] || 'text/html'
|
246
|
+
@cgi.out(mime) { f.read }
|
247
|
+
f.close
|
248
|
+
return
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
#
|
253
|
+
# 2nd: Check if we have a script for requested action
|
254
|
+
#
|
255
|
+
if (!File.exists?(script)) then
|
256
|
+
@cgi.out { "Script not found: #{script}" }
|
257
|
+
return
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
# 3rd: Load additional files
|
262
|
+
load_support_files
|
263
|
+
|
264
|
+
|
265
|
+
# 4th: Now let's run the actual page script code
|
266
|
+
controller_class_name = @conf['ACTION'].capitalize+"Controller"
|
267
|
+
|
268
|
+
# Clear all hints
|
269
|
+
Widget::clear_hints()
|
270
|
+
|
271
|
+
# Load the code of the action into the module
|
272
|
+
controller_module = @urlmap.action_module
|
273
|
+
|
274
|
+
|
275
|
+
controller_module.module_eval(File.open(script).read, script) unless \
|
276
|
+
controller_module.const_defined?(controller_class_name.intern)
|
277
|
+
|
278
|
+
# Catch a common user error
|
279
|
+
if !controller_module.const_defined?(controller_class_name.intern) then
|
280
|
+
@cgi.out { "Could not find #{controller_class_name} defined in #{script}" }
|
281
|
+
return
|
282
|
+
end
|
283
|
+
|
284
|
+
#
|
285
|
+
# Load the controller
|
286
|
+
#
|
287
|
+
begin
|
288
|
+
|
289
|
+
result = Sandbox.run(controller_class_name, @urlmap.action_module, @conf['ASSIGNS'],
|
290
|
+
$cgi.request_method, @conf['SUBCALL'])
|
291
|
+
|
292
|
+
|
293
|
+
@cgi.out('type' => 'text/html') {result}
|
294
|
+
|
295
|
+
rescue Cuca::ApplicationException => e
|
296
|
+
err = "<b>Application Error: #{e}</b><br/>"
|
297
|
+
e.backtrace.each do |b|
|
298
|
+
err +="<br/>#{b}"
|
299
|
+
end
|
300
|
+
@cgi.out('status' => 'SERVER_ERROR') { err }
|
301
|
+
return
|
302
|
+
|
303
|
+
rescue => e
|
304
|
+
err = "<b>System Error: #{e}</b><br/>"
|
305
|
+
e.backtrace.each do |b|
|
306
|
+
err +="<br/>#{b}"
|
307
|
+
end
|
308
|
+
@cgi.out('status' => 'SERVER_ERROR') { err }
|
309
|
+
return
|
310
|
+
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
end # module
|
data/lib/cuca/cgi_emu.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# little fake cgi class that allows automated testing of cgi application
|
2
|
+
|
3
|
+
class CGIEmu < CGI
|
4
|
+
class EnvTable
|
5
|
+
def initialize(options)
|
6
|
+
@test_path_info = options['PATH_INFO'] || '/'
|
7
|
+
@test_query_params = options['QUERY_PARAMS'] || {}
|
8
|
+
|
9
|
+
the_env_table.each_pair { |k,v| ENV[k] = v }
|
10
|
+
|
11
|
+
# ENV.merge(the_env_table)
|
12
|
+
end
|
13
|
+
|
14
|
+
def query_string
|
15
|
+
r = []
|
16
|
+
@test_query_params.each_pair { |k,v| r << "#{k}=#{v}" }
|
17
|
+
return r.join('&')
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def the_env_table
|
22
|
+
{"SERVER_NAME"=>"localhost",
|
23
|
+
"PATH_INFO"=>@test_path_info,
|
24
|
+
"REMOTE_HOST"=>"localhost",
|
25
|
+
"HTTP_ACCEPT_ENCODING"=>"x-gzip, x-deflate, gzip, deflate",
|
26
|
+
"HTTP_USER_AGENT"=>"Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.8 (like Gecko)",
|
27
|
+
"SCRIPT_NAME"=>"",
|
28
|
+
"SERVER_PROTOCOL"=>"HTTP/1.1",
|
29
|
+
"HTTP_ACCEPT_LANGUAGE"=>"en",
|
30
|
+
"HTTP_HOST"=>"localhost:2000",
|
31
|
+
"REMOTE_ADDR"=>"127.0.0.1",
|
32
|
+
"SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.8.6/2007-06-07)",
|
33
|
+
"HTTP_REFERER"=>"http://localhost:2000/",
|
34
|
+
"HTTP_COOKIE"=>"cuca_session=285715682bf030a19cb24b2560aabc36",
|
35
|
+
"HTTP_ACCEPT_CHARSET"=>"utf-8, utf-8;q=0.5, *;q=0.5",
|
36
|
+
"REQUEST_URI"=>"http://localhost:2000" + @test_path_info,
|
37
|
+
"SERVER_PORT"=>"2000",
|
38
|
+
"GATEWAY_INTERFACE"=>"CGI/1.1",
|
39
|
+
"QUERY_STRING"=>query_string,
|
40
|
+
"HTTP_ACCEPT"=>"text/html, image/jpeg, image/png, text/*, image/*, */*",
|
41
|
+
"SCRIPT_FILENAME"=>"/home/bones/workspace/cuca/scripts/../public/dispatch.cgi",
|
42
|
+
"REQUEST_METHOD"=>"GET",
|
43
|
+
"HTTP_CONNECTION"=>"Keep-Alive"}
|
44
|
+
end
|
45
|
+
|
46
|
+
def [](key)
|
47
|
+
# $stderr.puts "get FROM ENV_TABLE(#{key}) ==> #{the_env_table[key]}"
|
48
|
+
return the_env_table[key] || nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def env_table
|
53
|
+
EnvTable.new(@test_options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def stdinput
|
57
|
+
# $stderr.puts "Returning STDINPUT"
|
58
|
+
StringIO.new("")
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(options)
|
62
|
+
@test_options = options
|
63
|
+
super()
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
data/lib/cuca/cgi_fix.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
# We'll add three methods to the cgi class in order to separete get/post requests:
|
6
|
+
# * parameters -- all (mixed) get and post
|
7
|
+
# * query_parameters -- get
|
8
|
+
# * request_parameters -- post
|
9
|
+
#
|
10
|
+
class CGI # :nodoc:
|
11
|
+
class << self
|
12
|
+
def fix_env(ec)
|
13
|
+
if (ec['PATH_INFO'].nil? || ec['PATH_INFO'] == '') then
|
14
|
+
pi = ec['REQUEST_URI']
|
15
|
+
pi = pi[0..(pi.index('?')-1)] if pi.include?('?')
|
16
|
+
ec['PATH_INFO'] = pi
|
17
|
+
end
|
18
|
+
|
19
|
+
if (ec['QUERY_STRING'].nil? || ec['QUERY_STRING'] == '') then
|
20
|
+
ec['QUERY_STRING'] = ec['REQUEST_URI'].include?('?') ?
|
21
|
+
ec['REQUEST_URI'].scan(/.?\?(.*)/)[0][0] :
|
22
|
+
""
|
23
|
+
end
|
24
|
+
ec
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def env_table
|
30
|
+
CGI::fix_env(ENV)
|
31
|
+
ENV
|
32
|
+
end
|
33
|
+
|
34
|
+
def parameters
|
35
|
+
query_parameters.merge(request_parameters)
|
36
|
+
end
|
37
|
+
|
38
|
+
def query_parameters
|
39
|
+
res = {}
|
40
|
+
query_string.split(/[&;]/).each do |p|
|
41
|
+
k, v = p.split("=")
|
42
|
+
v = '' if v.nil?
|
43
|
+
res[CGI.unescape(k)] = CGI.unescape(v)
|
44
|
+
end
|
45
|
+
res
|
46
|
+
end
|
47
|
+
|
48
|
+
def request_parameters
|
49
|
+
if request_method == 'POST' then
|
50
|
+
res = {}
|
51
|
+
params.each_pair { |k,v| res[k] = v[0] }
|
52
|
+
return res
|
53
|
+
else
|
54
|
+
return {}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/lib/cuca/const.rb
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
module Cuca
|
2
|
+
|
3
|
+
# :nodoc:
|
4
|
+
class BreakControllerException < StandardError
|
5
|
+
# flags can be:
|
6
|
+
# :layout => 'new_layout' or false
|
7
|
+
# :redirect => '/to/new/url' = dont render, just redirect
|
8
|
+
# :error => "something happend" = display an application error
|
9
|
+
attr_reader :flags
|
10
|
+
def initialize(flags = {})
|
11
|
+
@flags = flags
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
# === Cuca Controller
|
17
|
+
#
|
18
|
+
# A controller handles the get/post request for your website and has the ability to
|
19
|
+
# generate an output directly making use of other Widgets and Layouts.
|
20
|
+
#
|
21
|
+
# The Controller class itself behaves just like Cuca::Widget, but instead of overwriting
|
22
|
+
# the output method you must implement run or post or get or any combination.
|
23
|
+
#
|
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 before_filter's.
|
26
|
+
#
|
27
|
+
# == Naming
|
28
|
+
#
|
29
|
+
# When you open a url like: http://your.website.com/users/list cuca will load the file /users/list.rb
|
30
|
+
# from your application path (usually app/users/list.rb). Within that file you must define one
|
31
|
+
# class derrived from Cuca::Controller with name FilenameController. The /users/list.rb example must
|
32
|
+
# therefor implement the ListController class.
|
33
|
+
#
|
34
|
+
# == Subcalls
|
35
|
+
#
|
36
|
+
# Sometimes you might want to implement another URL that is logically joined with your
|
37
|
+
# current page ("Ajax"?). To do this you can implement a (get|post|run)_subcallname method within
|
38
|
+
# your controller. This one will get called if you open http://your.website.com/-controller-subcallname .
|
39
|
+
#
|
40
|
+
# == Routing / Pretty URL's
|
41
|
+
#
|
42
|
+
# Depending on your directory structure within the app folder you can define variable directory names
|
43
|
+
# that will hint you within the controller.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
# File: app/user/__default_username/show.rb will map to http://user/johnrambo/show and define instance variable
|
47
|
+
# @username within the Controller defined in show.rb
|
48
|
+
#
|
49
|
+
# The magic URL prefix (default: __default_) can be change within App::Config
|
50
|
+
#
|
51
|
+
# == Examples
|
52
|
+
#
|
53
|
+
# Hello world (index.rb):
|
54
|
+
#
|
55
|
+
# class IndexController < Cuca::Controller
|
56
|
+
# def run
|
57
|
+
# content << "Hello World"
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# Using filters and layouts and generate page using the Markaby generator (fancy.rb):
|
62
|
+
#
|
63
|
+
# require 'cuca/generators/markaby'
|
64
|
+
#
|
65
|
+
# class FancyController < Cuca::Controller
|
66
|
+
# include Cuca::Generator::Markaby
|
67
|
+
# layout 'fancy'
|
68
|
+
# before_filter 'set_title'
|
69
|
+
#
|
70
|
+
# def set_title
|
71
|
+
# @page_title = "A Fancy Page"
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# def get
|
75
|
+
# mab do
|
76
|
+
# h1 "Welcome to the Fancy Page called #{@page_title}"
|
77
|
+
# p { text "Welcome to my"; b { paragraph } }
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
#
|
83
|
+
class Controller < Widget
|
84
|
+
|
85
|
+
attr_reader :cancel_execution # this can be set by anyone,
|
86
|
+
# methods get/post/run will not be executed
|
87
|
+
|
88
|
+
def self.def_layout
|
89
|
+
begin
|
90
|
+
self.def_layout_name
|
91
|
+
rescue
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.layout(name)
|
97
|
+
define_attr_method(:def_layout_name, name)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.before_filter(method)
|
101
|
+
begin
|
102
|
+
filters = self.def_before_filter
|
103
|
+
filters = "#{filters};#{method}"
|
104
|
+
define_attr_method(:def_before_filter, filters)
|
105
|
+
rescue => e
|
106
|
+
define_attr_method(:def_before_filter, method)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def run_before_filters
|
111
|
+
# $stderr.puts "Run before filters"
|
112
|
+
begin
|
113
|
+
before_filter = self.class.def_before_filter
|
114
|
+
rescue NoMethodError => e
|
115
|
+
# == no filters defined at all
|
116
|
+
# $stderr.puts " No before filters defined for #{self.class}"
|
117
|
+
return
|
118
|
+
end
|
119
|
+
before_filter.split(';').each do |filter|
|
120
|
+
begin
|
121
|
+
if (!self.methods.include?(filter)) then
|
122
|
+
raise ApplicationException.new("Before filter method not defined: '#{filter}'")
|
123
|
+
end
|
124
|
+
ret = self.send(filter)
|
125
|
+
@cancel_execution = true if ret == false
|
126
|
+
# rescue NoMethodError => e
|
127
|
+
# raise ApplicationException.new "Before filter method not defined: '#{filter}'"
|
128
|
+
rescue BreakControllerException => bc
|
129
|
+
handle_exception(bc)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# this method will stop execution of the controller
|
136
|
+
# it's usefull to break somewhere in the middle or to
|
137
|
+
# set a different layout
|
138
|
+
# flags can be
|
139
|
+
# :layout - Set a new layout
|
140
|
+
# :redirect - redirect to a different page
|
141
|
+
# :error - An error message (for application errors)
|
142
|
+
def stop(flags = {})
|
143
|
+
raise BreakControllerException.new(flags)
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# this piece of code will handle a thrown exception BreakControllerException
|
148
|
+
def handle_exception(e)
|
149
|
+
if e.flags.has_key?(:layout) then
|
150
|
+
self.class.layout(e.flags[:layout])
|
151
|
+
end
|
152
|
+
|
153
|
+
if e.flags.has_key?(:redirect) then
|
154
|
+
self.class.layout false
|
155
|
+
to = e.flags[:redirect]
|
156
|
+
clear
|
157
|
+
@_content = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"><html><head><title>Redirecting...</title><meta http-equiv=\"REFRESH\" content=\"0;url=#{to}\"></HEAD></HTML>"
|
158
|
+
@cancel_execution = true
|
159
|
+
end
|
160
|
+
|
161
|
+
if e.flags.has_key?(:error) then
|
162
|
+
self.class.layout false
|
163
|
+
clear
|
164
|
+
@error_message = e.flags[:error]
|
165
|
+
@cancel_execution = true
|
166
|
+
mab { html { body { h2 "Error"; text @error_message }}}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
# Overwrite this method to handle get and post events
|
172
|
+
def run
|
173
|
+
end
|
174
|
+
|
175
|
+
# Overwrite this method to handle get events
|
176
|
+
def get
|
177
|
+
end
|
178
|
+
|
179
|
+
# Overwrite this method to handle post events
|
180
|
+
def post
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
# This is the method as called from App. It will call 'get', 'post', 'run' and handle
|
186
|
+
# exceptions. what within [post,get,run]
|
187
|
+
# TOTHINKABOUT: run before filter from this method?
|
188
|
+
public
|
189
|
+
def _do(what, subcall)
|
190
|
+
return if @cancel_execution
|
191
|
+
|
192
|
+
method_name = what
|
193
|
+
method_name = "#{method_name}_#{subcall}" if subcall
|
194
|
+
begin
|
195
|
+
self.send(method_name) if self.methods.include?(method_name)
|
196
|
+
rescue BreakControllerException => e
|
197
|
+
handle_exception(e)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
def initialize(*args)
|
203
|
+
# set a global variable of the controller object.
|
204
|
+
# Widgets can make use of this to call or modify the controller class.
|
205
|
+
$controller_object = self
|
206
|
+
|
207
|
+
@cancel_execution = false
|
208
|
+
super(*args)
|
209
|
+
|
210
|
+
# FIXME: to think about...
|
211
|
+
get_assigns.each_pair do |k,v|
|
212
|
+
instance_variable_set("@#{k}", v)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
def load_layout
|
218
|
+
return nil if self.class.def_layout.nil?
|
219
|
+
begin
|
220
|
+
layout_class = Object::const_get(self.class.def_layout.capitalize+"Layout")
|
221
|
+
rescue => e
|
222
|
+
# fixme - if layout loading fails we should display some error
|
223
|
+
return nil
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
public
|
228
|
+
def output
|
229
|
+
layout_class = load_layout
|
230
|
+
return content unless layout_class
|
231
|
+
c = content.to_s
|
232
|
+
a = get_assigns
|
233
|
+
a[:content_for_layout] = c
|
234
|
+
@_content = layout_class.new(:assigns=>a)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
end # Module
|
240
|
+
|