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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OTczMmI3N2FkYTQ5NDhhNGU1ZTE1YTZiYjFhNzk4YTNlYWY1NDQxZA==
4
+ YmFjM2ViYTZiMTdlNDM0OTI2Mjg5YzUyN2VkOWIwMTMyMDc3YTgxMg==
5
5
  data.tar.gz: !binary |-
6
- MzY0NTE3NGYyOWY0OGYyYzE2NTc0NmUyYTE2OTA2ZWQ1MTFjOTYxMA==
6
+ Y2VkODYxZGUwNWI2MzU3Yjg5OGZmOTI5NmI3NzljNGE3OWZjYThhNg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZGJkYzlkZmIxYTEzYmYyZWVhY2E1ZDc0YjljOGU2NjRlZjU0YWQ3NmQ5ODU2
10
- N2JjMDIxZGMxOTBhNjk5NDcxYzM1MGVmNDE4M2MyZjA5YzhmODRlMjk3OTdm
11
- NmJjMTljZjdiZDVjZDljYjhlNTdkOWQzNzVkNWQ5YzA2NzAzNzQ=
9
+ MTRhNDdhNWVmMTBjOTY1ZDJmOTViYTRkYTVhZDRjZmY1YjQ2ODNmZTNiOTAw
10
+ M2YzNTM3ZjE2ZmMyZTk1Y2Y0YjVhOTljMWE0NzFiNTljMjVkMzMxYWMwNDVk
11
+ YjM3OGQxMGI5ODk1YzljYjc3NWU2YjNjYjVjNmE4YjA1ZmEwM2Q=
12
12
  data.tar.gz: !binary |-
13
- N2JjNzc2Nzg2NTlmNTBmNjNmYjRkNWRjODgzNjkyZjdlZWMyYWYzZDg4MDcx
14
- YWRkMzA5ODY3NWE4MzE1NjM5NjEyYzRmZmYwZTIwMjMzYjg1MWU0YzgzMjVm
15
- NjI2NzY5ZjYwYmVjNDQzYzUwZDg4NTc4NjU3Yzc1ZGM4ODJlZGI=
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.mkdir "#{project_path}/config/brickchains"
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, :logger
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, logger)
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
- @to_render = {
78
- format: :json,
79
- status: RETURN_CODES[options[:status]] || 200,
80
- headers: { 'Content-Type' => 'application/json' }.merge(options[:headers]),
81
- object: options[:json]
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
- @to_render = {
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
- @to_render[:headers]['Content-Length'] = options[:size] if options.has_key? :size
104
+ to_render[:headers]['Content-Length'] = options[:size] if options.has_key? :size
94
105
  elsif options.has_key? :status
95
- @to_render = {
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(@to_render)
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
@@ -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|
@@ -2,40 +2,89 @@ module Culpa
2
2
 
3
3
  class PathParser
4
4
 
5
- ROUTE_PATTERNS = {
6
- '/:res_name' => {
7
- regex: Regexp.new('^/(\w+)$'),
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
- current_pattern = ROUTE_PATTERNS[pattern]
32
- result = path.match(current_pattern[:regex])
11
+ result = path.match(pattern[:regex])
33
12
  return unless result
34
- returned = {}
35
- current_pattern[:id_to_sym].each do |id, sym|
36
- returned[sym] = result[id]
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
- returned
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
@@ -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 with_params(params)
44
- @params = Culpa::EnvelopeRequest.new(params)
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.0.0.2'
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
- @router = {}
42
- bc_path = options[:brickchains] || './config/brickchains'
43
- Dir["#{bc_path}/*.yml"].map { |bc_file|
44
- YAML.load_file(bc_file)
45
- }.each{ |bc|
46
- @router.merge!(bc)
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
- call_options = {
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
- call_options[:sub_call] = body['sub_call'] if body.has_key? 'sub_call'
71
- call_options[:input] = body['data']
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
- call_options[:input] = env['rack.input']
90
+ request[:input] = env['rack.input']
74
91
  end
75
- # Try the enforced routes
76
- if route_params = PathParser.parse('/:res_name', env['PATH_INFO'])
77
- call_options[:res_name] = route_params[:res_name]
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 StandardError => error
113
- @logger.error "Error ecountered while treating request : #{error.message}, aborting...\n#{error.backtrace.join("\n")}"
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
- router_method_name = "#{options[:sub_call]}_#{options[:res_name]}"
119
- envelope = Envelope.new
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
- return rack_error(404) unless @router.has_key? router_method_name
122
- @logger.info "Brickchain determined for #{router_method_name}"
123
- @router[router_method_name].each do |brick|
124
- # Iterating over the brickchain of this request
125
- if brick.is_a? String
126
- # This brick is a sychronous call, processing call directly.
127
- result = brick_call brick, envelope, request
128
- return result if result
129
- elsif brick.is_a?(Hash) && brick.has_key?('async')
130
- # This brick is a bundle of async bricks, treating it in threads.
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, {'Content-Type' => 'application/json'}, []]
147
+ [code.to_s, {}, []]
171
148
  end
172
149
 
173
150
  end
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'culpa', '~> 1.0'
3
+ gem 'culpa', '~> 1.1'
4
4
 
5
5
  # Use rspec to test your project.
6
6
  gem 'rspec', '~> 3.4'
@@ -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.0.0.2
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
  - - ! '>='