kiss 0.9.4 → 1.0
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/VERSION +1 -1
- data/lib/kiss.rb +545 -446
- data/lib/kiss/action.rb +34 -56
- data/lib/kiss/bench.rb +54 -0
- data/lib/kiss/controller_accessors.rb +42 -26
- data/lib/kiss/debug.rb +45 -0
- data/lib/kiss/exception_report.rb +49 -44
- data/lib/kiss/form.rb +105 -86
- data/lib/kiss/form/field.rb +212 -98
- data/lib/kiss/format.rb +175 -37
- data/lib/kiss/hacks.rb +133 -22
- data/lib/kiss/mailer.rb +4 -16
- data/lib/kiss/model.rb +60 -28
- data/lib/kiss/rack/bench.rb +2 -87
- data/lib/kiss/rack/log_exceptions.rb +2 -11
- data/lib/kiss/rack/show_debug.rb +3 -71
- data/lib/kiss/rack/show_exceptions.rb +2 -15
- data/lib/kiss/sequel_mysql.rb +4 -2
- data/lib/kiss/static_file.rb +31 -0
- data/lib/kiss/template_methods.rb +4 -11
- metadata +5 -3
- data/lib/kiss/rack/file.rb +0 -0
data/lib/kiss/model.rb
CHANGED
@@ -4,33 +4,37 @@ class Kiss
|
|
4
4
|
# to cache database model classes, unless no model_dir is specified.
|
5
5
|
class Model < Sequel::Model
|
6
6
|
class << self
|
7
|
+
def set_dataset(source)
|
8
|
+
super(source)
|
9
|
+
end
|
10
|
+
|
7
11
|
def name
|
8
12
|
@table.to_s
|
9
13
|
end
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
:"#{name.singularize.demodulize.underscore}_id"
|
14
|
+
|
15
|
+
def controller
|
16
|
+
@controller
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
17
|
-
|
19
|
+
def controller=(controller)
|
20
|
+
@controller = controller
|
18
21
|
end
|
19
22
|
|
20
|
-
def
|
21
|
-
|
23
|
+
def table
|
24
|
+
@table.to_sym
|
22
25
|
end
|
23
26
|
|
24
27
|
def table=(table)
|
25
28
|
@table = table
|
26
29
|
end
|
27
30
|
|
28
|
-
|
29
|
-
|
31
|
+
# Name symbol for default foreign key
|
32
|
+
def default_foreign_key
|
33
|
+
:"#{name.singularize.demodulize.underscore}_id"
|
30
34
|
end
|
31
35
|
|
32
|
-
def
|
33
|
-
|
36
|
+
def find_or_new(cond)
|
37
|
+
find(cond) || new(cond)
|
34
38
|
end
|
35
39
|
|
36
40
|
# TODO: Fix has_many and many_to_many associations
|
@@ -45,6 +49,8 @@ class Kiss
|
|
45
49
|
|
46
50
|
association_reflections[name]
|
47
51
|
end
|
52
|
+
|
53
|
+
include Kiss::KissAccessors
|
48
54
|
end
|
49
55
|
|
50
56
|
def method_missing(meth)
|
@@ -52,38 +58,43 @@ class Kiss
|
|
52
58
|
end
|
53
59
|
|
54
60
|
def controller
|
55
|
-
|
61
|
+
self.class.controller
|
56
62
|
end
|
57
63
|
|
58
64
|
include Kiss::ControllerAccessors
|
59
65
|
end
|
60
66
|
|
61
67
|
class ModelCache
|
62
|
-
def
|
68
|
+
def self.model_dir=(model_dir = nil)
|
69
|
+
@@model_dir = model_dir && ::File.directory?(model_dir) ? model_dir : nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(controller = nil)
|
63
73
|
@controller = controller
|
64
|
-
@model_dir = model_dir && File.directory?(model_dir) ? model_dir : nil
|
65
74
|
@cache = {}
|
66
75
|
end
|
67
76
|
|
68
77
|
def [](source)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
78
|
+
@cache[source] ||= begin
|
79
|
+
dataset = Model.db[source]
|
80
|
+
dataset.controller = @controller
|
81
|
+
(@@model_dir && source.is_a?(Symbol)) ? begin
|
82
|
+
# use file_cache
|
83
|
+
model_path = "#{@@model_dir}/#{source}.rb"
|
84
|
+
src = Kiss.file_cache(model_path)
|
73
85
|
klass = Class.new(Kiss::Model)
|
74
|
-
klass.set_dataset(
|
86
|
+
klass.set_dataset(dataset)
|
87
|
+
klass.controller = @controller
|
75
88
|
klass.table = source
|
76
89
|
klass.class_eval(src,model_path) if src
|
77
90
|
klass
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
# no mapping from source to filesystem path
|
82
|
-
# use ModelCache's own cache
|
83
|
-
@cache[source] ||= begin
|
91
|
+
end : begin
|
92
|
+
# no model_dir, or source is not a symbol
|
93
|
+
# no mapping from source to filesystem path
|
84
94
|
klass = Class.new(Kiss::Model)
|
85
|
-
klass.set_dataset(
|
95
|
+
klass.set_dataset(dataset)
|
86
96
|
klass.table = source if source.is_a?(Symbol)
|
97
|
+
klass.controller = @controller
|
87
98
|
klass
|
88
99
|
end
|
89
100
|
end
|
@@ -106,9 +117,30 @@ end
|
|
106
117
|
|
107
118
|
Sequel::Model::Associations::AssociationReflection.class_eval do
|
108
119
|
def associated_class
|
109
|
-
self[:class] ||=
|
120
|
+
self[:class] ||= self[:model].controller.dbm[self[:class_name].to_s.pluralize.to_sym]
|
110
121
|
end
|
111
122
|
def default_left_key
|
112
123
|
:"#{self[:model].name.singularize.underscore}_id"
|
113
124
|
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class Sequel::Model
|
128
|
+
def inherited(subclass)
|
129
|
+
self.instance_variables.each do |var|
|
130
|
+
subclass.instance_variable_set(var, instance_variable_get(var))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Sequel::Dataset
|
136
|
+
def controller=(controller)
|
137
|
+
@controller = controller
|
138
|
+
end
|
139
|
+
|
140
|
+
def execute(sql, opts={}, &block)
|
141
|
+
if @controller
|
142
|
+
@controller.last_sql = sql
|
143
|
+
end
|
144
|
+
@db.execute(sql, {:server=>@opts[:server] || :read_only}.merge(opts), &block)
|
145
|
+
end
|
114
146
|
end
|
data/lib/kiss/rack/bench.rb
CHANGED
@@ -1,58 +1,16 @@
|
|
1
|
-
def bench(label = nil)
|
2
|
-
Rack::Bench.close_bench_item(Kernel.caller[0])
|
3
|
-
Rack::Bench.on
|
4
|
-
|
5
|
-
if label
|
6
|
-
Rack::Bench.push_bench_item(
|
7
|
-
:label => label,
|
8
|
-
:start_time => Time.now,
|
9
|
-
:start_context => Kernel.caller[0]
|
10
|
-
)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
1
|
module Rack
|
15
|
-
# Rack::Bench
|
16
|
-
#
|
17
|
-
# bench(label) starts a new timer, which ends upon the next call to
|
18
|
-
# bench, or when execution returns to Rack::Bench.
|
19
|
-
#
|
20
|
-
# bench can be called without a label to end the previous timer
|
21
|
-
# without starting a new one.
|
22
|
-
#
|
23
|
-
# Total request duration is also displayed for any request in which
|
24
|
-
# the bench function is called.
|
2
|
+
# Rack::Bench shows total request duration for any request.
|
25
3
|
class Bench
|
26
|
-
def self.on
|
27
|
-
@@bench = true
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.close_bench_item(end_context = nil)
|
31
|
-
if @@bench_items[-1] && !@@bench_items[-1][:end_time]
|
32
|
-
@@bench_items[-1][:end_time] = Time.now
|
33
|
-
@@bench_items[-1][:end_context] = end_context
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.push_bench_item(item)
|
38
|
-
@@bench_items.push(item)
|
39
|
-
end
|
40
|
-
|
41
4
|
def initialize(app)
|
42
5
|
@app = app
|
43
6
|
end
|
44
7
|
|
45
8
|
def call(env)
|
46
|
-
@@bench = false
|
47
|
-
@@bench_items = []
|
48
|
-
|
49
9
|
start_time = Time.now
|
50
10
|
code, headers, body = @app.call(env)
|
51
11
|
end_time = Time.now
|
52
12
|
|
53
|
-
|
54
|
-
Rack::Bench.close_bench_item
|
55
|
-
contents = <<-EOT
|
13
|
+
contents = <<-EOT
|
56
14
|
<style>
|
57
15
|
.kiss_bench {
|
58
16
|
text-align: left;
|
@@ -73,59 +31,16 @@ module Rack
|
|
73
31
|
text-decoration: underline;
|
74
32
|
}
|
75
33
|
</style>
|
76
|
-
EOT
|
77
|
-
|
78
|
-
contents += @@bench_items.map do |item|
|
79
|
-
start_link = context_link(item[:start_context])
|
80
|
-
end_link = context_link(item[:end_context])
|
81
|
-
|
82
|
-
<<-EOT
|
83
|
-
<div class="kiss_bench">
|
84
|
-
<tt><b>#{item[:label].gsub(/\</,'<')} duration: #{sprintf("%0.3f",item[:end_time].to_f - item[:start_time].to_f)} s</b></tt>
|
85
|
-
<small style="line-height: 105%; display: block; padding-bottom: 3px">kiss bench<br/>started at #{start_link}<br/>ended at #{end_link || 'return to kiss bench'}</small>
|
86
|
-
</div>
|
87
|
-
EOT
|
88
|
-
end.join
|
89
|
-
|
90
|
-
contents += <<-EOT
|
91
34
|
<div class="kiss_bench">
|
92
35
|
<tt><b>TOTAL request duration: #{sprintf("%0.3f",end_time.to_f - start_time.to_f)} s</b></tt>
|
93
36
|
<br><small>kiss bench request total</small>
|
94
37
|
</div>
|
95
38
|
EOT
|
96
|
-
else
|
97
|
-
contents = ''
|
98
|
-
end
|
99
39
|
|
100
40
|
body.each {|p| contents += p }
|
101
41
|
headers['Content-Length'] = contents.length.to_s
|
102
42
|
|
103
43
|
[ code, headers, contents ]
|
104
44
|
end
|
105
|
-
|
106
|
-
def absolute_path(filename)
|
107
|
-
filename = ( filename =~ /\A\// ? '' : (Dir.pwd + '/') ) + filename
|
108
|
-
end
|
109
|
-
|
110
|
-
def context_link(context)
|
111
|
-
return nil unless context
|
112
|
-
|
113
|
-
filename, line, method = context.split(/:/)
|
114
|
-
textmate_url = "txmt://open?url=file://" + h(absolute_path(filename)) + '&line=' + line
|
115
|
-
%Q(<a href="#{textmate_url}">#{filename}:#{line}</a> #{method})
|
116
|
-
end
|
117
|
-
|
118
|
-
def textmate_href(frame)
|
119
|
-
"txmt://open?url=file://" + h(absolute_path(context)).sub(/:/,'&line=')
|
120
|
-
end
|
121
|
-
|
122
|
-
def h(obj) # :nodoc:
|
123
|
-
case obj
|
124
|
-
when String
|
125
|
-
Utils.escape_html(obj).gsub(/^(\s+)/) {' ' * $1.length}
|
126
|
-
else
|
127
|
-
Utils.escape_html(obj.inspect)
|
128
|
-
end
|
129
|
-
end
|
130
45
|
end
|
131
46
|
end
|
@@ -1,23 +1,14 @@
|
|
1
1
|
module Rack
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# TextMate links to source files, as well as the last database
|
5
|
-
# query, GET/POST params, cookies, and Rack environment variables.
|
6
|
-
#
|
7
|
-
# Be careful using this on public-facing sites as it could reveal
|
8
|
-
# potentially sensitive information to malicious users.
|
2
|
+
# Deprecated; now does nothing.
|
3
|
+
# Functionality moved to Kiss#initialize (lib/kiss.rb).
|
9
4
|
|
10
5
|
class LogExceptions
|
11
6
|
def initialize(app,path)
|
12
7
|
@app = app
|
13
|
-
@@file = ::File.open(path,'w')
|
14
8
|
end
|
15
9
|
|
16
10
|
def call(env)
|
17
11
|
@app.call(env)
|
18
|
-
rescue StandardError, LoadError, SyntaxError => e
|
19
|
-
@@file.print Kiss::ExceptionReport.generate(env, e) + "\n--- End of exception report --- \n\n"
|
20
|
-
raise
|
21
12
|
end
|
22
13
|
end
|
23
14
|
end
|
data/lib/kiss/rack/show_debug.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
|
-
$debug_messages = []
|
2
|
-
def debug(object)
|
3
|
-
$debug_messages.push( [object.inspect, Kernel.caller[0]] )
|
4
|
-
object
|
5
|
-
end
|
6
|
-
|
7
1
|
module Rack
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# Be careful using this on public-facing sites as it could reveal
|
11
|
-
# potentially sensitive information to malicious users.
|
2
|
+
# Deprecated; now does nothing.
|
3
|
+
# Functionality moved to Kiss#initialize (lib/kiss.rb).
|
12
4
|
|
13
5
|
class ShowDebug
|
14
6
|
def initialize(app)
|
@@ -16,67 +8,7 @@ module Rack
|
|
16
8
|
end
|
17
9
|
|
18
10
|
def call(env)
|
19
|
-
|
20
|
-
code, headers, body = @app.call(env)
|
21
|
-
|
22
|
-
if $debug_messages.size > 0
|
23
|
-
contents = <<-EOT
|
24
|
-
<style>
|
25
|
-
.kiss_debug {
|
26
|
-
text-align: left;
|
27
|
-
padding: 3px 7px;
|
28
|
-
border: 1px solid #ebe;
|
29
|
-
border-top: 1px solid #fdf;
|
30
|
-
border-bottom: 1px solid #d6d;
|
31
|
-
background-color: #fbf;
|
32
|
-
font-size: 12px;
|
33
|
-
color: #101;
|
34
|
-
}
|
35
|
-
.kiss_debug a {
|
36
|
-
color: #707;
|
37
|
-
text-decoration: none;
|
38
|
-
}
|
39
|
-
.kiss_debug a:hover {
|
40
|
-
color: #707;
|
41
|
-
text-decoration: underline;
|
42
|
-
}
|
43
|
-
</style>
|
44
|
-
EOT
|
45
|
-
contents += $debug_messages.map do |object,context|
|
46
|
-
filename, line, method = context.split(/:/)
|
47
|
-
textmate_url = "txmt://open?url=file://" + h(absolute_path(filename)) + '&line=' + line
|
48
|
-
<<-EOT
|
49
|
-
<div class="kiss_debug">
|
50
|
-
<tt><b>#{object.gsub(/\</,'<')}</b></tt>
|
51
|
-
<br><small>kiss debug output at <a href="#{textmate_url}">#{filename}:#{line}</a> #{method}</small>
|
52
|
-
</div>
|
53
|
-
EOT
|
54
|
-
end.join
|
55
|
-
else
|
56
|
-
contents = ''
|
57
|
-
end
|
58
|
-
|
59
|
-
body.each {|p| contents += p }
|
60
|
-
headers['Content-Length'] = contents.length.to_s
|
61
|
-
|
62
|
-
[ code, headers, contents ]
|
63
|
-
end
|
64
|
-
|
65
|
-
def absolute_path(filename)
|
66
|
-
filename = ( filename =~ /\A\// ? '' : (Dir.pwd + '/') ) + filename
|
67
|
-
end
|
68
|
-
|
69
|
-
def textmate_href(frame)
|
70
|
-
"txmt://open?url=file://" + h(absolute_path(context)).sub(/:/,'&line=')
|
71
|
-
end
|
72
|
-
|
73
|
-
def h(obj) # :nodoc:
|
74
|
-
case obj
|
75
|
-
when String
|
76
|
-
Utils.escape_html(obj).gsub(/^(\s+)/) {' ' * $1.length}
|
77
|
-
else
|
78
|
-
Utils.escape_html(obj.inspect)
|
79
|
-
end
|
11
|
+
@app.call(env)
|
80
12
|
end
|
81
13
|
end
|
82
14
|
end
|
@@ -1,11 +1,6 @@
|
|
1
1
|
module Rack
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# TextMate links to source files, as well as the last database
|
5
|
-
# query, GET/POST params, cookies, and Rack environment variables.
|
6
|
-
#
|
7
|
-
# Be careful using this on public-facing sites as it could reveal
|
8
|
-
# potentially sensitive information to malicious users.
|
2
|
+
# Deprecated; now does nothing.
|
3
|
+
# Functionality moved to Kiss#initialize (lib/kiss.rb).
|
9
4
|
|
10
5
|
class ShowExceptions
|
11
6
|
def initialize(app)
|
@@ -14,14 +9,6 @@ module Rack
|
|
14
9
|
|
15
10
|
def call(env)
|
16
11
|
@app.call(env)
|
17
|
-
rescue StandardError, LoadError, SyntaxError => e
|
18
|
-
body = Kiss::ExceptionReport.generate(env, e)
|
19
|
-
[500, {
|
20
|
-
"Content-Type" => "text/html",
|
21
|
-
"Content-Length" => body.length.to_s,
|
22
|
-
"X-Kiss-Error-Type" => e.class.name,
|
23
|
-
"X-Kiss-Error-Message" => e.message.sub(/\n.*/m,'')
|
24
|
-
}, body]
|
25
12
|
end
|
26
13
|
end
|
27
14
|
end
|
data/lib/kiss/sequel_mysql.rb
CHANGED
@@ -13,8 +13,10 @@ module Sequel
|
|
13
13
|
# Fixes bug in Sequel 1.5; shouldn't be needed for Sequel 2.x
|
14
14
|
# (need to double-check, however).
|
15
15
|
def fetch_arrays(sql)
|
16
|
-
|
17
|
-
|
16
|
+
execute(sql) do |r|
|
17
|
+
while row = r.fetch_row
|
18
|
+
yield row
|
19
|
+
end
|
18
20
|
end
|
19
21
|
self
|
20
22
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Kiss
|
2
|
+
class StaticFile
|
3
|
+
def initialize(path,mime_type = nil)
|
4
|
+
@path = path
|
5
|
+
@mime_type = mime_type
|
6
|
+
end
|
7
|
+
|
8
|
+
def finish
|
9
|
+
ext = File.extname(@path)[1..-1]
|
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]
|
17
|
+
else
|
18
|
+
return [404, {"Content-Type" => "text/plain"},
|
19
|
+
["File not found: #{@path}\n"]]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def each
|
24
|
+
File.open(@path, "rb") { |file|
|
25
|
+
while part = file.read(8192)
|
26
|
+
yield part
|
27
|
+
end
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|