kiss 1.0.4 → 1.1
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/Rakefile +9 -6
- data/VERSION +1 -1
- data/bin/kiss +75 -0
- data/data/scaffold.tgz +0 -0
- data/lib/kiss.rb +328 -337
- data/lib/kiss/action.rb +133 -47
- data/lib/kiss/bench.rb +2 -3
- data/lib/kiss/controller_accessors.rb +24 -68
- data/lib/kiss/debug.rb +4 -5
- data/lib/kiss/exception_report.rb +10 -6
- data/lib/kiss/hacks.rb +7 -3
- data/lib/kiss/iterator.rb +6 -6
- data/lib/kiss/login.rb +45 -0
- data/lib/kiss/mailer.rb +20 -2
- data/lib/kiss/model.rb +27 -31
- data/lib/kiss/rack/bench.rb +5 -0
- data/lib/kiss/rack/errors_ok.rb +19 -0
- data/lib/kiss/rack/facebook.rb +1 -0
- data/lib/kiss/rack/recorder.rb +22 -0
- data/lib/kiss/rack/show_debug.rb +1 -1
- data/lib/kiss/rack/show_exceptions.rb +1 -1
- data/lib/kiss/template_methods.rb +71 -30
- metadata +16 -8
data/lib/kiss/action.rb
CHANGED
@@ -1,64 +1,115 @@
|
|
1
1
|
class Kiss
|
2
2
|
class Action
|
3
|
+
class << self
|
4
|
+
def inherited(subclass)
|
5
|
+
subclass.clear_subclasses
|
6
|
+
end
|
7
|
+
def clear_subclasses
|
8
|
+
@subclasses = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_subclass(key, path, controller)
|
12
|
+
# each subdir must get a subclass of the parent dir class;
|
13
|
+
# each subclass has its own @subclasses hash of its subdirs
|
14
|
+
|
15
|
+
src, updated = controller.file_cache(path, true)
|
16
|
+
|
17
|
+
if updated
|
18
|
+
klass = Class.new(self)
|
19
|
+
klass.class_eval(src,path) if src
|
20
|
+
@subclasses[key] = klass
|
21
|
+
end
|
22
|
+
|
23
|
+
@subclasses[key]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
self.clear_subclasses
|
27
|
+
|
28
|
+
|
3
29
|
include Kiss::TemplateMethods
|
30
|
+
|
31
|
+
attr_reader :controller, :forms, :action, :action_uri, :action_subdir, :template, :extension,
|
32
|
+
:args, :params, :output, :output_options
|
33
|
+
|
34
|
+
def arg(index)
|
35
|
+
@args[index]
|
36
|
+
end
|
37
|
+
|
38
|
+
def args
|
39
|
+
@args
|
40
|
+
end
|
4
41
|
|
5
42
|
# Raises custom error message for missing methods, specifying current action
|
6
43
|
# more clearly than standard error message.
|
7
44
|
def method_missing(meth)
|
8
45
|
raise NoMethodError, "undefined method `#{meth}'"
|
9
46
|
end
|
10
|
-
|
11
|
-
def controller
|
12
|
-
@controller
|
13
|
-
end
|
14
|
-
|
47
|
+
|
15
48
|
# Creates a new action instance from controller data.
|
16
|
-
def initialize(controller)
|
49
|
+
def initialize(controller,action,action_uri,action_subdir,extension,args,params = nil)
|
17
50
|
@controller = controller
|
18
|
-
@action = @
|
19
|
-
|
51
|
+
@action = @template = action
|
52
|
+
@action_uri = action_uri
|
53
|
+
@action_subdir = action_subdir
|
54
|
+
@extension = extension
|
55
|
+
@args = args
|
56
|
+
@params = params || {}
|
57
|
+
|
20
58
|
@data = {}
|
21
59
|
@forms = {}
|
22
60
|
@template_dir = @controller.template_dir
|
23
61
|
end
|
24
62
|
|
25
|
-
# Invokes controller's file_cache, setting exception class to Kiss::TemplateFileNotFound,
|
26
|
-
# which will be raised if file is not found. (This exception class may be caught and
|
27
|
-
# handled by Rack builder option FileNotFound to produce an HTTP 404 error response.)
|
28
|
-
def file_cache(path,fnf_file_type,fnf_exception_class = Kiss::TemplateFileNotFound,&block)
|
29
|
-
controller.file_cache(path,fnf_file_type,fnf_exception_class,&block)
|
30
|
-
end
|
31
|
-
|
32
|
-
def forms
|
33
|
-
@forms
|
34
|
-
end
|
35
|
-
|
36
63
|
# Returns true is `form' param matches current action path.
|
37
64
|
def form_action_match
|
38
65
|
form = params['form']
|
39
66
|
return false unless form =~ /^\/?\w+(\/\w+)*$/s
|
40
67
|
return form == @action || @action =~ /\/#{form}/
|
41
68
|
end
|
42
|
-
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
result = dbm[table].where(:id => id).first || (raise "#{table.to_s.singularize} id=#{id} not found")
|
69
|
+
|
70
|
+
# Raises error if database model object does not match the key=value data in
|
71
|
+
# desired_values hash.
|
72
|
+
def check_desired_values(object,desired_values)
|
47
73
|
desired_values.each_pair do |key,value|
|
48
|
-
unless
|
49
|
-
raise "#{table.to_s.singularize} id=#{id} does not have #{key} == #{value.inspect}"
|
74
|
+
unless object[key] == value
|
75
|
+
raise "#{object.table.to_s.singularize} id=#{object.id} does not have #{key} == #{value.inspect}"
|
50
76
|
end
|
51
77
|
end
|
52
|
-
|
78
|
+
object
|
53
79
|
end
|
54
|
-
|
55
|
-
#
|
80
|
+
|
81
|
+
# Verifies valid id value, selects database model object from specified table
|
82
|
+
# with specified id, and checks result to match desired_values.
|
83
|
+
def get_object_by_id(table, id, desired_values = {})
|
84
|
+
id = id.to_i
|
85
|
+
raise 'bad object id' unless id > 0
|
86
|
+
result = dbm[table][id] || (raise "#{table.to_s.singularize} id=#{id} not found")
|
87
|
+
check_desired_values(result,desired_values)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns database model object from specified table, with id matching argument
|
91
|
+
# of specified index (defaults to first argument). Raises error if object not
|
92
|
+
# found in database or object data does not match desired_values.
|
93
|
+
def arg_object(table, index = 0, desired_values = {})
|
94
|
+
get_object_by_id(table, arg(index).to_i, desired_values)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns arg_object, or new object of specified table if arg_object fails.
|
56
98
|
def arg_object_or_new(table,index = 0,desired_values = {})
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
99
|
+
arg_object(table,index,desired_values) rescue dbm[table].new(desired_values)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns database model object from specified table, with id matching param
|
103
|
+
# of specified key (defaults to singular form of table name). Raises error if
|
104
|
+
# object not found in database or object data does not match desired_values.
|
105
|
+
def param_object(table, key = nil, desired_values = {})
|
106
|
+
key ||= table.to_s.singularize
|
107
|
+
get_object_by_id(table, params[key.to_s], desired_values)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns param_object, or new object of specified table if param_object fails.
|
111
|
+
def param_object_or_new(table,key = nil,desired_values = {})
|
112
|
+
param_object(table,key,desired_values) rescue dbm[table].new(desired_values)
|
62
113
|
end
|
63
114
|
|
64
115
|
# Validates param of specified key against specified format.
|
@@ -80,10 +131,10 @@ class Kiss
|
|
80
131
|
|
81
132
|
# Creates and invokes new Kiss::Mailer instance to send email message via SMTP.
|
82
133
|
def send_email(options = {})
|
83
|
-
Kiss::Mailer.new
|
134
|
+
Kiss::Mailer.new({
|
84
135
|
:data => data,
|
85
136
|
:controller => controller
|
86
|
-
}.merge(options))
|
137
|
+
}.merge(options)).send
|
87
138
|
end
|
88
139
|
|
89
140
|
# Sends HTTP 302 response to redirect client agent to specified URL.
|
@@ -92,14 +143,24 @@ class Kiss
|
|
92
143
|
end
|
93
144
|
|
94
145
|
# Redirects to specified action path, which may also include arguments.
|
95
|
-
def redirect_action(action)
|
96
|
-
redirect_url
|
146
|
+
def redirect_action(action, options = {})
|
147
|
+
redirect_url( app(options) + app_absolute_path(action) + (options[:params] ?
|
148
|
+
'?' + options[:params].keys.map do |k|
|
149
|
+
"#{Kiss.url_escape k.to_s}=#{Kiss.url_escape options[:params][k].to_s}"
|
150
|
+
end.join('&') : '')
|
151
|
+
)
|
97
152
|
end
|
153
|
+
|
154
|
+
def invoke(path,options = {})
|
155
|
+
action_handler = controller.invoke_action(path,options[:params] || {},{:layout => nil})
|
156
|
+
action_handler.output
|
157
|
+
end
|
158
|
+
alias_method :process_action, :invoke
|
98
159
|
|
99
160
|
# Convert specified action path by prefixing current action_subdir,
|
100
161
|
# unless specified path starts with slash (/).
|
101
162
|
def app_absolute_path(path)
|
102
|
-
path =
|
163
|
+
path = @action_subdir + path if path !~ /\A\//
|
103
164
|
path
|
104
165
|
end
|
105
166
|
|
@@ -111,15 +172,17 @@ class Kiss
|
|
111
172
|
return options[:text].to_s
|
112
173
|
end
|
113
174
|
|
114
|
-
@base_url ||= app
|
115
|
-
@layout = options.is_a?(Hash) && options.has_key?(:layout) ? options[:layout] :
|
116
|
-
(extension == 'rhtml' ? Kiss.layout : nil)
|
175
|
+
@base_url ||= app + action_uri
|
117
176
|
|
177
|
+
unless defined?(@layout)
|
178
|
+
@layout = options.is_a?(Hash) && options.has_key?(:layout) ? options[:layout] :
|
179
|
+
(extension == 'rhtml' ? Kiss.layout : nil)
|
180
|
+
end
|
118
181
|
content = options[:content].is_a?(String) ? options[:content] : process({
|
119
182
|
:template => template,
|
120
183
|
:extension => extension
|
121
184
|
}.merge(options))
|
122
|
-
|
185
|
+
|
123
186
|
while @layout
|
124
187
|
layout_path = get_template_path(
|
125
188
|
:template => @layout,
|
@@ -133,20 +196,43 @@ class Kiss
|
|
133
196
|
# process layout file
|
134
197
|
content = erubis(layout_path,binding,content)
|
135
198
|
end
|
136
|
-
|
137
199
|
# add a base tag in case of arguments appended to request URI
|
138
|
-
|
200
|
+
|
201
|
+
base_url = options.has_key?(:base_url) ? options[:base_url] : @base_url
|
202
|
+
if (base_url)
|
203
|
+
content = Kiss.html_prepend(%Q(<base href="#{base_url}" />),content,'head')
|
204
|
+
end
|
205
|
+
|
206
|
+
content
|
139
207
|
end
|
140
208
|
|
141
209
|
# Render and return response to Rack.
|
142
210
|
def render(options = {})
|
143
|
-
output = render_to_string(options)
|
144
|
-
|
211
|
+
@output = render_to_string(options)
|
212
|
+
@output_options = options
|
213
|
+
throw :kiss_action_done
|
145
214
|
end
|
146
215
|
|
147
216
|
# Placeholder for generic actions that do nothing but render template.
|
148
217
|
# render is called from Kiss#call after this method returns.
|
149
218
|
def call; end
|
219
|
+
|
220
|
+
# Does nothing by default; override this method in your _action.rb or
|
221
|
+
# other action files to specify authentication behavior.
|
222
|
+
def authenticate; end
|
223
|
+
|
224
|
+
# Does nothing by default; override this method in your _action.rb or
|
225
|
+
# other action files to expand objects from persistent login data.
|
226
|
+
def expand_login; end
|
227
|
+
|
228
|
+
# Ensure that action requested via SSL; redirects to same action with
|
229
|
+
# https protocol if protocol is not https.
|
230
|
+
def force_ssl(options = {})
|
231
|
+
unless protocol == 'https'
|
232
|
+
redirect_action(@action, options.merge( :protocol => 'https' ))
|
233
|
+
end
|
234
|
+
end
|
235
|
+
alias_method :force_https, :force_ssl
|
150
236
|
|
151
237
|
# Creates and adds form to current action, using specified attributes.
|
152
238
|
def add_form(attrs = {})
|
data/lib/kiss/bench.rb
CHANGED
@@ -4,7 +4,7 @@ class Kiss
|
|
4
4
|
# TextMate links to source files, plus login hash, last SQL,
|
5
5
|
# GET/POST params, cookies, and Rack environment variables.
|
6
6
|
module Bench
|
7
|
-
def prepend_benchmarks(
|
7
|
+
def prepend_benchmarks(document)
|
8
8
|
html = <<-EOT
|
9
9
|
<style>
|
10
10
|
.kiss_bench {
|
@@ -39,8 +39,7 @@ EOT
|
|
39
39
|
EOT
|
40
40
|
end.join
|
41
41
|
|
42
|
-
|
43
|
-
html
|
42
|
+
Kiss.html_prepend(html,document,'body')
|
44
43
|
end
|
45
44
|
|
46
45
|
def context_link(context)
|
@@ -4,46 +4,42 @@ class Kiss
|
|
4
4
|
Kiss.environment
|
5
5
|
end
|
6
6
|
|
7
|
+
def project_dir
|
8
|
+
Kiss.project_dir
|
9
|
+
end
|
10
|
+
|
11
|
+
def public_dir
|
12
|
+
Kiss.public_dir
|
13
|
+
end
|
14
|
+
|
7
15
|
def upload_dir
|
8
16
|
Kiss.upload_dir
|
9
17
|
end
|
10
|
-
|
11
|
-
def pub(*args)
|
12
|
-
Kiss.pub(*args)
|
13
|
-
end
|
14
18
|
end
|
15
19
|
|
16
20
|
module ControllerAccessors
|
17
21
|
def debug(object)
|
18
22
|
controller.debug(object, Kernel.caller[0])
|
19
23
|
end
|
24
|
+
alias_method :trace, :debug
|
20
25
|
|
21
26
|
def bench(label = nil)
|
22
27
|
controller.bench(label, Kernel.caller[0])
|
23
28
|
end
|
24
29
|
|
25
|
-
def
|
26
|
-
controller.
|
30
|
+
def models
|
31
|
+
controller.models
|
27
32
|
end
|
28
|
-
alias_method :
|
33
|
+
alias_method :dbm, :models
|
29
34
|
|
30
|
-
def
|
31
|
-
controller.
|
35
|
+
def database
|
36
|
+
controller.database
|
32
37
|
end
|
33
|
-
alias_method :models, :dbm
|
34
38
|
|
35
39
|
def file_cache(*args,&block)
|
36
40
|
controller.file_cache(*args,&block)
|
37
41
|
end
|
38
42
|
|
39
|
-
def params
|
40
|
-
controller.params
|
41
|
-
end
|
42
|
-
|
43
|
-
def args
|
44
|
-
controller.args
|
45
|
-
end
|
46
|
-
|
47
43
|
def session
|
48
44
|
controller.session
|
49
45
|
end
|
@@ -55,65 +51,25 @@ class Kiss
|
|
55
51
|
def request
|
56
52
|
controller.request
|
57
53
|
end
|
58
|
-
|
59
|
-
def set_login_expires(*args)
|
60
|
-
controller.set_login_expires(*args)
|
61
|
-
end
|
62
|
-
|
63
|
-
def set_login_data(*args)
|
64
|
-
controller.set_login_data(*args)
|
65
|
-
end
|
66
|
-
|
67
|
-
def set_login_session(*args)
|
68
|
-
controller.set_login_session(*args)
|
69
|
-
end
|
70
54
|
|
71
|
-
def logout
|
72
|
-
controller.logout
|
73
|
-
end
|
74
|
-
|
75
|
-
def reset_login_data
|
76
|
-
controller.reset_login_data
|
77
|
-
end
|
78
|
-
|
79
|
-
def reset_login_session
|
80
|
-
controller.reset_login_session
|
81
|
-
end
|
82
|
-
|
83
|
-
def login_session_valid?
|
84
|
-
controller.login_session_valid?
|
85
|
-
end
|
86
|
-
|
87
55
|
def app(*args)
|
88
56
|
controller.app_url(*args)
|
89
57
|
end
|
90
58
|
|
91
|
-
def
|
92
|
-
controller.
|
93
|
-
end
|
94
|
-
|
95
|
-
def action
|
96
|
-
controller.action
|
97
|
-
end
|
98
|
-
|
99
|
-
def template
|
100
|
-
controller.template
|
101
|
-
end
|
102
|
-
|
103
|
-
def action_subdir
|
104
|
-
controller.action_subdir
|
59
|
+
def pub(*args)
|
60
|
+
controller.pub_url(*args)
|
105
61
|
end
|
106
62
|
|
107
|
-
def
|
108
|
-
controller.
|
63
|
+
def env
|
64
|
+
controller.env
|
109
65
|
end
|
110
|
-
|
111
|
-
def
|
112
|
-
controller.
|
66
|
+
|
67
|
+
def protocol
|
68
|
+
controller.protocol
|
113
69
|
end
|
114
|
-
|
115
|
-
def
|
116
|
-
controller.
|
70
|
+
|
71
|
+
def host
|
72
|
+
controller.host
|
117
73
|
end
|
118
74
|
|
119
75
|
def new_email
|
data/lib/kiss/debug.rb
CHANGED
@@ -4,7 +4,7 @@ class Kiss
|
|
4
4
|
# TextMate links to source files, plus login hash, last SQL,
|
5
5
|
# GET/POST params, cookies, and Rack environment variables.
|
6
6
|
module Debug
|
7
|
-
def prepend_debug(
|
7
|
+
def prepend_debug(document)
|
8
8
|
html = <<-EOT
|
9
9
|
<style>
|
10
10
|
.kiss_debug {
|
@@ -29,7 +29,7 @@ text-decoration: underline;
|
|
29
29
|
EOT
|
30
30
|
html += @debug_messages.map do |object,context|
|
31
31
|
filename, line, method = context.split(/:/)
|
32
|
-
textmate_url = "txmt://open?url=file://" +
|
32
|
+
textmate_url = "txmt://open?url=file://" + Kiss.url_escape(Kiss.absolute_path(filename)) + '&line=' + line
|
33
33
|
<<-EOT
|
34
34
|
<div class="kiss_debug">
|
35
35
|
<tt><b>#{object.gsub(/\</,'<')}</b></tt>
|
@@ -37,9 +37,8 @@ EOT
|
|
37
37
|
</div>
|
38
38
|
EOT
|
39
39
|
end.join
|
40
|
-
|
41
|
-
|
42
|
-
html
|
40
|
+
|
41
|
+
Kiss.html_prepend(html,document,'body')
|
43
42
|
end
|
44
43
|
end
|
45
44
|
end
|
@@ -15,7 +15,7 @@ class Kiss
|
|
15
15
|
end
|
16
16
|
|
17
17
|
backtrace = exception.backtrace
|
18
|
-
#backtrace.shift while
|
18
|
+
#backtrace.shift while (backtrace[0] =~ /\/lib\/kiss(\/|\.rb)/) || (backtrace[0] =~ /\/lib\/sequel/)
|
19
19
|
|
20
20
|
frames = backtrace.map { |line|
|
21
21
|
frame = {}
|
@@ -58,8 +58,12 @@ class Kiss
|
|
58
58
|
"txmt://open?url=file://"+(h(Kiss.absolute_path(frame.filename)))+"&line="+(h frame.lineno)
|
59
59
|
end
|
60
60
|
|
61
|
+
def w(str)
|
62
|
+
str.gsub(/([;\/])/,'\1<wbr/>')
|
63
|
+
end
|
64
|
+
|
61
65
|
def h(*args)
|
62
|
-
Kiss.
|
66
|
+
Kiss.html_escape(*args)
|
63
67
|
end
|
64
68
|
|
65
69
|
# :stopdoc:
|
@@ -106,8 +110,8 @@ class Kiss
|
|
106
110
|
table.source td {
|
107
111
|
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
108
112
|
ul.traceback { list-style-type:none; }
|
109
|
-
ul.traceback li.frame {
|
110
|
-
div.context { margin:
|
113
|
+
ul.traceback li.frame { padding: 8px; border-top: 1px solid #ccc; }
|
114
|
+
div.context { margin: 6px 0 2px 40px; background-color: #fff; }
|
111
115
|
div.context ol {
|
112
116
|
padding: 1px; margin-bottom: -1px; }
|
113
117
|
div.context ol li {
|
@@ -216,7 +220,7 @@ class Kiss
|
|
216
220
|
<ul class="traceback">
|
217
221
|
<% frames.each { |frame| %>
|
218
222
|
<li class="frame">
|
219
|
-
<a href="<%= textmate_href(frame) %>">line <%= h frame.lineno %></a> of <code><%=h frame.filename %></code> (in <code><b><%=h frame.function %></b></code>)
|
223
|
+
<a href="<%= textmate_href(frame) %>">line <%= h frame.lineno %></a> of <code><%=w h frame.filename %></code> (in <code><b><%=h frame.function %></b></code>)
|
220
224
|
|
221
225
|
<% if frame.context_line %>
|
222
226
|
<div class="context" id="c<%=h frame.object_id %>">
|
@@ -339,7 +343,7 @@ class Kiss
|
|
339
343
|
<% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
340
344
|
<tr>
|
341
345
|
<td><%=h key %></td>
|
342
|
-
<td class="code"><div><%=h val %></div></td>
|
346
|
+
<td class="code"><div><%=w h val %></div></td>
|
343
347
|
</tr>
|
344
348
|
<% } %>
|
345
349
|
</tbody>
|