gin 0.0.0 → 1.0.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.
@@ -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
+ }