angus 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/angus +13 -0
- data/lib/angus.rb +6 -4
- data/lib/angus/base.rb +119 -0
- data/lib/angus/base_actions.rb +38 -0
- data/lib/angus/base_resource.rb +14 -0
- data/lib/angus/command.rb +27 -0
- data/lib/angus/generator.rb +141 -0
- data/lib/angus/generator/templates/Gemfile +5 -0
- data/lib/angus/generator/templates/README.md +0 -0
- data/lib/angus/generator/templates/config.ru.erb +10 -0
- data/lib/angus/generator/templates/definitions/messages.yml +0 -0
- data/lib/angus/generator/templates/definitions/operations.yml.erb +21 -0
- data/lib/angus/generator/templates/definitions/representations.yml +0 -0
- data/lib/angus/generator/templates/definitions/service.yml.erb +3 -0
- data/lib/angus/generator/templates/resources/resource.rb.erb +6 -0
- data/lib/angus/generator/templates/services/service.rb.erb +4 -0
- data/lib/angus/marshallings/base.rb +2 -0
- data/lib/angus/marshallings/marshalling.rb +136 -0
- data/lib/angus/marshallings/unmarshalling.rb +29 -0
- data/lib/angus/renders/base.rb +2 -0
- data/lib/angus/renders/html_render.rb +9 -0
- data/lib/angus/renders/json_render.rb +15 -0
- data/lib/angus/request_handler.rb +62 -0
- data/lib/angus/resource_definition.rb +78 -0
- data/lib/angus/response.rb +53 -0
- data/lib/angus/responses.rb +232 -0
- data/lib/angus/rspec/spec_helper.rb +3 -0
- data/lib/angus/rspec/support/examples.rb +64 -0
- data/lib/angus/rspec/support/examples/describe_errors.rb +36 -0
- data/lib/angus/rspec/support/matchers/operation_response_matchers.rb +117 -0
- data/lib/angus/rspec/support/operation_response.rb +57 -0
- data/lib/angus/utils.rb +2 -0
- data/lib/angus/utils/params.rb +24 -0
- data/lib/angus/utils/string.rb +27 -0
- data/lib/angus/version.rb +2 -2
- data/spec/angus/rspec/examples/describe_errors_spec.rb +64 -0
- data/spec/spec_helper.rb +27 -0
- metadata +181 -19
- data/.gitignore +0 -17
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -22
- data/README.md +0 -29
- data/Rakefile +0 -1
- data/angus.gemspec +0 -23
data/bin/angus
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
# resolve bin path, ignoring symlinks
|
5
|
+
require 'pathname'
|
6
|
+
bin_file = Pathname.new(__FILE__).realpath
|
7
|
+
|
8
|
+
# add self to libpath
|
9
|
+
$:.unshift File.expand_path('../../lib', bin_file)
|
10
|
+
|
11
|
+
require 'angus/command'
|
12
|
+
|
13
|
+
Angus::Command.start
|
data/lib/angus.rb
CHANGED
data/lib/angus/base.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require_relative 'resource_definition'
|
5
|
+
require_relative 'request_handler'
|
6
|
+
require_relative 'base_resource'
|
7
|
+
require_relative 'responses'
|
8
|
+
|
9
|
+
require_relative 'renders/base'
|
10
|
+
require_relative 'marshallings/base'
|
11
|
+
require_relative 'base_actions'
|
12
|
+
|
13
|
+
require 'angus/sdoc'
|
14
|
+
|
15
|
+
module Angus
|
16
|
+
class Base < RequestHandler
|
17
|
+
include BaseActions
|
18
|
+
|
19
|
+
FIRST_VERSION = '0.1'
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
super
|
23
|
+
|
24
|
+
@resources_definitions = []
|
25
|
+
@version = FIRST_VERSION
|
26
|
+
@name = self.class.name.downcase
|
27
|
+
@configured = false
|
28
|
+
@definitions = nil
|
29
|
+
@logger = Logger.new(STDOUT)
|
30
|
+
|
31
|
+
configure!
|
32
|
+
|
33
|
+
register_base_routes
|
34
|
+
register_resources_routes
|
35
|
+
end
|
36
|
+
|
37
|
+
def register_resources_routes
|
38
|
+
@resources_definitions.each do |resource|
|
39
|
+
register_resource_routes(resource)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def configured?
|
44
|
+
@configured
|
45
|
+
end
|
46
|
+
|
47
|
+
# TODO ver que hacer
|
48
|
+
def configure
|
49
|
+
end
|
50
|
+
|
51
|
+
def configure!
|
52
|
+
raise 'Already configured' if configured?
|
53
|
+
|
54
|
+
# TODO ver como hacer configurable
|
55
|
+
@definitions = Angus::SDoc::DefinitionsReader.service_definition('definitions')
|
56
|
+
|
57
|
+
configure
|
58
|
+
|
59
|
+
@configured = true
|
60
|
+
end
|
61
|
+
|
62
|
+
def service_code_name
|
63
|
+
@definitions.code_name
|
64
|
+
end
|
65
|
+
|
66
|
+
def service_version
|
67
|
+
@definitions.version
|
68
|
+
end
|
69
|
+
|
70
|
+
def register(resource_name, options = {})
|
71
|
+
resource_definition = ResourceDefinition.new(resource_name, @definitions)
|
72
|
+
|
73
|
+
@resources_definitions << resource_definition
|
74
|
+
end
|
75
|
+
|
76
|
+
def base_path
|
77
|
+
"/#{service_code_name}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def register_resource_routes(resource_definition)
|
81
|
+
resource_definition.operations.each do |operation|
|
82
|
+
method = operation.method.to_sym
|
83
|
+
op_path = "#{api_path}#{operation.path}"
|
84
|
+
|
85
|
+
response_metadata = resource_definition.build_response_metadata(operation.response_elements)
|
86
|
+
|
87
|
+
router.on(method, op_path) do |env, params|
|
88
|
+
request = Rack::Request.new(env)
|
89
|
+
params = Params.indifferent_params(params)
|
90
|
+
|
91
|
+
resource = resource_definition.resource_class.new(request, params)
|
92
|
+
|
93
|
+
begin
|
94
|
+
response = resource.send(operation.code_name)
|
95
|
+
|
96
|
+
response = {} unless response.is_a?(Hash)
|
97
|
+
|
98
|
+
messages = response.delete(:messages)
|
99
|
+
|
100
|
+
response = build_data_response(response, response_metadata, messages)
|
101
|
+
|
102
|
+
@response.write(response)
|
103
|
+
rescue Exception => error
|
104
|
+
status_code = get_error_status_code(error)
|
105
|
+
if status_code == Angus::Responses::HTTP_STATUS_CODE_INTERNAL_SERVER_ERROR
|
106
|
+
@logger.error("An exception occurs on #{resource.class.name}##{operation.code_name}")
|
107
|
+
@logger.error(error)
|
108
|
+
end
|
109
|
+
response = build_error_response(error)
|
110
|
+
|
111
|
+
@response.status = status_code
|
112
|
+
@response.write(response)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Angus
|
2
|
+
module BaseActions
|
3
|
+
|
4
|
+
def discover_paths
|
5
|
+
{
|
6
|
+
'doc' => doc_path,
|
7
|
+
'api' => api_path
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_base_routes
|
12
|
+
router.on(:get, '/') do
|
13
|
+
render discover_paths
|
14
|
+
end
|
15
|
+
|
16
|
+
router.on(:get, base_path) do
|
17
|
+
render discover_paths
|
18
|
+
end
|
19
|
+
|
20
|
+
router.on(:get, doc_path) do |env, params|
|
21
|
+
if params[:format] == 'json'
|
22
|
+
render(Angus::SDoc::JsonFormatter.format_service(@definitions), format: :json)
|
23
|
+
else
|
24
|
+
render(Angus::SDoc::HtmlFormatter.format_service(@definitions), format: :html)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def doc_path
|
30
|
+
"#{base_path}/doc/#{service_version}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def api_path
|
34
|
+
"#{base_path}/api/#{service_version}"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
require_relative 'generator'
|
5
|
+
|
6
|
+
module Angus
|
7
|
+
class Command < Thor
|
8
|
+
|
9
|
+
desc 'new [NAME]', 'Generate a new service'
|
10
|
+
def new(name)
|
11
|
+
generator.new_service(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'resource [NAME]', 'Generate a new resource'
|
15
|
+
method_option :actions, aliases: '-a', type: :array, desc: 'Generate the given actions for the resource'
|
16
|
+
def resource(name)
|
17
|
+
generator.resource(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def generator
|
23
|
+
@generator ||= Angus::Generator.new
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'thor'
|
3
|
+
require 'thor/actions'
|
4
|
+
|
5
|
+
module Angus
|
6
|
+
class Generator < Thor
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
RESOURCES_DIR = 'resources'
|
10
|
+
DEFINITIONS_DIR = 'definitions'
|
11
|
+
SERVICES_DIR = 'services'
|
12
|
+
|
13
|
+
NEW_APP_DIRECTORIES = %W(#{DEFINITIONS_DIR} #{RESOURCES_DIR} #{SERVICES_DIR})
|
14
|
+
|
15
|
+
NEW_APP_FILES = %w(
|
16
|
+
Gemfile
|
17
|
+
config.ru.erb
|
18
|
+
services/service.rb.erb
|
19
|
+
definitions/messages.yml
|
20
|
+
definitions/representations.yml
|
21
|
+
definitions/service.yml.erb
|
22
|
+
)
|
23
|
+
|
24
|
+
FILE_MAPPINGS = {
|
25
|
+
'services/service.rb.erb' => -> command, app_name { File.join(SERVICES_DIR, "#{command.underscore(command.classify(app_name))}.rb") },
|
26
|
+
'resources/resource.rb.erb' => -> command, name { "#{command.underscore(command.classify(name))}.rb" },
|
27
|
+
'definitions/operations.yml.erb' => -> command, _ { File.join(command.resource_name, 'operations.yml') }
|
28
|
+
}
|
29
|
+
|
30
|
+
no_commands do
|
31
|
+
def new_service(name)
|
32
|
+
app_name(name)
|
33
|
+
|
34
|
+
empty_directory(app_name)
|
35
|
+
|
36
|
+
NEW_APP_DIRECTORIES.each do |directory|
|
37
|
+
empty_directory(File.join(app_name, directory))
|
38
|
+
end
|
39
|
+
|
40
|
+
NEW_APP_FILES.each do |file|
|
41
|
+
if is_erb?(file)
|
42
|
+
copy_erb_file(file, app_name)
|
43
|
+
else
|
44
|
+
copy_file(file, File.join(app_name, file))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def resource(name)
|
50
|
+
resource_name(name)
|
51
|
+
resource_actions(options[:actions])
|
52
|
+
|
53
|
+
empty_directory(RESOURCES_DIR)
|
54
|
+
empty_directory(DEFINITIONS_DIR)
|
55
|
+
|
56
|
+
copy_erb_file('resources/resource.rb.erb', resource_name, RESOURCES_DIR)
|
57
|
+
copy_erb_file('definitions/operations.yml.erb', resource_name, DEFINITIONS_DIR)
|
58
|
+
insert_into_services(" register :#{resource_name}\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
def app_name(name = nil)
|
62
|
+
if name.nil?
|
63
|
+
@app_name
|
64
|
+
else
|
65
|
+
@app_name = name
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def resource_name(name = nil)
|
70
|
+
if name.nil?
|
71
|
+
@resource_name
|
72
|
+
else
|
73
|
+
@resource_name = name
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def resource_actions(actions = nil)
|
78
|
+
if actions.nil?
|
79
|
+
@resource_actions || []
|
80
|
+
else
|
81
|
+
@resource_actions = actions
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def classify(string)
|
86
|
+
string.sub(/^[a-z\d]*/) { $&.capitalize }.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
|
87
|
+
end
|
88
|
+
|
89
|
+
def underscore(camel_cased_word)
|
90
|
+
word = camel_cased_word.to_s.dup
|
91
|
+
word.gsub!(/::/, '/')
|
92
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
93
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
94
|
+
word.tr!('-', '_')
|
95
|
+
word.downcase!
|
96
|
+
word
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def is_erb?(file)
|
103
|
+
file.end_with?('.erb')
|
104
|
+
end
|
105
|
+
|
106
|
+
def copy_erb_file(file, name, base_path = nil)
|
107
|
+
base_path = name if base_path.nil?
|
108
|
+
|
109
|
+
tmp_file = Tempfile.new(File.basename(file))
|
110
|
+
|
111
|
+
source = File.expand_path(find_in_source_paths(file.to_s))
|
112
|
+
content = ERB.new(File.binread(source)).result(binding)
|
113
|
+
|
114
|
+
File.open(tmp_file.path, 'w') { |f| f << content }
|
115
|
+
tmp_file.close
|
116
|
+
|
117
|
+
copy_file(tmp_file.path, File.join(base_path, filename_resolver(file, name)))
|
118
|
+
end
|
119
|
+
|
120
|
+
def insert_into_services(content)
|
121
|
+
config = {}
|
122
|
+
config.merge!(:after => /def configure\n|def configure .*\n/)
|
123
|
+
|
124
|
+
file = Dir[File.join(Dir.pwd, SERVICES_DIR, '*.*')].first
|
125
|
+
|
126
|
+
insert_into_file(file, *([content] << config))
|
127
|
+
end
|
128
|
+
|
129
|
+
def filename_resolver(file, app_name)
|
130
|
+
if FILE_MAPPINGS[file].nil?
|
131
|
+
file.gsub('.erb', '')
|
132
|
+
else
|
133
|
+
FILE_MAPPINGS[file].call(self, app_name)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
Angus::Generator.source_root(File.join(File.dirname(File.expand_path(__FILE__)), 'generator',
|
141
|
+
'templates'))
|
File without changes
|
File without changes
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<% resource_actions.each do |action_name| %>
|
2
|
+
<%= action_name%>:
|
3
|
+
name:
|
4
|
+
description: |
|
5
|
+
|
6
|
+
path:
|
7
|
+
method:
|
8
|
+
uri:
|
9
|
+
- element:
|
10
|
+
description:
|
11
|
+
|
12
|
+
response:
|
13
|
+
- element:
|
14
|
+
description:
|
15
|
+
required:
|
16
|
+
type:
|
17
|
+
|
18
|
+
messages:
|
19
|
+
- key:
|
20
|
+
description:
|
21
|
+
<% end %>
|
File without changes
|