culpa 0.1.0.2
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 +7 -0
- data/bin/culpa +71 -0
- data/lib/action.rb +20 -0
- data/lib/culpa.rb +147 -0
- data/lib/envelope.rb +45 -0
- data/templates/Gemfile +4 -0
- data/templates/config.ru +18 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b2243f4c6c5fc62bc2973a233a454bb21796aa24
|
4
|
+
data.tar.gz: 57b0f85fffd6eb328cbadd4cf29c4bb6dc0e2f33
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 748036cb1c35be6eebeeec898b78da3a807606d5188251ab4e5bc71a245152f02379dc7e7ee0b12698807768f6e1b7a4363378f87e636703e132bed99e8920f7
|
7
|
+
data.tar.gz: 243bd8206d143a2853f9a9669f2378d41c8480951c37e432c3d1d11fba77c0b07205264a514ce6547341b3b839c62e40cc1d968a629e28c3034a6924960d714d
|
data/bin/culpa
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Actions
|
6
|
+
Dir['./actions/*.rb'].each do |file|
|
7
|
+
eval(File.open(file).read)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Models
|
12
|
+
Dir['./models/*.rb'].each do |file|
|
13
|
+
eval(File.open(file).read)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_project(project_path)
|
18
|
+
puts "Initializing Culpa project in #{project_path}"
|
19
|
+
|
20
|
+
unless Dir.exist? project_path
|
21
|
+
FileUtils.mkdir_p project_path
|
22
|
+
puts '==> Creating folder'
|
23
|
+
end
|
24
|
+
|
25
|
+
puts '==> Create directory structure'
|
26
|
+
FileUtils.mkdir "#{project_path}/actions"
|
27
|
+
FileUtils.touch "#{project_path}/actions/.keep"
|
28
|
+
FileUtils.mkdir "#{project_path}/models"
|
29
|
+
FileUtils.touch "#{project_path}/models/.keep"
|
30
|
+
FileUtils.mkdir "#{project_path}/config"
|
31
|
+
FileUtils.touch "#{project_path}/config/router.yml"
|
32
|
+
FileUtils.mkdir "#{project_path}/config/initializers"
|
33
|
+
FileUtils.touch "#{project_path}/config/initializers/.keep"
|
34
|
+
|
35
|
+
puts '==> Copying standard gemfile'
|
36
|
+
gemfile_path = File.join( File.dirname(__FILE__), '../templates/Gemfile' )
|
37
|
+
FileUtils.cp gemfile_path, "#{project_path}/Gemfile"
|
38
|
+
|
39
|
+
puts '==> Copying standard config.ru'
|
40
|
+
config_rackup_path = File.join( File.dirname(__FILE__), '../templates/config.ru' )
|
41
|
+
FileUtils.cp config_rackup_path, "#{project_path}/config.ru"
|
42
|
+
end
|
43
|
+
|
44
|
+
def open_console
|
45
|
+
require 'rubygems'
|
46
|
+
require 'bundler/setup'
|
47
|
+
require 'yaml'
|
48
|
+
require 'culpa'
|
49
|
+
require 'pry'
|
50
|
+
binding.pry
|
51
|
+
end
|
52
|
+
|
53
|
+
def display_help
|
54
|
+
puts 'Welcome to Culpa ! The MEA framework.'
|
55
|
+
puts 'Usage : culpa [command] [args 1..n]'
|
56
|
+
puts 'Commands supported :'
|
57
|
+
puts ' - new [folder] : Create a new culpa project on the specified folder'
|
58
|
+
puts ' - console : Drop an interactive shell within the running directory'
|
59
|
+
puts ''
|
60
|
+
puts 'This software is distributed under the MIT license, available here : '
|
61
|
+
puts ' - https://raw.githubusercontent.com/HipsterWhale/culpa/master/LICENSE'
|
62
|
+
end
|
63
|
+
|
64
|
+
case ARGV[0]
|
65
|
+
when 'new'
|
66
|
+
create_project ARGV[1]
|
67
|
+
when 'console'
|
68
|
+
open_console
|
69
|
+
else
|
70
|
+
display_help
|
71
|
+
end
|
data/lib/action.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Actions
|
2
|
+
|
3
|
+
class Action
|
4
|
+
|
5
|
+
attr_accessor :to_render
|
6
|
+
|
7
|
+
def initialize(envelope, request)
|
8
|
+
@e = envelope
|
9
|
+
@r = request
|
10
|
+
@to_render = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(response_object, options={})
|
14
|
+
@to_render = {object: response_object, options: options}
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
data/lib/culpa.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'envelope'
|
3
|
+
require 'action'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Culpa
|
7
|
+
|
8
|
+
ROUTE_PATTERNS = {
|
9
|
+
'/:res_name' => {
|
10
|
+
regex: Regexp.new('^/(\w+)$'),
|
11
|
+
id_to_sym: {
|
12
|
+
1 => :res_name
|
13
|
+
}
|
14
|
+
},
|
15
|
+
'/:res_name/:id' => {
|
16
|
+
regex: Regexp.new('^/(\w+)/(\w+)$'),
|
17
|
+
id_to_sym: {
|
18
|
+
1 => :res_name,
|
19
|
+
2 => :id
|
20
|
+
}
|
21
|
+
},
|
22
|
+
'/:res_name/:id/:sub_call' => {
|
23
|
+
regex: Regexp.new('^/(\w+)/(\w+)/(\w+)$'),
|
24
|
+
id_to_sym: {
|
25
|
+
1 => :res_name,
|
26
|
+
2 => :id,
|
27
|
+
3 => :sub_call
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
class Application
|
33
|
+
|
34
|
+
class UnpredictableSubCallError < StandardError; end
|
35
|
+
class NoRenderCalled < StandardError; end
|
36
|
+
|
37
|
+
def initialize(router = {})
|
38
|
+
@router = router
|
39
|
+
end
|
40
|
+
|
41
|
+
# Router's helpers
|
42
|
+
def call_action(options)
|
43
|
+
# Preparing common parts
|
44
|
+
router_method_name = "#{options[:sub_call]}_#{options[:res_name]}"
|
45
|
+
envelope = Envelope.new
|
46
|
+
request = EnvelopeRequest.new(options)
|
47
|
+
|
48
|
+
# The router do not have the key
|
49
|
+
if !@router || !@router.has_key?(router_method_name)
|
50
|
+
default_class = options[:res_name].split('_').map{|w| w.capitalize}.join
|
51
|
+
action_default_class = Actions.const_get(default_class).new(envelope, request)
|
52
|
+
if action_default_class && action_default_class.respond_to?(options[:sub_call])
|
53
|
+
action_default_class.send(options[:sub_call])
|
54
|
+
if action_default_class.to_render
|
55
|
+
return do_render action_default_class.to_render if action_default_class.to_render
|
56
|
+
else
|
57
|
+
raise NoRenderCalled.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
return not_found
|
61
|
+
end
|
62
|
+
|
63
|
+
# The router have the key
|
64
|
+
@router[router_method_name].each do |method|
|
65
|
+
action_class_name, method_name = method.split('.')
|
66
|
+
action_class = Actions.const_get(action_class_name).new(envelope, request)
|
67
|
+
action_class.send method_name
|
68
|
+
return do_render action_class.to_render if action_class.to_render
|
69
|
+
end
|
70
|
+
|
71
|
+
# If nothing called do_render...
|
72
|
+
raise NoRenderCalled.new
|
73
|
+
end
|
74
|
+
|
75
|
+
def do_render(to_render)
|
76
|
+
code = to_render[:options][:status] || 200
|
77
|
+
content_type = to_render[:options][:content_type] || 'application/json'
|
78
|
+
[code.to_s, {'Content-Type' => content_type}, [to_render[:object].to_json]]
|
79
|
+
end
|
80
|
+
|
81
|
+
def call(env)
|
82
|
+
call_options = {
|
83
|
+
verb: env['REQUEST_METHOD'].downcase.to_sym
|
84
|
+
}
|
85
|
+
if env['rack.input'].is_a? Hash
|
86
|
+
if env['rack.input'].has_key? :sub_call
|
87
|
+
call_options[:sub_call] = env['rack.input'][:sub_call]
|
88
|
+
end
|
89
|
+
call_options[:input] = env['rack.input'][:data]
|
90
|
+
else
|
91
|
+
call_options[:input] = env['rack.input']
|
92
|
+
end
|
93
|
+
if !(route_params = parse_path('/:res_name', env['PATH_INFO'])).nil?
|
94
|
+
call_options[:res_name] = route_params[:res_name]
|
95
|
+
case call_options[:verb]
|
96
|
+
when :get
|
97
|
+
call_options[:sub_call] = 'list'
|
98
|
+
when :post
|
99
|
+
call_options[:sub_call] = 'create'
|
100
|
+
else
|
101
|
+
raise UnpredictableSubCallError.new
|
102
|
+
end unless call_options.has_key? :sub_call
|
103
|
+
elsif !(route_params = parse_path('/:res_name/:id', env['PATH_INFO'])).nil?
|
104
|
+
call_options[:res_name] = route_params[:res_name]
|
105
|
+
call_options[:id] = route_params[:id]
|
106
|
+
case call_options[:verb]
|
107
|
+
when :get
|
108
|
+
call_options[:sub_call] = 'get'
|
109
|
+
when :put
|
110
|
+
call_options[:sub_call] = 'update'
|
111
|
+
when :delete
|
112
|
+
call_options[:sub_call] = 'delete'
|
113
|
+
else
|
114
|
+
raise UnpredictableSubCallError.new
|
115
|
+
end unless call_options.has_key? :sub_call
|
116
|
+
elsif !(route_params = parse_path('/:res_name/:id/:sub_call', env['PATH_INFO'])).nil?
|
117
|
+
call_options[:res_name] = route_params[:res_name]
|
118
|
+
call_options[:id] = route_params[:id]
|
119
|
+
call_options[:sub_call] = route_params[:sub_call]
|
120
|
+
else
|
121
|
+
return bad_request
|
122
|
+
end
|
123
|
+
call_action call_options
|
124
|
+
end
|
125
|
+
|
126
|
+
def parse_path(pattern, path)
|
127
|
+
current_pattern = ROUTE_PATTERNS[pattern]
|
128
|
+
result = path.match(current_pattern[:regex])
|
129
|
+
return unless result
|
130
|
+
returned = {}
|
131
|
+
current_pattern[:id_to_sym].each do |id, sym|
|
132
|
+
returned[sym] = result[id]
|
133
|
+
end
|
134
|
+
returned
|
135
|
+
end
|
136
|
+
|
137
|
+
def bad_request
|
138
|
+
['400', {'Content-Type' => 'application/json'}, ['{ "error": "bad_request" }']]
|
139
|
+
end
|
140
|
+
|
141
|
+
def not_found
|
142
|
+
['404', {'Content-Type' => 'application/json'}, ['{ "error": "not_found" }']]
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
data/lib/envelope.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Culpa
|
2
|
+
|
3
|
+
class MustHaveFailed < StandardError; end
|
4
|
+
|
5
|
+
class Envelope
|
6
|
+
|
7
|
+
def must_have!(*args)
|
8
|
+
args.each do |arg|
|
9
|
+
raise MustHaveFailed.new("Envelope must_have! failed on #{arg}") unless instance_variable_defined? "@#{arg}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(sym, *args)
|
14
|
+
sym = sym.to_s
|
15
|
+
if sym.end_with? '='
|
16
|
+
instance_variable_set "@#{sym.gsub('=','')}", args[0]
|
17
|
+
else
|
18
|
+
instance_variable_get "@#{sym.gsub('=','')}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class EnvelopeRequest
|
25
|
+
|
26
|
+
attr_accessor :verb
|
27
|
+
attr_accessor :action
|
28
|
+
attr_accessor :res_name
|
29
|
+
attr_accessor :id
|
30
|
+
attr_accessor :body
|
31
|
+
attr_accessor :input
|
32
|
+
|
33
|
+
def initialize(attributes)
|
34
|
+
attributes.each do |name, value|
|
35
|
+
instance_variable_set "@#{name}", value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
|
data/templates/Gemfile
ADDED
data/templates/config.ru
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'yaml'
|
4
|
+
require 'culpa'
|
5
|
+
|
6
|
+
module Actions
|
7
|
+
Dir['./actions/*.rb'].each do |file|
|
8
|
+
eval(File.open(file).read)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Models
|
13
|
+
Dir['./models/*.rb'].each do |file|
|
14
|
+
eval(File.open(file).read)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
run Culpa::Application.new(YAML.load_file('./config/router.yml'))
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: culpa
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jérémy SEBAN
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.10'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.10'
|
41
|
+
description: Culpa is a try of following MEA principles.
|
42
|
+
email: jeremy@seban.eu
|
43
|
+
executables:
|
44
|
+
- culpa
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- bin/culpa
|
49
|
+
- lib/action.rb
|
50
|
+
- lib/culpa.rb
|
51
|
+
- lib/envelope.rb
|
52
|
+
- templates/Gemfile
|
53
|
+
- templates/config.ru
|
54
|
+
homepage:
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 2.6.4
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: 'Culpa : the MEA framework'
|
78
|
+
test_files: []
|