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.
@@ -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>