dandy 0.6.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 +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/dandy.gemspec +46 -0
- data/exe/dandy +5 -0
- data/lib/dandy.rb +6 -0
- data/lib/dandy/app.rb +93 -0
- data/lib/dandy/base/handle_errors.rb +16 -0
- data/lib/dandy/chain.rb +68 -0
- data/lib/dandy/chain_factory.rb +39 -0
- data/lib/dandy/config.rb +15 -0
- data/lib/dandy/errors/dandy_error.rb +4 -0
- data/lib/dandy/errors/syntax_error.rb +6 -0
- data/lib/dandy/errors/view_engine_error.rb +6 -0
- data/lib/dandy/extensions/hash.rb +18 -0
- data/lib/dandy/generators/cli.rb +58 -0
- data/lib/dandy/generators/templates/Gemfile +4 -0
- data/lib/dandy/generators/templates/actions/common/handle_errors.rb +2 -0
- data/lib/dandy/generators/templates/actions/welcome.tt +9 -0
- data/lib/dandy/generators/templates/app/app.rb +4 -0
- data/lib/dandy/generators/templates/app/app.routes +4 -0
- data/lib/dandy/generators/templates/config.ru +17 -0
- data/lib/dandy/generators/templates/silicon.yml +7 -0
- data/lib/dandy/generators/templates/views/show_welcome.json.jbuilder +1 -0
- data/lib/dandy/loaders/dependency_loader.rb +21 -0
- data/lib/dandy/loaders/template_loader.rb +20 -0
- data/lib/dandy/loaders/type_loader.rb +25 -0
- data/lib/dandy/request.rb +65 -0
- data/lib/dandy/routing/builder.rb +96 -0
- data/lib/dandy/routing/file_reader.rb +27 -0
- data/lib/dandy/routing/match.rb +12 -0
- data/lib/dandy/routing/matcher.rb +40 -0
- data/lib/dandy/routing/parser.rb +25 -0
- data/lib/dandy/routing/route.rb +23 -0
- data/lib/dandy/routing/routing.rb +11 -0
- data/lib/dandy/routing/syntax.rb +29 -0
- data/lib/dandy/routing/syntax/action.rb +30 -0
- data/lib/dandy/routing/syntax/actions.rb +15 -0
- data/lib/dandy/routing/syntax/after_section.rb +19 -0
- data/lib/dandy/routing/syntax/before_section.rb +18 -0
- data/lib/dandy/routing/syntax/catch_section.rb +15 -0
- data/lib/dandy/routing/syntax/command.rb +36 -0
- data/lib/dandy/routing/syntax/commands.rb +7 -0
- data/lib/dandy/routing/syntax/node.rb +47 -0
- data/lib/dandy/routing/syntax/nodes.rb +13 -0
- data/lib/dandy/routing/syntax/primitives/arrow.rb +4 -0
- data/lib/dandy/routing/syntax/primitives/back_arrow.rb +4 -0
- data/lib/dandy/routing/syntax/primitives/eol.rb +4 -0
- data/lib/dandy/routing/syntax/primitives/http_status.rb +13 -0
- data/lib/dandy/routing/syntax/primitives/http_verb.rb +4 -0
- data/lib/dandy/routing/syntax/primitives/indent.rb +4 -0
- data/lib/dandy/routing/syntax/primitives/parameter.rb +4 -0
- data/lib/dandy/routing/syntax/primitives/path.rb +5 -0
- data/lib/dandy/routing/syntax/primitives/result_name.rb +4 -0
- data/lib/dandy/routing/syntax/respond.rb +25 -0
- data/lib/dandy/routing/syntax/route.rb +25 -0
- data/lib/dandy/routing/syntax/sections.rb +19 -0
- data/lib/dandy/routing/syntax/tree_section.rb +11 -0
- data/lib/dandy/routing/syntax/view.rb +7 -0
- data/lib/dandy/routing/syntax_error_interpreter.rb +28 -0
- data/lib/dandy/routing/syntax_grammar.tt +99 -0
- data/lib/dandy/template_registry.rb +29 -0
- data/lib/dandy/version.rb +3 -0
- data/lib/dandy/view_builder.rb +18 -0
- data/lib/dandy/view_builder_registry.rb +21 -0
- data/lib/dandy/view_builders/json.rb +16 -0
- data/lib/dandy/view_factory.rb +21 -0
- metadata +288 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'dandy/chain'
|
2
|
+
|
3
|
+
module Dandy
|
4
|
+
class ChainFactory
|
5
|
+
def initialize(container, dandy_config)
|
6
|
+
@container = container
|
7
|
+
@dandy_config = dandy_config
|
8
|
+
end
|
9
|
+
|
10
|
+
def create(match)
|
11
|
+
status = match.route.http_status || default_http_status(match.route.http_verb)
|
12
|
+
register_params(match.params)
|
13
|
+
register_status(status)
|
14
|
+
Chain.new(@container, @dandy_config, match.route.commands, match.route.catch)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def register_params(params)
|
20
|
+
params.keys.each do |key|
|
21
|
+
@container
|
22
|
+
.register_instance(params[key], key.to_sym)
|
23
|
+
.using_lifetime(:scope)
|
24
|
+
.bound_to(:dandy_request)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def register_status(status)
|
29
|
+
@container
|
30
|
+
.register_instance(status, :dandy_status)
|
31
|
+
.using_lifetime(:scope)
|
32
|
+
.bound_to(:dandy_request)
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_http_status(http_verb)
|
36
|
+
http_verb == 'POST' ? 201 : 200
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/dandy/config.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'dandy/extensions/hash'
|
3
|
+
|
4
|
+
module Dandy
|
5
|
+
class Config
|
6
|
+
def initialize(config_file_path)
|
7
|
+
path = File.join(Dir.pwd, config_file_path)
|
8
|
+
@params = YAML.load_file(path).deep_symbolize_keys!
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
@params[key]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Hash
|
2
|
+
# simplecov incorrectly covers activesupport variants of these overloads.
|
3
|
+
# Actually both methods are covered by unit tests.
|
4
|
+
|
5
|
+
# :nocov:
|
6
|
+
def symbolize_keys
|
7
|
+
Hash[map{|(k,v)| [k.to_sym,v]}]
|
8
|
+
end
|
9
|
+
|
10
|
+
def deep_symbolize_keys!
|
11
|
+
keys.each do |key|
|
12
|
+
val = delete(key)
|
13
|
+
self[(key.to_sym rescue key)] = val.is_a?(Hash) || val.is_a?(Array) ? val.deep_symbolize_keys! : val
|
14
|
+
end
|
15
|
+
self
|
16
|
+
end
|
17
|
+
# :nocov:
|
18
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'terminal-table'
|
3
|
+
|
4
|
+
module Dandy
|
5
|
+
class CLI < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
File.dirname(__FILE__)
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'new NAME', 'Create new Dandy application'
|
13
|
+
def new(name)
|
14
|
+
copy_file 'templates/app/app.rb', "#{name}/app/app.rb"
|
15
|
+
copy_file 'templates/app/app.routes', "#{name}/app/app.routes"
|
16
|
+
copy_file 'templates/dandy.yml', "#{name}/dandy.yml"
|
17
|
+
copy_file 'templates/config.ru', "#{name}/config.ru"
|
18
|
+
copy_file 'templates/views/show_welcome.json.jbuilder', "#{name}/app/views/show_welcome.json.jbuilder"
|
19
|
+
copy_file 'templates/actions/common/handle_errors.rb', "#{name}/app/actions/common/handle_errors.rb"
|
20
|
+
copy_file 'templates/Gemfile', "#{name}/Gemfile"
|
21
|
+
template 'templates/actions/welcome.tt', "#{name}/app/actions/welcome.rb", {app_name: name}
|
22
|
+
|
23
|
+
inside name do
|
24
|
+
run 'bundle'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'routes', 'Show Dandy application routes'
|
29
|
+
def routes
|
30
|
+
require './app/app'
|
31
|
+
app = App.new
|
32
|
+
|
33
|
+
headings = ['HTTP Verb', 'Path', 'Action Chain']
|
34
|
+
rows = app.routes.map do |route|
|
35
|
+
indent = ''
|
36
|
+
commands = []
|
37
|
+
route.commands.each_with_index do |c, index|
|
38
|
+
indent += ' ' if c.sequential? && index > 0
|
39
|
+
|
40
|
+
prefix = '->' if c.sequential?
|
41
|
+
prefix = '=>' if c.parallel?
|
42
|
+
prefix = '=*' if c.async?
|
43
|
+
|
44
|
+
commands << "#{indent} #{prefix} #{c.name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
commands = commands.join("\n")
|
49
|
+
[route.http_verb, route.path, commands]
|
50
|
+
end
|
51
|
+
|
52
|
+
table = Terminal::Table.new(headings: headings, rows: rows)
|
53
|
+
table.style = {all_separators: true}
|
54
|
+
puts table
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/cors'
|
3
|
+
require './app/app'
|
4
|
+
|
5
|
+
use Rack::Parser, :content_types => {
|
6
|
+
'application/json' => Proc.new { |body| ::MultiJson.decode body }
|
7
|
+
}
|
8
|
+
|
9
|
+
use Rack::Cors do
|
10
|
+
# Setup your CORS policy
|
11
|
+
end
|
12
|
+
|
13
|
+
if ENV['DANDY_ENV'] == 'development' || ENV['DANDY_ENV'] == ''
|
14
|
+
use Rack::Reloader
|
15
|
+
end
|
16
|
+
|
17
|
+
run App.new
|
@@ -0,0 +1 @@
|
|
1
|
+
json.message @message
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dandy
|
2
|
+
class DependencyLoader
|
3
|
+
def initialize(container, type_loader, dandy_env)
|
4
|
+
@container = container
|
5
|
+
@type_loader = type_loader
|
6
|
+
@types = type_loader.load_types
|
7
|
+
@dandy_env = dandy_env
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_components
|
11
|
+
if @dandy_env == 'development'
|
12
|
+
# every time reload types in development mode
|
13
|
+
@types = @type_loader.load_types
|
14
|
+
end
|
15
|
+
|
16
|
+
@types.each do |type|
|
17
|
+
@container.register(type).using_lifetime(:scope).bound_to(:dandy_request)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Dandy
|
2
|
+
class TemplateLoader
|
3
|
+
def initialize(dandy_config)
|
4
|
+
@directory = File.join(dandy_config[:path][:views], '**/*')
|
5
|
+
end
|
6
|
+
|
7
|
+
def load_templates
|
8
|
+
result = {}
|
9
|
+
|
10
|
+
files = Dir.glob(@directory).reject {|file_path| File.directory? file_path}
|
11
|
+
files.each do |file|
|
12
|
+
path = File.join Dir.pwd, file
|
13
|
+
content = File.read(path)
|
14
|
+
result[file] = content
|
15
|
+
end
|
16
|
+
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Dandy
|
2
|
+
class TypeLoader
|
3
|
+
def initialize(dandy_config)
|
4
|
+
@directories = dandy_config[:path][:dependencies]
|
5
|
+
end
|
6
|
+
|
7
|
+
def load_types
|
8
|
+
types = []
|
9
|
+
@directories.each do |directory|
|
10
|
+
dir = File.join(directory, '**/*')
|
11
|
+
files = Dir.glob(dir).reject {|file_path| File.directory? file_path}
|
12
|
+
|
13
|
+
files.each do |file|
|
14
|
+
path = File.join Dir.pwd, file
|
15
|
+
require path
|
16
|
+
file_name = File.basename(file).gsub(File.extname(file), '')
|
17
|
+
class_name = file_name.split('_').each(&:capitalize!).join('')
|
18
|
+
types << Object.const_get(class_name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
types
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'dandy/chain_factory'
|
2
|
+
require 'dandy/view_factory'
|
3
|
+
|
4
|
+
module Dandy
|
5
|
+
class Request
|
6
|
+
include Hypo::Scope
|
7
|
+
|
8
|
+
def initialize(route_matcher, container, chain_factory, view_factory)
|
9
|
+
@container = container
|
10
|
+
@route_matcher = route_matcher
|
11
|
+
@chain_factory = chain_factory
|
12
|
+
@view_factory = view_factory
|
13
|
+
end
|
14
|
+
|
15
|
+
def handle(rack_env)
|
16
|
+
create_scope
|
17
|
+
|
18
|
+
path = rack_env['PATH_INFO']
|
19
|
+
request_method = rack_env['REQUEST_METHOD']
|
20
|
+
match = @route_matcher.match(path, request_method)
|
21
|
+
content_type = rack_env['CONTENT_TYPE'] || 'application/json'
|
22
|
+
|
23
|
+
if match.nil?
|
24
|
+
result = [404, { 'Content-Type' => content_type }, []]
|
25
|
+
else
|
26
|
+
query = Rack::Utils.parse_nested_query(rack_env['QUERY_STRING']).symbolize_keys
|
27
|
+
register_params(query, :dandy_query)
|
28
|
+
|
29
|
+
data = rack_env['rack.parser.result']
|
30
|
+
register_params(data, :dandy_data)
|
31
|
+
|
32
|
+
chain = @chain_factory.create(match)
|
33
|
+
chain.execute
|
34
|
+
|
35
|
+
body = ''
|
36
|
+
if match.route.view
|
37
|
+
body = @view_factory.create(match.route.view, content_type)
|
38
|
+
end
|
39
|
+
|
40
|
+
status = @container.resolve(:dandy_status)
|
41
|
+
result = [status, { 'Content-Type' => content_type }, [body]]
|
42
|
+
end
|
43
|
+
|
44
|
+
release
|
45
|
+
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def create_scope
|
51
|
+
@container
|
52
|
+
.register_instance(self, :dandy_request)
|
53
|
+
.using_lifetime(:scope)
|
54
|
+
.bound_to(self)
|
55
|
+
end
|
56
|
+
|
57
|
+
def register_params(params, name)
|
58
|
+
unless params.nil?
|
59
|
+
@container.register_instance(params, name)
|
60
|
+
.using_lifetime(:scope)
|
61
|
+
.bound_to(:dandy_request)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'dandy/routing/route'
|
2
|
+
|
3
|
+
module Dandy
|
4
|
+
module Routing
|
5
|
+
class Builder
|
6
|
+
def initialize
|
7
|
+
@parsed_items = []
|
8
|
+
@route_params = []
|
9
|
+
@current_parent = nil
|
10
|
+
@prev_route = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def build(sections)
|
14
|
+
result = []
|
15
|
+
|
16
|
+
node = sections.node
|
17
|
+
restore_hierarchy(node, nil, node.to_hash)
|
18
|
+
|
19
|
+
@route_params.each do |route|
|
20
|
+
if route[:actions]
|
21
|
+
route[:actions].each do |action|
|
22
|
+
result << Route.new({
|
23
|
+
path: restore_path(route).gsub('$', '/$'),
|
24
|
+
params: restore_params(route).map{|p| p.sub('$', '')},
|
25
|
+
http_verb: action[:http_verb],
|
26
|
+
view: action[:view],
|
27
|
+
http_status: action[:http_status],
|
28
|
+
catch: sections.catch.command,
|
29
|
+
commands: restore_callbacks(route, :before) + action[:commands] + restore_callbacks(route, :after)
|
30
|
+
})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def restore_hierarchy(node, prev_route, current_parent)
|
41
|
+
route = node.to_hash
|
42
|
+
|
43
|
+
if route[:level] == current_parent[:level]
|
44
|
+
route[:parent] = current_parent[:parent]
|
45
|
+
parent = route
|
46
|
+
elsif route[:level] > prev_route[:level]
|
47
|
+
route[:parent] = prev_route
|
48
|
+
parent = prev_route
|
49
|
+
else
|
50
|
+
route[:parent] = current_parent
|
51
|
+
parent = current_parent
|
52
|
+
end
|
53
|
+
|
54
|
+
@route_params << route
|
55
|
+
@prev_route = route
|
56
|
+
|
57
|
+
if node.my_nodes
|
58
|
+
node.my_nodes.each {|n| restore_hierarchy(n, route, parent)}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def restore_path(route)
|
63
|
+
path = (route[:route][:path] || route[:route][:parameter] || '')
|
64
|
+
if route[:parent]
|
65
|
+
restore_path(route[:parent]) + path
|
66
|
+
else
|
67
|
+
path
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def restore_params(route)
|
72
|
+
if route[:route][:parameter]
|
73
|
+
param = [route[:route][:parameter]]
|
74
|
+
else
|
75
|
+
param = []
|
76
|
+
end
|
77
|
+
|
78
|
+
if route[:parent]
|
79
|
+
restore_params(route[:parent]) + param
|
80
|
+
else
|
81
|
+
param
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def restore_callbacks(route, type)
|
86
|
+
before = route[type]
|
87
|
+
|
88
|
+
if route[:parent]
|
89
|
+
restore_callbacks(route[:parent], type) + before
|
90
|
+
else
|
91
|
+
before
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|