kiss 1.1 → 1.7

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.
Files changed (45) hide show
  1. data/LICENSE +1 -1
  2. data/Rakefile +2 -1
  3. data/VERSION +1 -1
  4. data/bin/kiss +151 -34
  5. data/data/scaffold.tgz +0 -0
  6. data/lib/kiss.rb +389 -742
  7. data/lib/kiss/accessors/controller.rb +47 -0
  8. data/lib/kiss/accessors/request.rb +106 -0
  9. data/lib/kiss/accessors/template.rb +23 -0
  10. data/lib/kiss/action.rb +502 -132
  11. data/lib/kiss/bench.rb +14 -5
  12. data/lib/kiss/debug.rb +14 -6
  13. data/lib/kiss/exception_report.rb +22 -299
  14. data/lib/kiss/ext/core.rb +700 -0
  15. data/lib/kiss/ext/rack.rb +33 -0
  16. data/lib/kiss/ext/sequel_database.rb +47 -0
  17. data/lib/kiss/ext/sequel_mysql_dataset.rb +23 -0
  18. data/lib/kiss/form.rb +404 -179
  19. data/lib/kiss/form/field.rb +183 -307
  20. data/lib/kiss/form/field_types.rb +239 -0
  21. data/lib/kiss/format.rb +88 -70
  22. data/lib/kiss/html/exception_report.css +222 -0
  23. data/lib/kiss/html/exception_report.html +210 -0
  24. data/lib/kiss/iterator.rb +14 -12
  25. data/lib/kiss/login.rb +8 -8
  26. data/lib/kiss/mailer.rb +68 -66
  27. data/lib/kiss/model.rb +323 -36
  28. data/lib/kiss/rack/bench.rb +16 -8
  29. data/lib/kiss/rack/email_errors.rb +25 -15
  30. data/lib/kiss/rack/errors_ok.rb +2 -2
  31. data/lib/kiss/rack/facebook.rb +6 -6
  32. data/lib/kiss/rack/file_not_found.rb +10 -8
  33. data/lib/kiss/rack/log_exceptions.rb +3 -3
  34. data/lib/kiss/rack/recorder.rb +2 -2
  35. data/lib/kiss/rack/show_debug.rb +2 -2
  36. data/lib/kiss/rack/show_exceptions.rb +2 -2
  37. data/lib/kiss/request.rb +435 -0
  38. data/lib/kiss/sequel_session.rb +15 -14
  39. data/lib/kiss/static_file.rb +20 -13
  40. data/lib/kiss/template.rb +327 -0
  41. metadata +60 -25
  42. data/lib/kiss/controller_accessors.rb +0 -81
  43. data/lib/kiss/hacks.rb +0 -188
  44. data/lib/kiss/sequel_mysql.rb +0 -25
  45. data/lib/kiss/template_methods.rb +0 -167
@@ -12,7 +12,7 @@ class Kiss
12
12
  # timestamp :updated_at
13
13
  # end
14
14
 
15
- attr_accessor :needs_new_cookie
15
+ _attr_accessor :needs_new_cookie, :object
16
16
 
17
17
  class << self
18
18
  def setup_storage(controller)
@@ -21,9 +21,10 @@ class Kiss
21
21
  primary_key :id, :null => false
22
22
  column :session_id, :varchar, :null => false
23
23
  column :data, :text
24
- column :created_at, :timestamp, :null => false
25
24
  column :updated_at, :timestamp, :null => false
26
- index :session_id
25
+ column :created_at, :timestamp, :null => false
26
+ index :session_id, :unique => true
27
+ index :updated_at
27
28
  end
28
29
 
29
30
  # db << <<-EOT
@@ -93,24 +94,24 @@ class Kiss
93
94
  end
94
95
 
95
96
  def initialize(object)
96
- @object = object
97
- @data = self.class.unmarshal(@object[:data]) || {}
97
+ @_object = object
98
+ @_data = self.class.unmarshal(@_object[:data]) || {}
98
99
  end
99
100
 
100
101
  def values(*args)
101
- @object.values(*args)
102
+ @_object.values(*args)
102
103
  end
103
104
 
104
105
  # Regenerate the Session ID
105
106
  def regenerate
106
- @object.update_attributes(:session_id => Kiss::SessionMixin::rand_uuid)
107
- @needs_new_cookie = true
107
+ @_object.update_attributes(:session_id => Kiss::SessionMixin::rand_uuid)
108
+ @_needs_new_cookie = true
108
109
  end
109
110
 
110
111
  # Recreates the cookie with the default expiration time
111
112
  # Useful during log in for pushing back the expiration date
112
113
  def refresh_expiration
113
- @needs_new_cookie = true
114
+ @_needs_new_cookie = true
114
115
  end
115
116
 
116
117
  # good
@@ -142,20 +143,20 @@ class Kiss
142
143
  # good
143
144
  # Lazy-unmarshal session state.
144
145
  def data
145
- @data
146
+ @_data
146
147
  end
147
148
 
148
149
  # good
149
150
  # Has the session been loaded yet?
150
151
  def loaded?
151
- !! @data
152
+ !! @_data
152
153
  end
153
154
 
154
155
  # good
155
156
  def save
156
- @object[:data] = self.class.marshal(self.data)
157
- @object[:updated_at] = Time.now
158
- @object.save
157
+ @_object[:data] = self.class.marshal(self.data)
158
+ @_object[:updated_at] = Time.now
159
+ @_object.save
159
160
  end
160
161
 
161
162
  private
@@ -1,27 +1,34 @@
1
1
  class Kiss
2
2
  class StaticFile
3
- def initialize(path,mime_type = nil)
4
- @path = path
5
- @mime_type = mime_type
3
+ def initialize(path, options = {})
4
+ @_path = path
5
+ @_options = options
6
6
  end
7
7
 
8
8
  def finish
9
- ext = File.extname(@path)[1..-1]
9
+ ext = File.extname(@_path)[1..-1]
10
10
 
11
- if File.file?(@path) && File.readable?(@path)
12
- [200, {
13
- "Last-Modified" => File.mtime(@path).rfc822,
14
- "Content-Type" => @mime_type || Kiss.mime_type(ext) || "text/plain",
15
- "Content-Length" => File.size(@path).to_s
16
- }, self]
11
+ headers = {
12
+ "Last-Modified" => File.mtime(@_path).rfc822,
13
+ "Content-Type" => @_options[:type] || Kiss.mime_type(ext) || "text/plain",
14
+ "Content-Length" => File.size(@_path).to_s
15
+ }
16
+
17
+ if @_options[:filename]
18
+ headers['Content-Disposition'] = "#{@_options[:disposition] || 'inline'}; filename=#{@_options[:filename]}"
19
+ elsif @_options[:disposition]
20
+ headers['Content-Disposition'] = @_options[:disposition]
21
+ end
22
+
23
+ if File.file?(@_path) && File.readable?(@_path)
24
+ [200, headers, self]
17
25
  else
18
- return [404, {"Content-Type" => "text/plain"},
19
- ["File not found: #{@path}\n"]]
26
+ raise "file not found: #{@_path}"
20
27
  end
21
28
  end
22
29
 
23
30
  def each
24
- File.open(@path, "rb") { |file|
31
+ File.open(@_path, "rb") { |file|
25
32
  while part = file.read(8192)
26
33
  yield part
27
34
  end
@@ -0,0 +1,327 @@
1
+ class Kiss
2
+ class Template
3
+ class << self
4
+ _attr_reader :subdir, :filename, :path, :parent_class, :root_class, :template_dir
5
+ attr_reader :layout
6
+
7
+ # Called when this class or one of its subclasses is inherited.
8
+ # subclass (Class) - the new, inheriting subclass
9
+ def inherited(subclass)
10
+ subclass.init_child_classes
11
+ end
12
+
13
+ # Initialize the subclasses instance variable of this class (or subclass)
14
+ def init_child_classes
15
+ @_child_classes = {}
16
+ end
17
+
18
+ # Gets path to template file according to specified options
19
+ # Options:
20
+ # template - template path, relative to current Template subclass subdirectory or,
21
+ # if starting with '/', relative to root template directory
22
+ # extension - template file extension, to be appended to template path unless
23
+ # template path already contains a dot
24
+ def get_template_path(options = {})
25
+ path = options[:template]
26
+ unless path =~ /\./
27
+ path += '.' + (options[:extension] || 'rhtml')
28
+ end
29
+
30
+ # If template doesn't start with '/', prepend current Template subclass
31
+ # subdirectory path.
32
+ (path =~ /\A\//) ? path : "#{options[:subdir] || self.subdir}/#{path}"
33
+ end
34
+
35
+ def get_root_class(controller, root_dir)
36
+ parent_class = self
37
+
38
+ klass = Class.new(self)
39
+ Kiss.register_class_path(klass, root_dir)
40
+ klass.class_eval do
41
+ @@controller = controller
42
+ @_parent_class = parent_class
43
+ @_root_class = klass
44
+ @_template_dir = root_dir
45
+ @layout = nil
46
+ end
47
+ klass
48
+ end
49
+
50
+ # Select or create the matching template subclass for a given template path.
51
+ def get_subclass_from_path(path, default_extension = nil)
52
+ # TODO: If Kiss configured not to modify template cache,
53
+ # then we should check whether this path has a registered
54
+ # class and, if so, return it.
55
+
56
+ extension = (path =~ /\.(\w+)\Z/) ? $1 : default_extension
57
+ dot_extension = extension ? ('.' + extension) : ''
58
+
59
+ # If path starts with slash, get subclass from root template class for
60
+ # the same dot_extension. Else get subclass from this template class.
61
+ klass = if path[0,1] == '/'
62
+ path = path[1, path.length]
63
+ root_class.get_child_class(dot_extension)
64
+ else
65
+ self
66
+ end
67
+ klass.get_subclass_from_path_parts(path.split(/\/+/), dot_extension)
68
+ end
69
+
70
+ def get_subclass_from_path_parts(parts, dot_extension = '')
71
+ part = parts.shift
72
+ return self if !part
73
+ raise 'bad template path' if part !~ /\A[\w\-\.]*\Z/i
74
+
75
+ test_path = subdir + '/' + part
76
+ klass = if @@controller.directory_exists?(@_template_dir + test_path)
77
+ get_child_class(dot_extension, test_path).
78
+ get_subclass_from_path_parts(parts, dot_extension)
79
+ else
80
+ raise 'bad template path' unless parts.empty?
81
+ get_child_class(dot_extension, subdir, part)
82
+ end
83
+
84
+ klass
85
+ end
86
+
87
+ def get_child_class(dot_extension, subdir = '', filename = nil)
88
+ # each subdir must get a subclass of the parent dir class;
89
+ # each subclass has its own @_child_classes hash of its subdirs
90
+
91
+ is_directory_subclass = !filename
92
+
93
+ if filename
94
+ filename += dot_extension unless filename =~ /#{dot_extension}\Z/
95
+ else
96
+ filename = '_macros' + dot_extension
97
+ end
98
+ template_path = subdir + '/' + filename
99
+ absolute_fs_path = @_template_dir + template_path
100
+ template_body, template_cache_changed = @@controller.file_cache(absolute_fs_path, true)
101
+
102
+ layout_path = subdir + '/_layout' + dot_extension
103
+ layout_file, layout_cache_changed = @@controller.file_cache(@_template_dir + layout_path, true)
104
+
105
+ # Inherit this class's layout path if no layout file in subdir.
106
+ layout_path = @layout unless layout_file
107
+
108
+ if template_cache_changed || !@_child_classes.has_key?(filename)
109
+ child_class = create_child_class(
110
+ :subdir => subdir,
111
+ :filename => filename,
112
+ :layout => layout_path,
113
+ :path => template_path
114
+ )
115
+ Kiss.register_class_path(child_class, absolute_fs_path)
116
+ if template_body
117
+ source = get_ruby_source_from_erb(template_body)
118
+ source = ('def erubis(content); ' + source + '; end') unless is_directory_subclass
119
+ child_class.class_eval(source, absolute_fs_path)
120
+ end
121
+ @_child_classes[filename] = child_class
122
+ elsif layout_cache_changed
123
+ # Layout file either added or modified; in case it was newly added,
124
+ # set the subclass's @layout (if it's not already set).
125
+ @_child_classes[filename].class_eval do
126
+ @layout ||= layout_path
127
+ end
128
+ end
129
+
130
+ @_child_classes[filename]
131
+ end
132
+
133
+ def create_child_class(attrs)
134
+ instance_var_attrs = {
135
+ :parent_class => self,
136
+ :root_class => @_root_class,
137
+ :template_dir => @_template_dir
138
+ }.merge(attrs)
139
+
140
+ klass = Class.new(self)
141
+ klass._instance_variables_set_from_attrs(instance_var_attrs)
142
+ klass
143
+ end
144
+
145
+ def get_ruby_source_from_erb(erb)
146
+ source = Erubis::Eruby.new(preprocess_erb(erb)).src
147
+ source.gsub!(/\b_buf\b/, '@_buf')
148
+ source
149
+ end
150
+
151
+ # Pre-process Kiss additions in ERB code.
152
+ def preprocess_erb(erb)
153
+ # macros
154
+ result = erb.gsub(
155
+ /\<\%\s*macro\s+(\w+)/,
156
+ "<% def \\1(*args, &block); ob = swap_buf(''); _macro_\\1(*args, &block); swap_buf(ob); end; def _macro_\\1"
157
+ ).gsub(
158
+ /\<\%\s*end\s+macro\s*\%\>/,
159
+ '<% end %>'
160
+ )
161
+
162
+ # may be DEPRECATED soon
163
+ # generic loop iterator, using 'iterate'
164
+ result.gsub!(/\<\%\s*iterate\s+(\w+)\s+(.*?)\s*\%\>/,
165
+ "<% \\1 = Kiss::Iterator.new; \\2; \\1.increment %>")
166
+
167
+ # DEPRECATED
168
+ # for loop iterator
169
+ result.gsub!(/\<\%\s*for\s+(\w+)\s+in\s+(.*?)\s*\%\>/,
170
+ "<% \\1_loop = Kiss::Iterator.new(\\2); for \\1 in \\1_loop.collection; \\1_loop.increment %>")
171
+
172
+ result
173
+ end
174
+
175
+ def include_macros(template_path)
176
+ if template_path[0,1] != '/'
177
+ template_path = subdir + '/' + template_path
178
+ end
179
+ template_body = @@controller.file_cache(@_template_dir + template_path)
180
+ raise 'bad template path' if !template_body
181
+ class_eval(get_ruby_source_from_erb(template_body))
182
+ end
183
+
184
+ end
185
+ self.init_child_classes
186
+
187
+ include Kiss::ControllerAccessors
188
+ include Kiss::RequestAccessors
189
+ include Kiss::DatabaseAccessors
190
+ include Kiss::TemplateMethods
191
+
192
+ attr_accessor :layout, :title
193
+
194
+ _attr_reader :locals, :request
195
+ alias_method :local, :locals
196
+
197
+ def initialize(request, delegate = nil, predecessor = nil, locals = {})
198
+ @_delegate = delegate
199
+ @_request = request
200
+ @_controller = delegate.controller if delegate
201
+ @_predecessor = predecessor || delegate
202
+ @_locals = locals
203
+ end
204
+
205
+ def make_layout_absolute(relative_object)
206
+ if @layout && @layout !~ /\A\//
207
+ @layout = relative_object.class.subdir + '/' + @layout
208
+ end
209
+ end
210
+
211
+ def call(content = nil)
212
+ import_instance_variables(@_predecessor)
213
+ make_layout_absolute(@_predecessor)
214
+
215
+ content = erubis(content)
216
+
217
+ # self.class.no_more_macros
218
+
219
+ make_layout_absolute(self)
220
+ export_instance_variables(@_predecessor)
221
+
222
+ content
223
+ end
224
+
225
+ def erubis(content, locals = {})
226
+ raise Kiss::FileNotFoundError::Template, "template not found: '#{self.class.path}'"
227
+ end
228
+
229
+ private
230
+
231
+ def include_macros(template_path)
232
+ self.class.include_macros(template_path)
233
+ end
234
+
235
+ def method_missing(method, *args, &block)
236
+ if method.to_s =~ /\A_class_(\d+)_(.*)/
237
+ __send__ $2.to_sym, $1.to_i, *args, &block
238
+ else
239
+ @_delegate.__send__ method, *args, &block
240
+ end
241
+ end
242
+
243
+ def context_class
244
+ Kiss.context_class
245
+ end
246
+
247
+ # Reads file specified by options and return contents (without template
248
+ # processing).
249
+ def read_file(filename)
250
+ File.read(filename)
251
+ end
252
+
253
+ def insert(*args)
254
+ File.read(self.class.template_dir + context_class.get_template_path(get_options_hash(*args)))
255
+ end
256
+ alias_method :insert_file, :insert
257
+
258
+ # Processes template specified by options and return results.
259
+ def process_template(*args)
260
+ options = get_options_hash(*args)
261
+ path = context_class.get_template_path(options)
262
+
263
+ if (options[:data] || options[:vars])
264
+ new_data = (options[:data] || {}).merge(options[:vars] || {})
265
+ old_data = self.data
266
+ self.data = self.data.merge(new_data)
267
+ else
268
+ old_data = nil
269
+ end
270
+
271
+ # can Template.get_child_class do the path parsing? maybe...
272
+ # and then Action.get_child_class can do the same? what about parsing mixed-in args?
273
+ content = self.class.root_class.get_subclass_from_path(path).new(request, @_delegate, self, options[:locals] || options[:local] || {}).call
274
+ self.data = old_data if old_data
275
+
276
+ content
277
+ end
278
+ alias_method :render, :process_template
279
+ alias_method :process, :process_template
280
+ alias_method :process_partial, :process_template
281
+ alias_method :render_partial, :process_template
282
+
283
+ # Convert arguments into options hash for use get_template_path.
284
+ # - get_options_hash(template_path)
285
+ # - get_options_hash(template_path, options_hash)
286
+ # - get_options_hash(options_hash)
287
+ # where template_path is relative to the app's template_dir
288
+ # and options_hash specifies:
289
+ # - template: template path
290
+ # - layout: layout template path
291
+ def get_options_hash(*args)
292
+ case args.length
293
+ when 0
294
+ {}
295
+ when 1
296
+ args[0].is_a?(Hash) ? args[0] : { :template => args[0] }
297
+ when 2
298
+ args[1].merge( :template => args[0] )
299
+ else
300
+ raise 'too many arguments'
301
+ end
302
+ end
303
+
304
+
305
+ # Escapes string for use in URLs.
306
+ def url_escape(string)
307
+ string.url_escape
308
+ end
309
+ alias_method :u, :url_escape
310
+ alias_method :escape, :url_escape
311
+ alias_method :url_encode, :url_escape
312
+ alias_method :escape_url, :url_escape
313
+
314
+ # Encodes string for output to HTML.
315
+ def html_escape(str)
316
+ str.html_escape
317
+ end
318
+ alias_method :h, :html_escape
319
+ alias_method :escape_html, :html_escape
320
+
321
+ def swap_buf(n)
322
+ o = @_buf
323
+ @_buf = n
324
+ o
325
+ end
326
+ end
327
+ end