culpa 1.0.0.2 → 1.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.
- checksums.yaml +8 -8
- data/bin/culpa +10 -2
- data/lib/culpa/action.rb +23 -12
- data/lib/culpa/brickchain_helpers.rb +74 -0
- data/lib/culpa/envelope.rb +1 -3
- data/lib/culpa/path_parser.rb +78 -29
- data/lib/culpa/routes_builder.rb +68 -0
- data/lib/culpa/test_helpers.rb +17 -20
- data/lib/culpa.rb +57 -80
- data/templates/culpa/Gemfile +1 -1
- data/templates/culpa/index.html +52 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YmFjM2ViYTZiMTdlNDM0OTI2Mjg5YzUyN2VkOWIwMTMyMDc3YTgxMg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Y2VkODYxZGUwNWI2MzU3Yjg5OGZmOTI5NmI3NzljNGE3OWZjYThhNg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MTRhNDdhNWVmMTBjOTY1ZDJmOTViYTRkYTVhZDRjZmY1YjQ2ODNmZTNiOTAw
|
10
|
+
M2YzNTM3ZjE2ZmMyZTk1Y2Y0YjVhOTljMWE0NzFiNTljMjVkMzMxYWMwNDVk
|
11
|
+
YjM3OGQxMGI5ODk1YzljYjc3NWU2YjNjYjVjNmE4YjA1ZmEwM2Q=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NDUxYmQ5ZjllODdhMDU1NGFjN2I0NWZlYTBmNmMzOWI2YmQ4MDlkN2ZmOTA3
|
14
|
+
ZDc4M2I3MmNjY2IzYjVkZTFiYmQyYTRlYzZjM2Q1ZjljYTI4MDkwMDk1ZWJi
|
15
|
+
MTM3YmM1MTUxMTg3OTBlZGVlODQ5MzkzZDc0YmZkYTRjYTMyNTQ=
|
data/bin/culpa
CHANGED
@@ -18,9 +18,10 @@ def create_project(project_path)
|
|
18
18
|
FileUtils.touch "#{project_path}/actions/.keep"
|
19
19
|
FileUtils.mkdir "#{project_path}/models"
|
20
20
|
FileUtils.touch "#{project_path}/models/.keep"
|
21
|
+
FileUtils.mkdir "#{project_path}/public"
|
22
|
+
FileUtils.touch "#{project_path}/public/.keep"
|
21
23
|
FileUtils.mkdir "#{project_path}/config"
|
22
|
-
FileUtils.
|
23
|
-
FileUtils.touch "#{project_path}/config/brickchains/.keep"
|
24
|
+
FileUtils.touch "#{project_path}/config/brickchains.rb"
|
24
25
|
FileUtils.mkdir "#{project_path}/config/initializers"
|
25
26
|
FileUtils.touch "#{project_path}/config/initializers/.keep"
|
26
27
|
|
@@ -36,6 +37,12 @@ def create_project(project_path)
|
|
36
37
|
dockerfile_rackup_path = File.join( File.dirname(__FILE__), '../templates/culpa/Dockerfile' )
|
37
38
|
FileUtils.cp dockerfile_rackup_path, "#{project_path}/Dockerfile"
|
38
39
|
|
40
|
+
puts '==> Copying default public folder content'
|
41
|
+
index_html_rackup_path = File.join( File.dirname(__FILE__), '../templates/culpa/index.html' )
|
42
|
+
FileUtils.cp index_html_rackup_path, "#{project_path}/public/index.html"
|
43
|
+
logo_png_rackup_path = File.join( File.dirname(__FILE__), '../templates/culpa/logo.png' )
|
44
|
+
FileUtils.cp logo_png_rackup_path, "#{project_path}/public/logo.png"
|
45
|
+
|
39
46
|
puts '==> Switching to project directory'
|
40
47
|
FileUtils.cd project_path
|
41
48
|
|
@@ -92,6 +99,7 @@ end
|
|
92
99
|
|
93
100
|
def start_server
|
94
101
|
require 'rack'
|
102
|
+
ENV['RACK_ENV'] ||= 'development'
|
95
103
|
Rack::Handler.default.run(Culpa::Application.new, Port: 4748)
|
96
104
|
end
|
97
105
|
|
data/lib/culpa/action.rb
CHANGED
@@ -55,7 +55,7 @@ class Action
|
|
55
55
|
unknown_error: 520
|
56
56
|
}
|
57
57
|
|
58
|
-
attr_reader :e, :r
|
58
|
+
attr_reader :e, :r
|
59
59
|
|
60
60
|
class RenderNow < StandardError
|
61
61
|
attr_reader :to_render
|
@@ -64,24 +64,35 @@ class Action
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
def initialize(envelope, request
|
67
|
+
def initialize(envelope, request)
|
68
68
|
@e = envelope
|
69
69
|
@r = request
|
70
|
-
@logger = logger
|
71
70
|
@to_render = nil
|
72
71
|
end
|
73
72
|
|
73
|
+
def method_missing(sym)
|
74
|
+
@r.send(sym)
|
75
|
+
end
|
76
|
+
|
74
77
|
def render(options={})
|
75
78
|
options[:headers] ||= {}
|
76
79
|
if options.has_key? :json
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
80
|
+
if options[:json]
|
81
|
+
to_render = {
|
82
|
+
format: :json,
|
83
|
+
status: RETURN_CODES[options[:status]] || 200,
|
84
|
+
headers: { 'Content-Type' => 'application/json' }.merge(options[:headers]),
|
85
|
+
object: options[:json]
|
86
|
+
}
|
87
|
+
else
|
88
|
+
to_render = {
|
89
|
+
format: :status,
|
90
|
+
headers: options[:headers],
|
91
|
+
status: RETURN_CODES[options[:or_status]]
|
82
92
|
}
|
93
|
+
end
|
83
94
|
elsif options.has_key? :file
|
84
|
-
|
95
|
+
to_render = {
|
85
96
|
format: :file_from_path,
|
86
97
|
headers: {
|
87
98
|
'Content-Type' => options[:content_type] || 'application/octet-stream',
|
@@ -90,9 +101,9 @@ class Action
|
|
90
101
|
status: RETURN_CODES[options[:status]] || 200,
|
91
102
|
object: Culpa::FileStreamer.new(options[:file])
|
92
103
|
}
|
93
|
-
|
104
|
+
to_render[:headers]['Content-Length'] = options[:size] if options.has_key? :size
|
94
105
|
elsif options.has_key? :status
|
95
|
-
|
106
|
+
to_render = {
|
96
107
|
format: :status,
|
97
108
|
headers: options[:headers],
|
98
109
|
status: RETURN_CODES[options[:status]]
|
@@ -100,7 +111,7 @@ class Action
|
|
100
111
|
else
|
101
112
|
raise Culpa::UnknownRenderCall.new
|
102
113
|
end
|
103
|
-
raise RenderNow.new(
|
114
|
+
raise RenderNow.new(to_render)
|
104
115
|
end
|
105
116
|
|
106
117
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Culpa
|
2
|
+
|
3
|
+
class BrickchainExecutor
|
4
|
+
|
5
|
+
attr_reader :envelope
|
6
|
+
|
7
|
+
def initialize(envelope, request)
|
8
|
+
@envelope = envelope
|
9
|
+
@request = request
|
10
|
+
end
|
11
|
+
|
12
|
+
def async(&blk)
|
13
|
+
asb = AsyncBrickchain.new(@envelope, @request)
|
14
|
+
asb.instance_eval(&blk)
|
15
|
+
asb.chs.each { |call_holder| call_holder.thread.join }
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(sym)
|
19
|
+
return CallHolder.new(sym.to_s, @envelope, @request)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class AsyncBrickchain
|
25
|
+
|
26
|
+
attr_reader :envelope
|
27
|
+
attr_reader :chs
|
28
|
+
|
29
|
+
def initialize(envelope, request)
|
30
|
+
@envelope = envelope
|
31
|
+
@request = request
|
32
|
+
@chs = []
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(sym)
|
36
|
+
@chs << CallHolder.new(sym.to_s, @envelope, @request, true)
|
37
|
+
@chs.last
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class CallHolder
|
43
|
+
|
44
|
+
attr_reader :thread
|
45
|
+
|
46
|
+
def initialize(brickname, envelope, request, async=false)
|
47
|
+
@brickname = brickname
|
48
|
+
@envelope = envelope
|
49
|
+
@request = request
|
50
|
+
@async = async
|
51
|
+
end
|
52
|
+
|
53
|
+
def from(action_name)
|
54
|
+
if @async
|
55
|
+
@thread = Thread.new { sub_from(action_name) }
|
56
|
+
else
|
57
|
+
sub_from(action_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def sub_from(action_name)
|
62
|
+
action = get_action(action_name.to_s)
|
63
|
+
action.send @brickname
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_action(name)
|
68
|
+
action_name = name.split('_').map{|w| w.capitalize}.join
|
69
|
+
return Actions.const_get(action_name).new(@envelope, @request)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/lib/culpa/envelope.rb
CHANGED
@@ -39,11 +39,9 @@ module Culpa
|
|
39
39
|
class EnvelopeRequest
|
40
40
|
|
41
41
|
attr_accessor :verb
|
42
|
+
attr_accessor :path
|
42
43
|
attr_accessor :input
|
43
44
|
attr_accessor :params
|
44
|
-
attr_accessor :res_name
|
45
|
-
attr_accessor :id
|
46
|
-
attr_accessor :sub_call
|
47
45
|
|
48
46
|
def initialize(attributes)
|
49
47
|
attributes.each do |name, value|
|
data/lib/culpa/path_parser.rb
CHANGED
@@ -2,40 +2,89 @@ module Culpa
|
|
2
2
|
|
3
3
|
class PathParser
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
id_to_sym: {
|
9
|
-
1 => :res_name
|
10
|
-
}
|
11
|
-
},
|
12
|
-
'/:res_name/:id' => {
|
13
|
-
regex: Regexp.new('^/(\w+)/(\w+)$'),
|
14
|
-
id_to_sym: {
|
15
|
-
1 => :res_name,
|
16
|
-
2 => :id
|
17
|
-
}
|
18
|
-
},
|
19
|
-
'/:res_name/:id/:sub_call' => {
|
20
|
-
regex: Regexp.new('^/(\w+)/(\w+)/(\w+)$'),
|
21
|
-
id_to_sym: {
|
22
|
-
1 => :res_name,
|
23
|
-
2 => :id,
|
24
|
-
3 => :sub_call
|
25
|
-
}
|
26
|
-
}
|
27
|
-
}
|
5
|
+
def self.route_patterns
|
6
|
+
@@route_patterns
|
7
|
+
end
|
28
8
|
|
29
9
|
# Parsing a path
|
30
10
|
def self.parse(pattern, path)
|
31
|
-
|
32
|
-
result = path.match(current_pattern[:regex])
|
11
|
+
result = path.match(pattern[:regex])
|
33
12
|
return unless result
|
34
|
-
|
35
|
-
|
36
|
-
|
13
|
+
return Hash[result.names.map{ |name| name }.zip(result.captures)]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Use the brickchains to build the cache of route
|
17
|
+
def self.create_route_cache(brickchains, global_prefix)
|
18
|
+
custom_routes = []
|
19
|
+
brickchains.each do |name,data|
|
20
|
+
next unless data.has_key?(:options) and data[:options] and data[:options].has_key?(:url)
|
21
|
+
item = {
|
22
|
+
regex: self.compose_regex(global_prefix + data[:options][:url]),
|
23
|
+
router_method_name: name
|
24
|
+
}
|
25
|
+
item[:verb] = data[:options][:verb] if data[:options].has_key? :verb
|
26
|
+
custom_routes << item
|
37
27
|
end
|
38
|
-
|
28
|
+
fixed_routes = [
|
29
|
+
{
|
30
|
+
infer: {
|
31
|
+
get: 'list',
|
32
|
+
post: 'create'
|
33
|
+
},
|
34
|
+
regex: self.compose_regex(global_prefix + '/:res_name')
|
35
|
+
}, {
|
36
|
+
infer: {
|
37
|
+
get: 'get',
|
38
|
+
put: 'update',
|
39
|
+
patch: 'update',
|
40
|
+
delete: 'delete'
|
41
|
+
},
|
42
|
+
regex: self.compose_regex(global_prefix + '/:res_name/:id')
|
43
|
+
}, {
|
44
|
+
regex: self.compose_regex(global_prefix + '/:res_name/:id/:sub_call')
|
45
|
+
}
|
46
|
+
]
|
47
|
+
@@route_patterns = custom_routes + fixed_routes
|
48
|
+
end
|
49
|
+
|
50
|
+
# Transforms a string to its equivalent for the compose_regex
|
51
|
+
def self.dir_to_regex(dir)
|
52
|
+
return dir unless dir.start_with? ':'
|
53
|
+
"(?<#{dir[1..dir.length-1]}>\\w+)"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Composing a regex from a human-readable http path pattern
|
57
|
+
def self.compose_regex(url)
|
58
|
+
regex = url.split('/').map{ |dir| self.dir_to_regex(dir) }.join('/')
|
59
|
+
Regexp.new("^#{regex}$")
|
60
|
+
end
|
61
|
+
|
62
|
+
# Search for the routes, extracts informations and returns
|
63
|
+
# the router method name and the request informations
|
64
|
+
def self.extract_vars(path, call_options)
|
65
|
+
@@route_patterns.each do |data|
|
66
|
+
next unless params = self.parse(data, path)
|
67
|
+
call_options[:params].merge!(params)
|
68
|
+
if data.has_key?(:router_method_name)
|
69
|
+
raise RouteNotFoundError.new if data.has_key?(:verb) and call_options[:verb] != data[:verb]
|
70
|
+
return data[:router_method_name], call_options
|
71
|
+
else
|
72
|
+
return self.infer_method_name(data, call_options[:params], call_options[:verb]), call_options
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Try to return the router method name or infer it automatically
|
78
|
+
def self.infer_method_name(pattern, params, verb)
|
79
|
+
if params.has_key?('sub_call')
|
80
|
+
return "#{params['sub_call']}_#{params['res_name']}".to_sym
|
81
|
+
end
|
82
|
+
|
83
|
+
unless pattern[:infer].is_a?(Hash) and pattern[:infer].has_key?(verb)
|
84
|
+
raise UnpredictableSubCallError.new
|
85
|
+
end
|
86
|
+
|
87
|
+
return "#{pattern[:infer][verb]}_#{params['res_name']}".to_sym
|
39
88
|
end
|
40
89
|
|
41
90
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Culpa
|
2
|
+
|
3
|
+
class RoutesBuilder
|
4
|
+
|
5
|
+
attr_reader :result
|
6
|
+
attr_reader :prefix
|
7
|
+
attr_reader :public_folder
|
8
|
+
|
9
|
+
def initialize(to_eval)
|
10
|
+
@result = {}
|
11
|
+
if to_eval.is_a? String
|
12
|
+
instance_eval(to_eval)
|
13
|
+
else
|
14
|
+
instance_eval(&to_eval)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def global_prefix(prefix)
|
19
|
+
@prefix = prefix
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_public_folder(folder)
|
23
|
+
@public_folder = folder
|
24
|
+
end
|
25
|
+
|
26
|
+
def brickchain(name,*opts,&blk)
|
27
|
+
@result[name] = {
|
28
|
+
options: opts[0],
|
29
|
+
block: blk
|
30
|
+
}
|
31
|
+
@result[name][:before] = @before if @before
|
32
|
+
end
|
33
|
+
|
34
|
+
def before(&blk)
|
35
|
+
@before=[] unless @before
|
36
|
+
@before << blk
|
37
|
+
end
|
38
|
+
|
39
|
+
def brickchain_context(name, &blk)
|
40
|
+
@result.merge!(RoutesBuilder.new(blk).result)
|
41
|
+
end
|
42
|
+
|
43
|
+
def infer(name, &blk)
|
44
|
+
bta = BlockToArray.new
|
45
|
+
bta.instance_eval(&blk)
|
46
|
+
bta.culpa_bta_result.each do |item|
|
47
|
+
route_name = "#{item}_#{name}".to_sym
|
48
|
+
@result[route_name] = {
|
49
|
+
class_name: name,
|
50
|
+
brick_name: item
|
51
|
+
}
|
52
|
+
@result["#{item}_#{name}".to_sym][:before] = @before if @before
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
class BlockToArray
|
59
|
+
attr_reader :culpa_bta_result
|
60
|
+
def initialize
|
61
|
+
@culpa_bta_result = []
|
62
|
+
end
|
63
|
+
def method_missing(sym)
|
64
|
+
@culpa_bta_result << sym.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/lib/culpa/test_helpers.rb
CHANGED
@@ -14,41 +14,34 @@ module CulpaHelpers
|
|
14
14
|
|
15
15
|
class BrickCall
|
16
16
|
|
17
|
+
attr_reader :from_done
|
18
|
+
|
17
19
|
def initialize(brick_name)
|
18
20
|
@brick_name = brick_name.to_s
|
21
|
+
@from_done = false
|
19
22
|
end
|
20
23
|
|
21
24
|
def from(action_const)
|
25
|
+
@from_done = true
|
22
26
|
@action_const = action_const
|
23
|
-
@envelope = Culpa::Envelope.new()
|
24
|
-
@params = Culpa::EnvelopeRequest.new({})
|
25
|
-
self
|
26
|
-
end
|
27
|
-
|
28
|
-
# :nocov:
|
29
|
-
def and_with_envelope(envelope)
|
30
|
-
with_envelope(envelope)
|
31
|
-
end
|
32
|
-
|
33
|
-
def and_with_params(params)
|
34
|
-
with_params(params)
|
35
|
-
end
|
36
|
-
# :nocov:
|
37
|
-
|
38
|
-
def with_envelope(envelope)
|
39
|
-
@envelope = Culpa::Envelope.new(envelope)
|
40
27
|
self
|
41
28
|
end
|
42
29
|
|
43
|
-
def
|
44
|
-
|
30
|
+
def with(opts)
|
31
|
+
opts[:envelope] ||= {}
|
32
|
+
@envelope = Culpa::Envelope.new(opts[:envelope])
|
33
|
+
opts.delete(:envelope)
|
34
|
+
@params = Culpa::EnvelopeRequest.new(opts)
|
45
35
|
self
|
46
36
|
end
|
47
37
|
|
48
38
|
def call
|
39
|
+
return unless @from_done
|
49
40
|
return @call_cache if @call_done
|
41
|
+
@envelope ||= Culpa::Envelope.new({})
|
42
|
+
@params ||= Culpa::EnvelopeRequest.new({})
|
43
|
+
@action_const.new(@envelope, @params).send(@brick_name)
|
50
44
|
@call_done = true
|
51
|
-
@action_const.new(@envelope, @params, Logger.new(STDOUT)).send(@brick_name)
|
52
45
|
return @call_cache = { to_render: nil, envelope: @envelope }
|
53
46
|
rescue Action::RenderNow => renderer
|
54
47
|
return @call_cache = { to_render: renderer.to_render, envelope: @envelope }
|
@@ -58,6 +51,7 @@ module CulpaHelpers
|
|
58
51
|
|
59
52
|
RSpec::Matchers.define :have_status do |expected|
|
60
53
|
match do |actual|
|
54
|
+
actual.from(described_class) unless actual.from_done or described_class.nil?
|
61
55
|
bc = actual.call
|
62
56
|
bc[:to_render][:status] == Action::RETURN_CODES[expected]
|
63
57
|
end
|
@@ -66,6 +60,7 @@ module CulpaHelpers
|
|
66
60
|
RSpec::Matchers.define :have_headers do |expected|
|
67
61
|
match do |actual|
|
68
62
|
expected.each do |k,v|
|
63
|
+
actual.from(described_class) unless actual.from_done or described_class.nil?
|
69
64
|
bc = actual.call
|
70
65
|
return false unless bc[:to_render][:headers].has_key?(k) and
|
71
66
|
bc[:to_render][:headers][k] == expected[k]
|
@@ -76,6 +71,7 @@ module CulpaHelpers
|
|
76
71
|
|
77
72
|
RSpec::Matchers.define :have_body do |expected|
|
78
73
|
match do |actual|
|
74
|
+
actual.from(described_class) unless actual.from_done or described_class.nil?
|
79
75
|
bc = actual.call
|
80
76
|
bc[:to_render][:object] == expected
|
81
77
|
end
|
@@ -83,6 +79,7 @@ module CulpaHelpers
|
|
83
79
|
|
84
80
|
RSpec::Matchers.define :have_envelope do |expected|
|
85
81
|
match do |actual|
|
82
|
+
actual.from(described_class) unless actual.from_done or described_class.nil?
|
86
83
|
bc = actual.call
|
87
84
|
bc[:envelope] == Culpa::Envelope.new(expected)
|
88
85
|
end
|
data/lib/culpa.rb
CHANGED
@@ -7,8 +7,10 @@ require 'logger'
|
|
7
7
|
require_relative 'culpa/envelope'
|
8
8
|
require_relative 'culpa/path_parser'
|
9
9
|
require_relative 'culpa/file_streamer'
|
10
|
+
require_relative 'culpa/routes_builder'
|
11
|
+
require_relative 'culpa/brickchain_helpers'
|
10
12
|
|
11
|
-
CULPA_VERSION='1.
|
13
|
+
CULPA_VERSION='1.1.0'
|
12
14
|
|
13
15
|
ACTIONS_PATH ||= './actions/*.rb'
|
14
16
|
MODELS_PATH ||= './models/*.rb'
|
@@ -30,6 +32,7 @@ module Culpa
|
|
30
32
|
class UnpredictableSubCallError < StandardError; end
|
31
33
|
class NoRenderCalled < StandardError; end
|
32
34
|
class UnknownRenderCall < StandardError; end
|
35
|
+
class RouteNotFoundError < StandardError; end
|
33
36
|
|
34
37
|
Dir[MODELS_PATH].each do |file|
|
35
38
|
require file
|
@@ -38,13 +41,13 @@ module Culpa
|
|
38
41
|
class Application
|
39
42
|
|
40
43
|
def initialize(options = {})
|
41
|
-
|
42
|
-
bc_path = options[:brickchains] || './config/brickchains'
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
# Loading brickchains definitions
|
45
|
+
bc_path = options[:brickchains] || './config/brickchains.rb'
|
46
|
+
route_builder = RoutesBuilder.new(File.read(bc_path))
|
47
|
+
@public_folder = options[:public] || route_builder.public_folder || './public'
|
48
|
+
@router = route_builder.result
|
49
|
+
PathParser.create_route_cache(@router, route_builder.prefix || '')
|
50
|
+
# Logging helper
|
48
51
|
@logger = Logger.new(options[:log_output] || STDOUT)
|
49
52
|
@logger.level = case ENV['RACK_ENV']
|
50
53
|
when 'development', 'test'
|
@@ -55,102 +58,76 @@ module Culpa
|
|
55
58
|
# :nocov:
|
56
59
|
end
|
57
60
|
@logger.info 'Culpa fully initialized'
|
61
|
+
if %w(development test).include? ENV['RACK_ENV']
|
62
|
+
@rack_file = Rack::File.new(@public_folder)
|
63
|
+
end
|
58
64
|
end
|
59
65
|
|
60
66
|
##
|
61
67
|
# Rack entrypoint
|
62
68
|
def call(env)
|
63
|
-
|
69
|
+
path = env['PATH_INFO']
|
70
|
+
if @rack_file && (File.exists?("#{@public_folder}#{path}") || path == '/')
|
71
|
+
env['PATH_INFO'] = '/index.html' if path == '/'
|
72
|
+
return @rack_file.call(env)
|
73
|
+
end
|
74
|
+
@logger.info "Received request : #{path}"
|
75
|
+
# Request preparation
|
76
|
+
request = {
|
64
77
|
verb: env['REQUEST_METHOD'].downcase.to_sym,
|
65
78
|
params: Rack::Utils.parse_nested_query(env['QUERY_STRING'])
|
66
79
|
}
|
67
80
|
# Parse body if in JSON, otherwise pass the rack.input directly
|
68
81
|
if env['CONTENT_TYPE'] == 'application/json'
|
69
82
|
body = JSON.parse(env['rack.input'].read)
|
70
|
-
|
71
|
-
|
83
|
+
request[:input] = if body.has_key?('data')
|
84
|
+
body['data']
|
85
|
+
else
|
86
|
+
body
|
87
|
+
end
|
88
|
+
request[:params]['sub_call'] = body['sub_call'] if body.has_key? 'sub_call'
|
72
89
|
else
|
73
|
-
|
90
|
+
request[:input] = env['rack.input']
|
74
91
|
end
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
case call_options[:verb]
|
79
|
-
when :get
|
80
|
-
call_options[:sub_call] = 'list'
|
81
|
-
when :post
|
82
|
-
call_options[:sub_call] = 'create'
|
83
|
-
else
|
84
|
-
raise UnpredictableSubCallError.new
|
85
|
-
end unless call_options.has_key? :sub_call
|
86
|
-
elsif route_params = PathParser.parse('/:res_name/:id', env['PATH_INFO'])
|
87
|
-
call_options[:res_name] = route_params[:res_name]
|
88
|
-
call_options[:id] = route_params[:id]
|
89
|
-
case call_options[:verb]
|
90
|
-
when :get
|
91
|
-
call_options[:sub_call] = 'get'
|
92
|
-
when :put
|
93
|
-
call_options[:sub_call] = 'update'
|
94
|
-
when :delete
|
95
|
-
call_options[:sub_call] = 'delete'
|
96
|
-
else
|
97
|
-
raise UnpredictableSubCallError.new
|
98
|
-
end unless call_options.has_key? :sub_call
|
99
|
-
elsif route_params = PathParser.parse('/:res_name/:id/:sub_call', env['PATH_INFO'])
|
100
|
-
call_options[:res_name] = route_params[:res_name]
|
101
|
-
call_options[:id] = route_params[:id]
|
102
|
-
call_options[:sub_call] = route_params[:sub_call]
|
103
|
-
else
|
104
|
-
@logger.info "Refused request, #{env['PATH_INFO']} didn't match enforced routes"
|
105
|
-
return rack_error 400
|
106
|
-
end
|
107
|
-
# Call the brickchain and return the value to Rack
|
108
|
-
@logger.info "Calling brickchain => subcall: #{call_options[:sub_call]}, action: #{call_options[:res_name]}, id: #{call_options[:id]}"
|
109
|
-
call_brickchain call_options
|
92
|
+
# Extract vars from path, take route decision and go !
|
93
|
+
method_name, f_request = PathParser.extract_vars(path, request)
|
94
|
+
call_brickchain method_name, f_request
|
110
95
|
rescue UnpredictableSubCallError, JSON::ParserError
|
111
96
|
rack_error 400
|
112
|
-
rescue
|
113
|
-
@logger.
|
97
|
+
rescue RouteNotFoundError
|
98
|
+
@logger.info "Route not found : #{env['PATH_INFO']}"
|
99
|
+
rack_error 404
|
100
|
+
rescue StandardError => err
|
101
|
+
if ENV['RACK_ENV'] == 'development'
|
102
|
+
# :nocov:
|
103
|
+
@logger.error "#{err.to_s}\n#{err.backtrace.join("\n")}"
|
104
|
+
# :nocov:
|
105
|
+
end
|
114
106
|
rack_error 500
|
115
107
|
end
|
116
108
|
|
117
|
-
def call_brickchain(options)
|
118
|
-
|
119
|
-
|
109
|
+
def call_brickchain(router_method_name, options)
|
110
|
+
raise RouteNotFoundError.new unless @router.has_key? router_method_name
|
111
|
+
@logger.info "Executing brickchain : #{router_method_name}"
|
112
|
+
envelope = Envelope.new
|
120
113
|
request = EnvelopeRequest.new(options)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
brick['async'].map { |async_brick|
|
132
|
-
Thread.new { brick_call(async_brick, envelope, request) }
|
133
|
-
}.each { |thread|
|
134
|
-
result = thread.value
|
135
|
-
return result if result
|
136
|
-
}
|
137
|
-
end
|
114
|
+
route = @router[router_method_name]
|
115
|
+
route[:before].each do |before_block|
|
116
|
+
BrickchainExecutor.new(envelope, request).instance_eval(&before_block)
|
117
|
+
end if route.has_key? :before
|
118
|
+
|
119
|
+
if route.has_key? :block
|
120
|
+
BrickchainExecutor.new(envelope, request).instance_eval(&route[:block])
|
121
|
+
else
|
122
|
+
ch = CallHolder.new(route[:brick_name], envelope, request)
|
123
|
+
ch.from(route[:class_name])
|
138
124
|
end
|
139
|
-
raise NoRenderCalled.new
|
140
|
-
end
|
141
125
|
|
142
|
-
|
143
|
-
# Call a brick and get the instancied class of the brick's parent action
|
144
|
-
def brick_call(brick, envelope, request)
|
145
|
-
action_class_name, method_name = brick.split('.')
|
146
|
-
action_class = Actions.const_get(action_class_name).new(envelope, request, @logger)
|
147
|
-
action_class.send method_name
|
148
|
-
return false
|
126
|
+
raise NoRenderCalled.new
|
149
127
|
rescue Action::RenderNow => renderer
|
150
128
|
return do_render(renderer.to_render)
|
151
129
|
end
|
152
130
|
|
153
|
-
##
|
154
131
|
# Renders a json or anything else
|
155
132
|
def do_render(to_render)
|
156
133
|
body = case to_render[:format]
|
@@ -167,7 +144,7 @@ module Culpa
|
|
167
144
|
##
|
168
145
|
# Rack pre-formatted error
|
169
146
|
def rack_error(code)
|
170
|
-
[code.to_s, {
|
147
|
+
[code.to_s, {}, []]
|
171
148
|
end
|
172
149
|
|
173
150
|
end
|
data/templates/culpa/Gemfile
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
<!DOCTYPE HTML>
|
2
|
+
|
3
|
+
<html>
|
4
|
+
|
5
|
+
<head>
|
6
|
+
<title>Culpa : Welcome</title>
|
7
|
+
<style>
|
8
|
+
body, html {
|
9
|
+
padding: 0;
|
10
|
+
margin: 0;
|
11
|
+
font-family: sans-serif;
|
12
|
+
text-align: center;
|
13
|
+
}
|
14
|
+
.top-bar {
|
15
|
+
line-height: 60px;
|
16
|
+
height: 60px;
|
17
|
+
background-color: #be1735;
|
18
|
+
color: white;
|
19
|
+
font-size: 20px;
|
20
|
+
}
|
21
|
+
img {
|
22
|
+
margin: 40px;
|
23
|
+
width: 400px;
|
24
|
+
margin-left: auto;
|
25
|
+
margin-right: auto;
|
26
|
+
}
|
27
|
+
a {
|
28
|
+
display: block;
|
29
|
+
margin-left: auto;
|
30
|
+
margin-right: auto;
|
31
|
+
width: 300px;
|
32
|
+
padding: 10px 30px;
|
33
|
+
text-decoration: none;
|
34
|
+
border-radius: 30px;
|
35
|
+
border: 2px solid #be1735;
|
36
|
+
color: #be1735;
|
37
|
+
transition: all ease-in-out .4s;
|
38
|
+
}
|
39
|
+
a:hover {
|
40
|
+
color: white;
|
41
|
+
background-color: #be1735;
|
42
|
+
}
|
43
|
+
</style>
|
44
|
+
</head>
|
45
|
+
|
46
|
+
<body>
|
47
|
+
<div class="top-bar">Welcome to your Culpa project !</div>
|
48
|
+
<img src="logo.png" />
|
49
|
+
<a href="http://culpaframework.org/guides.html">Read the guide to get started !</a>
|
50
|
+
</body>
|
51
|
+
|
52
|
+
</html>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: culpa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jérémy SEBAN
|
@@ -62,14 +62,17 @@ files:
|
|
62
62
|
- bin/culpa
|
63
63
|
- lib/culpa.rb
|
64
64
|
- lib/culpa/action.rb
|
65
|
+
- lib/culpa/brickchain_helpers.rb
|
65
66
|
- lib/culpa/envelope.rb
|
66
67
|
- lib/culpa/file_streamer.rb
|
67
68
|
- lib/culpa/path_parser.rb
|
69
|
+
- lib/culpa/routes_builder.rb
|
68
70
|
- lib/culpa/test_helpers.rb
|
69
71
|
- templates/culpa/Dockerfile
|
70
72
|
- templates/culpa/Gemfile
|
71
73
|
- templates/culpa/config.ru
|
72
74
|
- templates/culpa/generators/action.tmpl
|
75
|
+
- templates/culpa/index.html
|
73
76
|
- templates/culpa/logo.png
|
74
77
|
homepage: http://culpaframework.org/
|
75
78
|
licenses:
|
@@ -81,9 +84,9 @@ require_paths:
|
|
81
84
|
- lib
|
82
85
|
required_ruby_version: !ruby/object:Gem::Requirement
|
83
86
|
requirements:
|
84
|
-
- -
|
87
|
+
- - ~>
|
85
88
|
- !ruby/object:Gem::Version
|
86
|
-
version: '0'
|
89
|
+
version: '2.0'
|
87
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
91
|
requirements:
|
89
92
|
- - ! '>='
|