casual-api 3.0.2 → 3.0.3

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 (6) hide show
  1. checksums.yaml +4 -4
  2. data/lib/casual-api.rb +1 -0
  3. data/lib/casual.rb +108 -85
  4. data/lib/config.ru +1 -2
  5. data/lib/dsl.rb +238 -189
  6. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1552be82e1bda4452edefb02d6aa9bcf80a3acc
4
- data.tar.gz: 72e38979a147b0433d378a5c2a622af96e1624ca
3
+ metadata.gz: 45222dcf0822e0dd2a5435627f91147a2bc4a78e
4
+ data.tar.gz: 8ee0ef74b92e226dec8e3a307ffd23a9bc297e01
5
5
  SHA512:
6
- metadata.gz: abfc8346e7c6caba58546df5d88fc242cd3ff37d7768c215d975ecc1f3bf3c0f4cdc5f2fcd17c8f2d630ec3c1df14e1a3b427eed17e1de2d8b21ed556809c0a9
7
- data.tar.gz: 1f4471ba83ee2893bae942cee31911ca86452c93cb317f432a187f844066e8e6b6351a1afdb84e43a5ecf203fbd972fe9ef1e711cad4dc19dbf4a2a85f376186
6
+ metadata.gz: 09d85af70129a29def81eb53d2a16dd106ede9e2470b7b84aca70effc69747bfb22f45012ffb95c7868d6c7476581821cd46ec3046c74554f1d6564903c785c2
7
+ data.tar.gz: 8a25f7ee7cf1a01882557418dcc3259b21a8aefe1582fb59dc481246e57cff7908fa29ef4cb2c6d53a000c771732678e9dda39a372384f5c24b8babda5ffb6b7
data/lib/casual-api.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative './dsl.rb'
data/lib/casual.rb CHANGED
@@ -1,102 +1,125 @@
1
1
  require 'rack'
2
+ require 'tempfile'
2
3
  require_relative './dsl.rb'
3
4
 
4
- class ChildProcess
5
- def initialize(action, parameters, session)
6
- @file_descriptors = []
7
- @action = action
8
- @params = parameters
9
- @session = session
10
- end
5
+ module CasualAPI
6
+ class ChildProcess
7
+ def initialize(action, parameters, session)
8
+ @action = action
9
+ @params = parameters
10
+ @session = session
11
+ end
12
+
13
+ def execute
14
+ check_parameter
15
+ obj = Action.fire @action, @params, @session
16
+ {
17
+ session: obj.session,
18
+ status_code: obj.status_code,
19
+ content_type: obj.content_type,
20
+ body: obj.body,
21
+ }
22
+ end
11
23
 
12
- def execute
13
- obj = Action.fire @action, @params, @session
14
- {
15
- session: obj.session,
16
- status_code: obj.status_code,
17
- content_type: obj.content_type,
18
- body: obj.body,
19
- }
24
+ private
25
+ def check_parameter
26
+ check_satisfy_required_parameters
27
+ check_upload_file
28
+ end
29
+ def check_satisfy_required_parameters
30
+ unless @action[:parameters].all?{|k,v|@params[k] || !v[:required]}
31
+ raise InvalidParameterError, "Invalid parameter(s)"
32
+ end
33
+ end
34
+ def check_upload_file
35
+ unless @action[:parameters].all?{|k,v|
36
+ next false if v[:is_file] ^ @params[k].is_a?(Hash)
37
+ next false if v[:as_file] && @params[k].is_a?(Hash)
38
+ true
39
+ }
40
+ raise InvalidParameterError, "Invalid parameter(s)"
41
+ end
42
+ end
20
43
  end
21
- end
22
44
 
23
45
 
24
- # RackApp
25
- class Casual
26
- def initialize(prefix, file, mode)
27
- @prefix = prefix
28
- @file = file
29
- @mode = mode
30
- end
46
+ # RackApp
47
+ class Casual
48
+ def initialize(prefix, file, mode)
49
+ @prefix = prefix
50
+ @file = file
51
+ @mode = mode
52
+ end
31
53
 
32
- def call(env)
33
- @env = env
34
- @req = Rack::Request.new(env)
35
- fire_action
36
- end
54
+ def call(env)
55
+ @env = env
56
+ @req = Rack::Request.new(env)
57
+ fire_action
58
+ end
37
59
 
38
- private
39
- def fire_action
40
- actions = parse_actions
41
- action = detect_action actions
42
- if action
43
- begin
44
- cp = ChildProcess.new(action, parameters, session)
45
- result = cp.execute
46
- @req.session['casual.session'] = result[:session]
47
- res = Rack::Response.new(){|r|
48
- r.status = result[:status_code]
49
- r.write result[:body]
50
- r['Content-Type'] = map_content_type(result[:content_type])
51
- }
52
- res.finish
53
- rescue CommandError => ex
54
- return [ex.status_code,
55
- { 'Content-Type' => map_content_type(ex.content_type) },
56
- [ex.message]]
57
- rescue => ex
58
- [500, {}, ["Server Error"]]
60
+ private
61
+ def fire_action
62
+ actions = parse_actions
63
+ action = detect_action actions
64
+ if action
65
+ begin
66
+ cp = ChildProcess.new(action, parameters, session)
67
+ result = cp.execute
68
+ @req.session['casual.session'] = result[:session]
69
+ res = Rack::Response.new(){|r|
70
+ r.status = result[:status_code]
71
+ r.write result[:body]
72
+ r['Content-Type'] = map_content_type(result[:content_type])
73
+ }
74
+ res.finish
75
+ rescue CommandError => ex
76
+ return [ex.status_code,
77
+ { 'Content-Type' => map_content_type(ex.content_type) },
78
+ [ex.message]]
79
+ rescue => ex
80
+ [500, {}, ["Server Error"]]
81
+ end
82
+ else
83
+ [404, {}, []]
59
84
  end
60
- else
61
- [404, {}, []]
62
85
  end
63
- end
64
86
 
65
- def map_content_type(c)
66
- {
67
- txt: "text/plain;charset=utf-8",
68
- html: "text/html;charset=utf-8",
69
- }[c.to_sym] || c
70
- end
87
+ def map_content_type(c)
88
+ {
89
+ txt: "text/plain;charset=utf-8",
90
+ html: "text/html;charset=utf-8",
91
+ }[c.to_sym] || c
92
+ end
71
93
 
72
- def parse_actions
73
- if @mode == "development" || !@actions
74
- @actions = DSL.parse @file
75
- else
76
- @actions
94
+ def parse_actions
95
+ if @mode == "development" || !@actions
96
+ @actions = DSL.parse @file
97
+ else
98
+ @actions
99
+ end
77
100
  end
78
- end
79
101
 
80
- def detect_action actions
81
- path = request_path
82
- _method = request_method
83
- actions.detect{|act|
84
- act[:method] == _method && make_path(act) == path
85
- }
86
- end
87
- def make_path action
88
- @prefix + (action[:domain] + action[:names]).compact.map{|p|p.to_s}.join('/')
89
- end
90
- def parameters
91
- @req.params.map{|k,v| {k.to_sym => v} }.inject({}, :merge)
92
- end
93
- def session
94
- @req.session['casual.session'] || {}
95
- end
96
- def request_method
97
- @req.request_method.downcase.to_sym
98
- end
99
- def request_path
100
- @req.path
102
+ def detect_action actions
103
+ path = request_path
104
+ _method = request_method
105
+ actions.detect{|act|
106
+ act[:method] == _method && make_path(act) == path
107
+ }
108
+ end
109
+ def make_path action
110
+ @prefix + (action[:domain] + action[:names]).compact.map{|p|p.to_s}.join('/')
111
+ end
112
+ def parameters
113
+ @req.params.map{|k,v| {k.to_sym => v} }.inject({}, :merge)
114
+ end
115
+ def session
116
+ @req.session['casual.session'] || {}
117
+ end
118
+ def request_method
119
+ @req.request_method.downcase.to_sym
120
+ end
121
+ def request_path
122
+ @req.path
123
+ end
101
124
  end
102
125
  end
data/lib/config.ru CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rack'
2
2
  require 'yaml'
3
-
4
3
  require_relative './casual.rb'
5
4
 
6
5
  config = open("config.yaml"){|f|
@@ -17,7 +16,7 @@ use Rack::Session::Pool,
17
16
  (config["routes"] || {}).each{|k,v|
18
17
  map(k)do
19
18
  run Rack::Cascade.new [
20
- Casual.new(k, v, config["mode"])
19
+ CasualAPI::Casual.new(k, v, config["mode"])
21
20
  ]
22
21
  end
23
22
  }
data/lib/dsl.rb CHANGED
@@ -1,228 +1,277 @@
1
1
  require 'pp'
2
2
  require 'securerandom'
3
+ require 'tempfile'
3
4
 
4
- module BodyWriter
5
- def self.extended obj
6
- obj.instance_variable_set(:@body, "")
7
- end
8
- def write str
9
- @body << str.to_s
10
- end
11
- def print str
12
- @body << str.to_s
13
- end
14
- def puts str
15
- @body << str.to_s << "\n"
16
- end
17
- def printf *p
18
- @body << (sprintf *p)
19
- end
20
- def command cmd
21
- write `#{cmd.to_s}`
5
+ module CasualAPI
6
+ module BodyWriter
7
+ def self.extended obj
8
+ obj.instance_variable_set(:@body, "")
9
+ end
10
+ def write str
11
+ @body << str.to_s
12
+ end
13
+ def print str
14
+ @body << str.to_s
15
+ end
16
+ def puts str
17
+ @body << str.to_s << "\n"
18
+ end
19
+ def printf *p
20
+ @body << (sprintf *p)
21
+ end
22
+ def command cmd
23
+ write `#{cmd.to_s}`
24
+ end
25
+ def body
26
+ @body
27
+ end
28
+ def body=(value)
29
+ @body = value
30
+ end
22
31
  end
23
- def body
24
- @body
32
+
33
+ class CommandError < StandardError
34
+ def content_type; :txt; end
35
+ def status_code; 400; end
25
36
  end
26
- def body=(value)
27
- @body = value
37
+ class InvalidParameterError < CommandError
28
38
  end
29
- end
30
39
 
31
- class CommandError < StandardError
32
- def content_type; :txt; end
33
- def status_code; 400; end
34
- end
35
- class InvalidParameterError < CommandError
36
- end
40
+ module Action
41
+ def self.extended obj
42
+ obj.instance_variable_set(:@session, {})
43
+ obj.instance_variable_set(:@content_type, nil)
44
+ obj.instance_variable_set(:@status_code, 200)
45
+ obj.instance_variable_set(:@tempfiles, [])
46
+ end
37
47
 
38
- module Action
39
- def self.extended obj
40
- obj.instance_variable_set(:@session, {})
41
- obj.instance_variable_set(:@content_type, nil)
42
- obj.instance_variable_set(:@status_code, 200)
43
- obj.instance_variable_set(:@tempfiles, [])
44
- end
48
+ def invalid_parameter *params
49
+ raise InvalidParameterError, "Invalid parameter(s): #{params.map{|p|p.to_s}.join(',')}"
50
+ end
45
51
 
46
- def invalid_parameter *params
47
- raise InvalidParameterError, "Invalid parameter(s): #{params.map{|p|p.to_s}.join(',')}"
48
- end
52
+ def file? file
53
+ file.is_a? Hash
54
+ end
49
55
 
50
- def file? file
51
- file.is_a? Hash
52
- end
56
+ def tempfile file
57
+ Action.tempfile self, file
58
+ end
53
59
 
54
- def tempfile file
55
- if file.is_a? Hash
56
- file[:tempfile].path
57
- else
58
- f = Tempfile.open(SecureRandom.hex(4))
59
- f.write file
60
- f.flush
61
- @tempfiles.push f
62
- f.path
60
+ def session_clear
61
+ session.clear
62
+ session = nil
63
63
  end
64
- end
65
64
 
66
- def session_clear
67
- session.clear
68
- session = nil
69
- end
65
+ def session; @session; end
66
+ def session=(value); @session = value; end
67
+ def content_type; @content_type; end
68
+ def tempfiles; @tempfiles; end
69
+ def status_code; @status_code; end
70
+ def content_type=(type); @content_type = type; end
71
+ def status_code=(code); @status_code = code; end
70
72
 
71
- def session; @session; end
72
- def session=(value); @session = value; end
73
- def content_type; @content_type; end
74
- def tempfiles; @tempfiles; end
75
- def status_code; @status_code; end
76
- def content_type=(type); @content_type = type; end
77
- def status_code=(code); @status_code = code; end
78
-
79
-
80
- def self.fire(action, params, session)
81
- obj = Object
82
- obj.extend Action
83
- obj.extend BodyWriter
84
- obj.content_type = action[:content_type] || :txt
85
- obj.session = session
86
- ps = action[:action].parameters.map{|_, name|
87
- {name => params[name]}
88
- }.inject({}, :merge)
89
- result = execute(obj, ps, action[:action])
90
- unless result[:executed]
91
- raise InvalidParameterError, "Not given required parameter(s)"
92
- end
93
- obj.tempfiles.each{|f| f.close }
94
- obj
95
- end
96
73
 
97
- private
98
- def self.execute(obj, ps, block)
99
- vs = block.parameters.map{|_,n|ps[n]}.inject([[],true]){|(acc,b),v|
100
- (b && v) ? [acc+[v],b] : [acc,false]
101
- }[0]
102
- if block.arity <= vs.length
103
- result = obj.instance_exec(*vs, &block)
104
- { executed: true, result: result }
105
- else
106
- { executed: false }
74
+ def self.fire(action, params, session)
75
+ obj = Object
76
+ obj.extend Action
77
+ obj.extend BodyWriter
78
+ obj.content_type = action[:content_type] || :txt
79
+ obj.session = session
80
+ ps = action[:action].parameters.map{|_, name|
81
+ {name => params[name]}
82
+ }.inject({}, :merge)
83
+ execute(obj, ps, action)
84
+ obj.tempfiles.each{|f| f.close }
85
+ obj
107
86
  end
108
- end
109
87
 
110
- end
88
+ private
89
+ def self.execute(obj, ps, action)
90
+ ps = to_as_file(obj, ps, action)
91
+ ps = to_tempfile(obj, ps, action)
92
+ vs = action[:parameters].map{|k,v|ps[k]}
93
+ obj.instance_exec(*vs, &(action[:action]))
94
+ end
111
95
 
112
- module Annotation
113
- def file *files
114
- @context_stack.last[:files] ||= []
115
- @context_stack.last[:files].push(*files)
116
- end
117
- def description(param1, param2=nil)
118
- @context_stack.last[:description] ||= {}
119
- if param2
120
- @context_stack.last[:description][:params] ||= {}
121
- @context_stack.last[:description][:params][param1] = param2
122
- else
123
- @context_stack.last[:description][:path] = param1
96
+ def self.to_as_file(obj, ps, action)
97
+ action[:parameters].select{|_,v|v[:as_file]}.each{|k,_|
98
+ f = Tempfile.open(k.to_s)
99
+ f.write ps[k]
100
+ f.close
101
+ f.open
102
+ obj.tempfiles.push f
103
+ ps[k] = {tempfile: f}
104
+ }
105
+ ps
106
+ end
107
+ def self.to_tempfile(obj, ps, action)
108
+ action[:parameters].select{|_,v|v[:tempfile]}.each{|k,_|
109
+ ps[k] = tempfile obj, ps[k]
110
+ }
111
+ ps
112
+ end
113
+ def self.tempfile(obj,file)
114
+ if file.is_a? Hash
115
+ file[:tempfile].path
116
+ else
117
+ f = Tempfile.open(SecureRandom.hex(4))
118
+ f.write file
119
+ f.flush
120
+ f.close
121
+ f.open
122
+ obj.tempfiles.push f
123
+ f.path
124
+ end
124
125
  end
125
126
  end
126
- def clear_annotaion
127
- @context_stack.last.delete(:files)
128
- @context_stack.last.delete(:description)
129
- end
130
- end
131
127
 
132
- module ExtExpression
133
- def content_type type
134
- @context_stack.last[:content_type] = type
135
- end
136
- end
128
+ module Annotation
129
+ def is_file *files
130
+ @context_stack.last[:is_file] ||= []
131
+ @context_stack.last[:is_file].push(*files)
132
+ end
133
+ def as_file *files
134
+ @context_stack.last[:as_file] ||= []
135
+ @context_stack.last[:as_file].push(*files)
136
+ end
137
+ def tempfile *files
138
+ @context_stack.last[:tempfile] ||= []
139
+ @context_stack.last[:tempfile].push(*files)
140
+ end
137
141
 
138
- module SyntaxSugar
139
- def get(*names, &block); path(:get, *names, &block); end
140
- def post(*names, &block); path(:post, *names, &block); end
141
-
142
- def command(*names, cmd)
143
- eval(replace :get, names, "write `#{cmd}`")
142
+ def description(param1, param2=nil)
143
+ @context_stack.last[:description] ||= {}
144
+ if param2
145
+ @context_stack.last[:description][:params] ||= {}
146
+ @context_stack.last[:description][:params][param1] = param2
147
+ else
148
+ @context_stack.last[:description][:path] = param1
149
+ end
150
+ end
151
+ def clear_annotaion
152
+ @context_stack.last.delete(:is_file)
153
+ @context_stack.last.delete(:as_file)
154
+ @context_stack.last.delete(:tempfile)
155
+ @context_stack.last.delete(:description)
156
+ end
144
157
  end
145
158
 
146
- def text(*names, text)
147
- eval(replace :get, names, "write \"#{text}\"")
159
+ module ExtExpression
160
+ def content_type type
161
+ @context_stack.last[:content_type] = type
162
+ end
148
163
  end
149
164
 
150
- def get_command(*names, cmd); command(*names, cmd); end
151
- def post_command(*names, cmd)
152
- eval(replace :post, names, "write `#{cmd}`")
153
- end
165
+ module SyntaxSugar
166
+ def get(*names, &block); path(:get, *names, &block); end
167
+ def post(*names, &block); path(:post, *names, &block); end
168
+
169
+ def command(*names, cmd)
170
+ eval(replace :get, names, "write `#{cmd}`")
171
+ end
154
172
 
155
- def get_text(*names, text); text(*names, text); end
156
- def post_text(*names, text)
157
- eval(replace :post, names, "write \"#{text}\"")
158
- end
173
+ def text(*names, text)
174
+ eval(replace :get, names, "write \"#{text}\"")
175
+ end
176
+
177
+ def get_command(*names, cmd); command(*names, cmd); end
178
+ def post_command(*names, cmd)
179
+ eval(replace :post, names, "write `#{cmd}`")
180
+ end
159
181
 
160
- private
161
- def replace meth, names, cmd,
162
- ps = cmd.scan(/\$\{(?<p>[^\}]*)\}/).flatten
163
- fs = cmd.scan(/\$\<(?<p>[^\}]*)\>/).flatten
164
- c = ps.inject(cmd){|acc,c|
165
- acc.gsub /\$\{#{c}\}/, "\#{#{c}}"
166
- }
167
- c = fs.inject(c){|acc,c|
168
- acc.gsub /\$\<#{c}\>/, "\#{#{c}}"
169
- }
170
- filecheck = fs.inject(""){|acc,f|
171
- acc << "invalid_parameter :#{f} unless file? #{f}\n"
172
- acc << "#{f} = tempfile #{f}\n"
173
- }
174
- args = (ps + fs).join(',')
182
+ def get_text(*names, text); text(*names, text); end
183
+ def post_text(*names, text)
184
+ eval(replace :post, names, "write \"#{text}\"")
185
+ end
186
+
187
+ private
188
+ def replace meth, names, cmd
189
+ ps = cmd.scan(/\$\{(?<p>[^:\}]*)\}/).flatten
190
+ fs = cmd.scan(/\$\{file:(?<p>[^:\}]*)\}/).flatten
191
+ c = ps.inject(cmd){|acc,c|
192
+ acc.gsub /\$\{#{c}\}/, "\#{#{c}}"
193
+ }
194
+ c = fs.inject(c){|acc,c|
195
+ acc.gsub /\$\{file:#{c}\}/, "\#{#{c}}"
196
+ }
197
+ is_file_annotation = "is_file " + fs.map{|f|":#{f}"}.join(',')
198
+ tempfile_annotation = "tempfile " + fs.map{|f|":#{f}"}.join(',')
199
+ args = (ps + fs).join(',')
175
200
  <<PROC
201
+ #{is_file_annotation}
202
+ #{tempfile_annotation}
176
203
  #{meth.to_s}(#{names.map{|n|":#{n.to_s}"}.join(',')}) do | #{args} |
177
- #{filecheck}#{c}
204
+ #{c}
178
205
  end
179
206
  PROC
207
+ end
180
208
  end
181
- end
182
209
 
183
210
 
184
- module DSL
185
- def self.extended obj
186
- # add stacks and a context to an extended object.
187
- obj.instance_variable_set(:@action_stack, [])
188
- obj.instance_variable_set(:@domain_stack, [])
189
- obj.instance_variable_set(:@context_stack, [{}])
190
- obj.extend Action
191
- obj.extend Annotation
192
- obj.extend ExtExpression
193
- obj.extend SyntaxSugar
194
- end
195
- def action_stack
196
- @action_stack
197
- end
211
+ module DSL
212
+ def self.extended obj
213
+ # add stacks and a context to an extended object.
214
+ obj.instance_variable_set(:@action_stack, [])
215
+ obj.instance_variable_set(:@domain_stack, [])
216
+ obj.instance_variable_set(:@context_stack, [{}])
217
+ obj.extend Action
218
+ obj.extend Annotation
219
+ obj.extend ExtExpression
220
+ obj.extend SyntaxSugar
221
+ end
222
+ def action_stack
223
+ @action_stack
224
+ end
198
225
 
199
- def domain(*path , &block)
200
- # annotations don't effect over domain
201
- clear_annotaion
202
- @domain_stack.push *path
203
- @context_stack.push(@context_stack.last.clone)
204
- block.call
205
- @context_stack.pop
206
- @domain_stack.pop path.length
207
- # annotations don't effect over domain
208
- clear_annotaion
209
- end
226
+ def domain(*path , &block)
227
+ # annotations don't effect over domain
228
+ clear_annotaion
229
+ @domain_stack.push *path
230
+ @context_stack.push(@context_stack.last.clone)
231
+ block.call
232
+ @context_stack.pop
233
+ @domain_stack.pop path.length
234
+ # annotations don't effect over domain
235
+ clear_annotaion
236
+ end
210
237
 
211
- def path(method, *names, &block)
212
- meta = @context_stack.last.clone
213
- meta[:method] = method
214
- meta[:names] = names
215
- meta[:domain] = @domain_stack.clone
216
- meta[:action] = block
217
- clear_annotaion # annotations effect only a path.
218
- @action_stack.push meta
219
- end
220
-
221
- def self.parse(file)
222
- obj = Object.new
223
- obj.extend DSL
224
- obj.instance_eval(open(file){|f|f.read})
225
- obj.action_stack
226
- end
238
+ def path(method, *names, &block)
239
+ meta = @context_stack.last.clone
240
+ meta[:method] = method
241
+ meta[:names] = names
242
+ meta[:domain] = @domain_stack.clone
243
+ meta[:action] = block
244
+ clear_annotaion # annotations effect only a path.
245
+ @action_stack.push meta
246
+ end
247
+
248
+ def self.parse(file)
249
+ obj = Object.new
250
+ obj.extend DSL
251
+ obj.instance_eval(open(file){|f|f.read})
252
+ obj.action_stack.map{|x| normalize x }
253
+ end
227
254
 
255
+ def self.normalize hs
256
+ description = hs[:description] || {}
257
+ description[:params] ||= {}
258
+ parameters = hs[:action].parameters.map.with_index{|(_,n),i|
259
+ { n => {
260
+ required: i < hs[:action].arity,
261
+ is_file: (hs[:is_file]||[]).include?(n),
262
+ as_file: (hs[:as_file]||[]).include?(n),
263
+ tempfile: (hs[:tempfile]||[]).include?(n),
264
+ }
265
+ }
266
+ }.inject({}, :merge)
267
+ {
268
+ method: hs[:method],
269
+ names: hs[:names],
270
+ description: description,
271
+ domain: hs[:domain],
272
+ action: hs[:action],
273
+ parameters: parameters,
274
+ }
275
+ end
276
+ end
228
277
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: casual-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takeshi Kojima
@@ -38,6 +38,7 @@ extensions: []
38
38
  extra_rdoc_files: []
39
39
  files:
40
40
  - bin/casual-api
41
+ - lib/casual-api.rb
41
42
  - lib/casual.rb
42
43
  - lib/config.ru
43
44
  - lib/dsl.rb