kiss 1.0.4 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = @controller.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
- # Returns object from specified table, with id matching first argument (or argument of specified index).
44
- def arg_object(table,index = 0,desired_values = {})
45
- id = arg(index)
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 result[key] == value
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
- result
78
+ object
53
79
  end
54
-
55
- # Returns arg_object, or new object of specified table if arg_object is not found.
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
- begin
58
- arg_object(table,index,desired_values)
59
- rescue => e
60
- dbm[table].new(desired_values)
61
- end
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.send_email({
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 "#{app}#{ app_absolute_path(action) }"
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 = controller.action_subdir + '/' + path if path !~ /\A\//
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(action_subdir + '/')
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
- content.sub(/(\<html)/i,%Q(<base href="#{@base_url}" />\\1))
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
- controller.send_response(output,options)
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 = {})
@@ -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(body)
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
- body.each {|p| html += p }
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 db
26
- controller.db
30
+ def models
31
+ controller.models
27
32
  end
28
- alias_method :database, :db
33
+ alias_method :dbm, :models
29
34
 
30
- def dbm
31
- controller.dbm
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 host
92
- controller.host
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 extension
108
- controller.extension
63
+ def env
64
+ controller.env
109
65
  end
110
-
111
- def arg(index)
112
- controller.args[index]
66
+
67
+ def protocol
68
+ controller.protocol
113
69
  end
114
-
115
- def authenticate
116
- controller.authenticate
70
+
71
+ def host
72
+ controller.host
117
73
  end
118
74
 
119
75
  def new_email
@@ -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(body)
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://" + h(Kiss.absolute_path(filename)) + '&amp;line=' + line
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(/\</,'&lt;')}</b></tt>
@@ -37,9 +37,8 @@ EOT
37
37
  </div>
38
38
  EOT
39
39
  end.join
40
-
41
- body.each {|p| html += p }
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 lines[0] =~ /\/lib\/kiss(\/|\.rb)/
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)))+"&amp;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.h(*args)
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 { margin-bottom:1em; }
110
- div.context { margin: 10px 0 10px 40px; background-color: #fff; }
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>