gin 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ class Gin::Response < Rack::Response
2
+
3
+ NO_HEADER_STATUSES = [100, 101, 204, 205, 304].freeze #:nodoc:
4
+ H_CTYPE = "Content-Type".freeze #:nodoc:
5
+ H_CLENGTH = "Content-Length".freeze #:nodoc:
6
+
7
+ attr_accessor :status
8
+ attr_reader :body
9
+
10
+ def body= value
11
+ value = value.body while Rack::Response === value
12
+ @body = value.respond_to?(:each) ? value : [value.to_s]
13
+ @body
14
+ end
15
+
16
+
17
+ def finish
18
+ body_out = body
19
+ header[H_CTYPE] ||= 'text/html;charset=UTF-8'
20
+
21
+ if NO_HEADER_STATUSES.include?(status.to_i)
22
+ header.delete H_CTYPE
23
+ header.delete H_CLENGTH
24
+
25
+ if status.to_i > 200
26
+ close
27
+ body_out = []
28
+ end
29
+ end
30
+
31
+ update_content_length
32
+
33
+ [status.to_i, header, body_out]
34
+ end
35
+
36
+
37
+ private
38
+
39
+ def update_content_length
40
+ if header[H_CTYPE] && !header[H_CLENGTH]
41
+ case body
42
+ when Array
43
+ header[H_CLENGTH] = body.inject(0) do |l, p|
44
+ l + Rack::Utils.bytesize(p)
45
+ end.to_s
46
+ when File
47
+ header[H_CLENGTH] = body.size.to_s
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,222 @@
1
+ class Gin::Router
2
+
3
+ class PathArgumentError < Gin::Error; end
4
+
5
+ class Mount
6
+ DEFAULT_ACTION_MAP = {
7
+ :index => %w{get /},
8
+ :show => %w{get /:id},
9
+ :new => %w{get /new},
10
+ :create => %w{post /:id},
11
+ :edit => %w{get /:id/edit},
12
+ :update => %w{put /:id},
13
+ :destroy => %w{delete /:id}
14
+ }
15
+
16
+ VERBS = %w{get post put delete head options trace}
17
+
18
+ VERBS.each do |verb|
19
+ define_method(verb){|action, *args| add(verb, action, *args)}
20
+ end
21
+
22
+
23
+ def initialize ctrl, base_path, sep="/", &block
24
+ @sep = sep
25
+ @ctrl = ctrl
26
+ @routes = []
27
+ @actions = []
28
+ @base_path = base_path.split(@sep)
29
+
30
+ instance_eval(&block) if block_given?
31
+ defaults unless block_given?
32
+ end
33
+
34
+
35
+ # Create restful routes if they aren't taken already.
36
+ def defaults restful_only=false
37
+ (@ctrl.actions - @actions).each do |action|
38
+ verb, path = DEFAULT_ACTION_MAP[action]
39
+ verb, path = ['get', "/#{action}"] if !restful_only && verb.nil?
40
+
41
+ add(verb, action, path) unless verb.nil? ||
42
+ @routes.any?{|(r,n,(c,a,p))| r == make_route(verb, path)[0] }
43
+ end
44
+ end
45
+
46
+
47
+ def any action, path=nil
48
+ VERBS.each{|verb| send verb, action, path}
49
+ end
50
+
51
+
52
+ def make_route verb, path
53
+ param_keys = []
54
+ route = [verb].concat @base_path
55
+ route.concat path.split(@sep)
56
+ route.delete_if{|part| part.empty?}
57
+
58
+ route.map! do |part|
59
+ if part[0] == ":"
60
+ param_keys << part[1..-1]
61
+ "%s"
62
+ else
63
+ part
64
+ end
65
+ end
66
+
67
+ [route, param_keys]
68
+ end
69
+
70
+
71
+ def add verb, action, *args
72
+ path = args.shift if String === args[0]
73
+ name = args.shift.to_sym if args[0]
74
+
75
+ path ||= action.to_s
76
+ name ||= :"#{action}_#{@ctrl.controller_name}"
77
+
78
+ route, param_keys = make_route(verb, path)
79
+ @routes << [route, name, [@ctrl, action, param_keys]]
80
+ @actions << action.to_sym
81
+ end
82
+
83
+
84
+ def each_route &block
85
+ @routes.each{|(route, name, value)| block.call(route, name, value) }
86
+ end
87
+ end
88
+
89
+
90
+ class Node
91
+ attr_accessor :value
92
+
93
+ def initialize
94
+ @children = {}
95
+ end
96
+
97
+ def [] key
98
+ @children[key]
99
+ end
100
+
101
+ def add_child key, val=nil
102
+ @children[key] ||= Node.new
103
+ @children[key].value = val unless val.nil?
104
+ end
105
+ end
106
+
107
+
108
+ def initialize separator="/" # :nodoc:
109
+ @sep = separator
110
+ @routes_tree = Node.new
111
+ @routes_lookup = {}
112
+ end
113
+
114
+
115
+ ##
116
+ # Add a Controller to the router with a base path.
117
+
118
+ def add ctrl, base_path=nil, &block
119
+ base_path ||= ctrl.controller_name
120
+
121
+ mount = Mount.new(ctrl, base_path, @sep, &block)
122
+
123
+ mount.each_route do |route_ary, name, val|
124
+ curr_node = @routes_tree
125
+
126
+ route_ary.each do |part|
127
+ curr_node.add_child part
128
+ curr_node = curr_node[part]
129
+ end
130
+
131
+ curr_node.value = val
132
+ route = [route_ary[0], "/" << route_ary[1..-1].join(@sep), val[2]]
133
+
134
+ @routes_lookup[name] = route if name
135
+ @routes_lookup[val[0..1]] = route
136
+ end
137
+ end
138
+
139
+
140
+ ##
141
+ # Check if a Controller and action combo has a route.
142
+
143
+ def has_route? ctrl, action
144
+ !!@routes_lookup[[ctrl, action]]
145
+ end
146
+
147
+
148
+ ##
149
+ # Yield every Controller, action, route combination.
150
+
151
+ def each_route &block
152
+ @routes_lookup.each do |key,route|
153
+ next unless Array === key
154
+ block.call route, key[0], key[1]
155
+ end
156
+ end
157
+
158
+
159
+ ##
160
+ # Get the path to the given Controller and action combo or route name,
161
+ # provided with the needed params. Routes with missing path params will raise
162
+ # MissingParamError. Returns a String starting with "/".
163
+ #
164
+ # path_to FooController, :show, :id => 123
165
+ # #=> "/foo/123"
166
+ #
167
+ # path_to :show_foo, :id => 123
168
+ # #=> "/foo/123"
169
+
170
+ def path_to *args
171
+ key = Class === args[0] ? args.slice!(0..1) : args.shift
172
+ verb, route, param_keys = @routes_lookup[key]
173
+ raise PathArgumentError, "No route for #{Array(key).join("#")}" unless route
174
+
175
+ params = (args.pop || {}).dup
176
+
177
+ route = route.dup
178
+ route = route % param_keys.map do |k|
179
+ params.delete(k) || params.delete(k.to_sym) ||
180
+ raise(PathArgumentError, "Missing param #{k}")
181
+ end unless param_keys.empty?
182
+
183
+ route << "?#{Gin.build_query(params)}" unless params.empty?
184
+
185
+ route
186
+ end
187
+
188
+
189
+ ##
190
+ # Takes a path and returns an array of 3 items:
191
+ # [controller_class, action_symbol, path_params_hash]
192
+ # Returns nil if no match was found.
193
+
194
+ def resources_for http_verb, path
195
+ param_vals = []
196
+ curr_node = @routes_tree[http_verb.to_s.downcase]
197
+
198
+ path.scan(%r{/([^/]+|$)}) do |(key)|
199
+ next if key.empty?
200
+
201
+ if curr_node[key]
202
+ curr_node = curr_node[key]
203
+
204
+ elsif curr_node["%s"]
205
+ param_vals << key
206
+ curr_node = curr_node["%s"]
207
+
208
+ else
209
+ return
210
+ end
211
+ end
212
+
213
+ return unless curr_node.value
214
+ rsc = curr_node.value.dup
215
+
216
+ rsc[-1] = param_vals.empty? ?
217
+ Hash.new :
218
+ rsc[-1].inject({}){|h, name| h[name] = param_vals.shift; h}
219
+
220
+ rsc
221
+ end
222
+ end
@@ -0,0 +1,56 @@
1
+ ##
2
+ # Taken from Sinatra.
3
+ #
4
+ # Class of the response body in case you use #stream.
5
+ #
6
+ # Three things really matter: The front and back block (back being the
7
+ # block generating content, front the one sending it to the client) and
8
+ # the scheduler, integrating with whatever concurrency feature the Rack
9
+ # handler is using.
10
+ #
11
+ # Scheduler has to respond to defer and schedule.
12
+
13
+ class Gin::Stream
14
+ def self.schedule(*) yield end
15
+ def self.defer(*) yield end
16
+
17
+
18
+ def initialize(scheduler = self.class, keep_open = false, &back)
19
+ @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
20
+ @callbacks, @closed = [], false
21
+ end
22
+
23
+ def close
24
+ return if @closed
25
+ @closed = true
26
+ @scheduler.schedule { @callbacks.each { |c| c.call }}
27
+ end
28
+
29
+ def each(&front)
30
+ @front = front
31
+ @scheduler.defer do
32
+ begin
33
+ @back.call(self)
34
+ rescue Exception => e
35
+ @scheduler.schedule { raise e }
36
+ end
37
+ close unless @keep_open
38
+ end
39
+ end
40
+
41
+ def <<(data)
42
+ @scheduler.schedule { @front.call(data.to_s) }
43
+ self
44
+ end
45
+
46
+ def callback(&block)
47
+ return yield if @closed
48
+ @callbacks << block
49
+ end
50
+
51
+ alias errback callback
52
+
53
+ def closed?
54
+ @closed
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Bad Request</title>
5
+ <link rel="stylesheet" type="text/css" href="/gin.css"/>
6
+ </head>
7
+ <body>
8
+ <div class="canvas error">
9
+ <h1>Bad Request</h1>
10
+ <p>The server could not process your request as formed.</p>
11
+ </div>
12
+ </body>
13
+ </html>
14
+
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Page Not Found</title>
5
+ <link rel="stylesheet" type="text/css" href="/gin.css"/>
6
+ </head>
7
+ <body>
8
+ <div class="canvas error">
9
+ <h1>Page Not Found</h1>
10
+ <p>The page you requested does not exist.</p>
11
+ </div>
12
+ </body>
13
+ </html>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Server Error</title>
5
+ <link rel="stylesheet" type="text/css" href="/gin.css"/>
6
+ </head>
7
+ <body>
8
+ <div class="canvas error">
9
+ <h1>Server Error</h1>
10
+ <p>An unexpected error occurred. We have been notified, of this issue.<br/>
11
+ Please check again later.</p>
12
+ </div>
13
+ </body>
14
+ </html>
@@ -0,0 +1,38 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>%s</title>
5
+ <link rel="stylesheet" type="text/css" href="/gin.css"/>
6
+ </head>
7
+ <body>
8
+ <div class="canvas">
9
+ <img src="/gin_sm.png" class="logo"/>
10
+ <h1>%s</h1>
11
+ <p>%s</p>
12
+ <div id="apptrace" class="trace">
13
+ <a href="#" onclick="show_fulltrace()">Show Full Trace</a>
14
+ %s
15
+ </div>
16
+ <div id="fulltrace" class="trace">
17
+ <a href="#" onclick="hide_fulltrace()">Hide Full Trace</a>
18
+ %s
19
+ </div>
20
+ </div>
21
+ <script>
22
+ var apptrace = document.getElementById('apptrace');
23
+ var fulltrace = document.getElementById('fulltrace');
24
+
25
+ function show_fulltrace(){
26
+ apptrace.style.display = "none";
27
+ fulltrace.style.display = "block";
28
+ return true;
29
+ }
30
+
31
+ function hide_fulltrace(){
32
+ apptrace.style.display = "block";
33
+ fulltrace.style.display = "none";
34
+ return true;
35
+ }
36
+ </script>
37
+ </body>
38
+ </html>
Binary file
@@ -0,0 +1,61 @@
1
+
2
+ body {
3
+ font-family: Helvetica Neue, Helvetica, Sans-Serif;
4
+ background-color: #eee;
5
+ margin: 50px;
6
+ }
7
+
8
+ .canvas {
9
+ border: 1px solid #ccc;
10
+ padding: 12px;
11
+ padding-top: 10px;
12
+ border-radius: 15px;
13
+ box-shadow: 3px 3px 10px #aaa;
14
+ background-color: #fff;
15
+ margin: 0 auto;
16
+ margin-top: 50px;
17
+ max-width: 1200px;
18
+ }
19
+
20
+ .canvas h1 {
21
+ margin-top: 0;
22
+ border-bottom: 1px solid #ccc;
23
+ }
24
+
25
+ .canvas pre {
26
+ clear: both;
27
+ margin: 15px;
28
+ padding: 15px;
29
+ border: 1px solid #ccc;
30
+ overflow: scroll;
31
+ background-color: #efefef;
32
+ border-radius: 10px;
33
+ box-shadow: 3px 3px -10px #aaa;
34
+ }
35
+
36
+ .error {
37
+ width: 500px;
38
+ }
39
+
40
+ .trace a {
41
+ float: right;
42
+ color: #687c93;
43
+ font-size: 12px;
44
+ text-decoration: none;
45
+ position: relative;
46
+ top: -75px;
47
+ margin-right: 15px;
48
+ }
49
+
50
+ #fulltrace {
51
+ display: none;
52
+ }
53
+
54
+ img.logo {
55
+ float: left;
56
+ position: relative;
57
+ top: -35px;
58
+ left: -35px;
59
+ margin-bottom: -50px;
60
+ margin-right: -25px;
61
+ }