casual-api 3.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.
Files changed (6) hide show
  1. checksums.yaml +7 -0
  2. data/bin/casual-api +7 -0
  3. data/lib/casual.rb +94 -0
  4. data/lib/config.ru +34 -0
  5. data/lib/dsl.rb +185 -0
  6. metadata +88 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1f562aeb27eaed4448ca125d26b257e9c0cf51c6
4
+ data.tar.gz: 08a14d0ba57999138e843de5e5e567c33ffa50b0
5
+ SHA512:
6
+ metadata.gz: 194ac34b57ddec0727cfe72c415cb66266b589c0477e9c8784869e9a534a089c302c825b143cc718d669f312e95995f6e46c6673fea540fd2388acf9d591a55a
7
+ data.tar.gz: eb1f02762637aeef75ed68d48786d35a36a097d8b846ddb401e17cae5334e0abe6bae542c9faa62ac88b476cb7b2061978298147cc6063f4767b066bb92c8e7f
data/bin/casual-api ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'open3'
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ command = "thin -R #{lib}/config.ru start"
5
+ puts "casual-api is running, CTRL+C to stop"
6
+ _, out, err, th = Open3.popen3(command)
7
+ th.join
data/lib/casual.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'rack'
2
+ require_relative './dsl.rb'
3
+
4
+ class ChildProcess
5
+ def initialize(action, parameters, session)
6
+ @file_descriptors = []
7
+ @action = action
8
+ @params = parameters
9
+ @session = session
10
+ end
11
+
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
+ }
20
+ end
21
+ end
22
+
23
+
24
+ # RackApp
25
+ class Casual
26
+ def initialize(prefix, file, mode)
27
+ @prefix = prefix
28
+ @file = file
29
+ @mode = mode
30
+ end
31
+
32
+ def call(env)
33
+ @env = env
34
+ @req = Rack::Request.new(env)
35
+ fire_action
36
+ end
37
+
38
+ private
39
+ def fire_action
40
+ actions = parse_actions
41
+ action = detect_action actions
42
+ if action
43
+ cp = ChildProcess.new(action, parameters, session)
44
+ result = cp.execute
45
+ @req.session['casual.session'] = result[:session]
46
+ res = Rack::Response.new(){|r|
47
+ r.status = result[:status_code]
48
+ r.write result[:body]
49
+ r['Content-Type'] = map_content_type(result[:content_type])
50
+ }
51
+ res.finish
52
+ else
53
+ [404, {}, []]
54
+ end
55
+ end
56
+
57
+ def map_content_type(c)
58
+ {
59
+ txt: "text/plain;charset=utf-8",
60
+ html: "text/html;charset=utf-8",
61
+ }[c.to_sym] || c
62
+ end
63
+
64
+ def parse_actions
65
+ if @mode == "development" || !@actions
66
+ @actions = DSL.parse @file
67
+ else
68
+ @actions
69
+ end
70
+ end
71
+
72
+ def detect_action actions
73
+ path = request_path
74
+ _method = request_method
75
+ actions.detect{|act|
76
+ act[:method] == _method && make_path(act) == path
77
+ }
78
+ end
79
+ def make_path action
80
+ @prefix + (action[:domain] + [action[:name]]).compact.map{|p|p.to_s}.join('/')
81
+ end
82
+ def parameters
83
+ @req.params.map{|k,v| {k.to_sym => v} }.inject({}, :merge)
84
+ end
85
+ def session
86
+ @req.session['casual.session'] || {}
87
+ end
88
+ def request_method
89
+ @req.request_method.downcase.to_sym
90
+ end
91
+ def request_path
92
+ @req.path
93
+ end
94
+ end
data/lib/config.ru ADDED
@@ -0,0 +1,34 @@
1
+ require 'rack'
2
+ require 'yaml'
3
+
4
+ require_relative './casual.rb'
5
+
6
+ config = open("config.yaml"){|f|
7
+ YAML.load f.read
8
+ }
9
+
10
+ use Rack::Session::Pool,
11
+ :key => config["session"]["key"],
12
+ :domain => config["session"]["domain"],
13
+ :path => config["session"]["path"],
14
+ :expire_after => config["session"]["expire"],
15
+ :secret => config["session"]["secret"]
16
+
17
+ (config["routes"] || {}).each{|k,v|
18
+ map(k)do
19
+ run Rack::Cascade.new [
20
+ Casual.new(k, v, config["mode"])
21
+ ]
22
+ end
23
+ }
24
+
25
+ (config["static_web"] || {}).each{|k,v|
26
+ use Rack::Static, urls: [""],
27
+ root: v["directory"],
28
+ index: v["index"]
29
+ map(k)do
30
+ run Rack::Cascade.new [
31
+ Rack::Directory.new(v["directory"]),
32
+ ]
33
+ end
34
+ }
data/lib/dsl.rb ADDED
@@ -0,0 +1,185 @@
1
+ require 'pp'
2
+ require 'securerandom'
3
+
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}`
22
+ end
23
+ def body
24
+ @body
25
+ end
26
+ def body=(value)
27
+ @body = value
28
+ end
29
+ end
30
+
31
+ module Action
32
+ def self.extended obj
33
+ obj.instance_variable_set(:@session, {})
34
+ obj.instance_variable_set(:@content_type, nil)
35
+ obj.instance_variable_set(:@status_code, 200)
36
+ obj.instance_variable_set(:@tempfiles, [])
37
+ end
38
+
39
+ def file? file
40
+ file.is_a? Hash
41
+ end
42
+
43
+ def tempfile file
44
+ if file.is_a? Hash
45
+ file[:tempfile].path
46
+ else
47
+ f = Tempfile.open(SecureRandom.hex(4))
48
+ f.write file
49
+ f.flush
50
+ @tempfiles.push f
51
+ f.path
52
+ end
53
+ end
54
+
55
+ def session_clear
56
+ session.clear
57
+ session = nil
58
+ end
59
+
60
+ def session; @session; end
61
+ def session=(value); @session = value; end
62
+ def content_type; @content_type; end
63
+ def tempfiles; @tempfiles; end
64
+ def status_code; @status_code; end
65
+ def content_type=(type); @content_type = type; end
66
+ def status_code=(code); @status_code = code; end
67
+
68
+
69
+ def self.fire(action, params, session)
70
+ obj = Object
71
+ obj.extend Action
72
+ obj.extend BodyWriter
73
+ obj.content_type = action[:content_type] || :txt
74
+ obj.session = session
75
+ ps = action[:action].parameters.map{|_, name|
76
+ {name => params[name]}
77
+ }.inject({}, :merge)
78
+ result = execute(obj, ps, action[:action])
79
+ unless result[:executed]
80
+ obj.status_code = 400
81
+ obj.body = "Not given enough parameter"
82
+ obj.content_type = :txt
83
+ end
84
+ obj.tempfiles.each{|f| f.close }
85
+ obj
86
+ end
87
+
88
+ private
89
+ def self.execute(obj, ps, block)
90
+ vs = block.parameters.map{|_,n|ps[n]}.inject([[],true]){|(acc,b),v|
91
+ (b && v) ? [acc+[v],b] : [acc,false]
92
+ }[0]
93
+ if block.arity <= vs.length
94
+ result = obj.instance_exec(*vs, &block)
95
+ { executed: true, result: result }
96
+ else
97
+ { executed: false }
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ module Annotation
104
+ def file *files
105
+ @context_stack.last[:files] ||= []
106
+ @context_stack.last[:files].push(*files)
107
+ end
108
+ def description(param1, param2=nil)
109
+ @context_stack.last[:description] ||= {}
110
+ if param2
111
+ @context_stack.last[:description][:params] ||= {}
112
+ @context_stack.last[:description][:params][param1] = param2
113
+ else
114
+ @context_stack.last[:description][:path] = param1
115
+ end
116
+ end
117
+ def clear_annotaion
118
+ @context_stack.last.delete(:files)
119
+ @context_stack.last.delete(:description)
120
+ end
121
+ end
122
+
123
+ module ExtExpression
124
+ def content_type type
125
+ @context_stack.last[:content_type] = type
126
+ end
127
+ end
128
+
129
+ module SyntaxSugar
130
+ def get(path, &block); path(:get, path, caller, &block); end
131
+ def post(path, &block); path(:post, path, caller, &block); end
132
+
133
+ def command(path, cmd)
134
+ get path do
135
+ write `#{cmd}`
136
+ end
137
+ end
138
+ end
139
+
140
+
141
+ module DSL
142
+ def self.extended obj
143
+ # add stacks and a context to an extended object.
144
+ obj.instance_variable_set(:@action_stack, [])
145
+ obj.instance_variable_set(:@domain_stack, [])
146
+ obj.instance_variable_set(:@context_stack, [{}])
147
+ obj.extend Action
148
+ obj.extend Annotation
149
+ obj.extend ExtExpression
150
+ obj.extend SyntaxSugar
151
+ end
152
+ def action_stack
153
+ @action_stack
154
+ end
155
+
156
+ def domain(*path , &block)
157
+ # annotations don't effect over domain
158
+ clear_annotaion
159
+ @domain_stack.push *path
160
+ @context_stack.push(@context_stack.last.clone)
161
+ block.call
162
+ @context_stack.pop
163
+ @domain_stack.pop path.length
164
+ # annotations don't effect over domain
165
+ clear_annotaion
166
+ end
167
+
168
+ def path(method, name, c=nil, &block)
169
+ meta = @context_stack.last.clone
170
+ meta[:method] = method
171
+ meta[:name] = name
172
+ meta[:domain] = @domain_stack.clone
173
+ meta[:action] = block
174
+ clear_annotaion # annotations effect only a path.
175
+ @action_stack.push meta
176
+ end
177
+
178
+ def self.parse(file)
179
+ obj = Object.new
180
+ obj.extend DSL
181
+ obj.instance_eval(open(file){|f|f.read})
182
+ obj.action_stack
183
+ end
184
+
185
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: casual-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Takeshi Kojima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thin
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.6.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.6'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.6.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: rack
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.5'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.5.2
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.5'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.5.2
53
+ description: A simple web server
54
+ email: tatkeshi@gmail.com
55
+ executables:
56
+ - casual-api
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - bin/casual-api
61
+ - lib/casual.rb
62
+ - lib/config.ru
63
+ - lib/dsl.rb
64
+ homepage: https://github.com/tatkeshi/casual-api
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.2.2
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: casual-api
88
+ test_files: []