kiss 1.1 → 1.7

Sign up to get free protection for your applications and to get access to all the features.
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