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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 35729ef648356d307df4cbb9a49aa1df7e053949
|
4
|
+
data.tar.gz: 695e1200d2c7066ed4f989842322437c5635779b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 52323c799596e094a5f2dd26a3a0b178f5016696fa4cd7cf33b0df4add8209f5fda43e908cdd1cfa26024bca443f599e048ecd7f17d85def852a3797e111b71b
|
7
|
+
data.tar.gz: 37757ebdc264e506788c398c0c525dac7f2b2cd882b27501283434faa8b0c60beeb813b85eb885eecacdac0f904e54f0bc34d35c527f725d1854c9275b4bada5
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Vladimir Kalinkin
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# Dandy
|
2
|
+
|
3
|
+
Dandy is a minimalistic web API framework. Its main idea is to implement an approach
|
4
|
+
from Clean Architecture principles - "web is just a delivery mechanism".
|
5
|
+
Dandy is build on top of IoC container Hypo and forces to use dependency injection
|
6
|
+
approach everywhere.
|
7
|
+
|
8
|
+
## Basic Concepts
|
9
|
+
|
10
|
+
1. [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) is a heart of Dandy. Atomic actions can depend on request parameters, services, repositories, output of other
|
11
|
+
actions and other stuff. You can easily inject such dependencies in your actions through a constructor.
|
12
|
+
Dependency Injection significantly improves development experience: isolate your components, enjoy writing unit tests.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class LoadPost
|
16
|
+
def initialize(id, post_storage)
|
17
|
+
@id = id
|
18
|
+
@storage = post_storage
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
@storage.find(@id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
2. Instead of boring Ruby block-style routes definition like in Rails, Sinatra and others Dandy uses its
|
28
|
+
own language for that. Small example:
|
29
|
+
|
30
|
+
```
|
31
|
+
:receive
|
32
|
+
.->
|
33
|
+
:before -> user@load_current_user
|
34
|
+
/posts ->
|
35
|
+
$id ->
|
36
|
+
:before -> load_post
|
37
|
+
/comments ->
|
38
|
+
POST -> add_comment -> notify_author -> notify_subscribers -> :respond <- comment_test =201
|
39
|
+
:catch -> handle_errors
|
40
|
+
```
|
41
|
+
|
42
|
+
3. The combination of flexible router and dependency injection breaks existing dogmas.
|
43
|
+
Dandy framework introduces Abstract Chain pattern as a replacement for Model-View-Controller
|
44
|
+
and other ancient approaches. Every request handles by a set of atomic actions.
|
45
|
+
|
46
|
+
```
|
47
|
+
# POST /posts/$id/comments
|
48
|
+
... -> load_post -> add_comment -> ...
|
49
|
+
```
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class LoadPost
|
53
|
+
def initialize(id, post_storage)
|
54
|
+
@id = id
|
55
|
+
@storage = post_storage
|
56
|
+
end
|
57
|
+
|
58
|
+
def call
|
59
|
+
@storage.find(@id)
|
60
|
+
end
|
61
|
+
|
62
|
+
def result_name
|
63
|
+
'post'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class AddComment
|
68
|
+
def initialize(post, dandy_data, user, comment_storage)
|
69
|
+
@post = post
|
70
|
+
@data = dandy_data
|
71
|
+
@user = user
|
72
|
+
@storage = comment_storage
|
73
|
+
end
|
74
|
+
|
75
|
+
def call
|
76
|
+
@storage.create(post: @post, message: @data[:message], user: @user)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
4. Dandy is a micro-framework for micro-services. It's not intended to create monolithic giants!
|
82
|
+
In terms of [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design)
|
83
|
+
concepts one Dandy application should wrap only one [Bounded Context](https://en.wikipedia.org/wiki/Domain-driven_design#Bounded_context).
|
84
|
+
|
85
|
+
## Getting Started
|
86
|
+
|
87
|
+
1. Install Dandy:
|
88
|
+
|
89
|
+
```
|
90
|
+
$ gem install dandy
|
91
|
+
```
|
92
|
+
|
93
|
+
2. At the command prompt, create a new Dandy application:
|
94
|
+
|
95
|
+
```
|
96
|
+
$ dandy new dandy-app
|
97
|
+
```
|
98
|
+
|
99
|
+
3. Go to directory `dandy-app` and start the application using a server you prefer:
|
100
|
+
|
101
|
+
```
|
102
|
+
$ puma -p 8000 config.ru
|
103
|
+
```
|
104
|
+
|
105
|
+
or just
|
106
|
+
|
107
|
+
```
|
108
|
+
$ rackup -p 8000 config.ru
|
109
|
+
```
|
110
|
+
|
111
|
+
4. Using a browser, go to http://localhost:8000 and you'll see:
|
112
|
+
|
113
|
+
```json
|
114
|
+
{"message": "Welcome to dandy-app!"}
|
115
|
+
```
|
116
|
+
|
117
|
+
5. Investigate example application code, it will explain most of Dandy aspects.
|
118
|
+
6. For more details visit our [Wiki](https://github.com/cylon-v/dandy/wiki).
|
119
|
+
|
120
|
+
## Development
|
121
|
+
|
122
|
+
Usual, but always helpful steps:
|
123
|
+
|
124
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
125
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
126
|
+
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
127
|
+
|
128
|
+
## Contributing
|
129
|
+
|
130
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cylon-v/dandy.
|
131
|
+
|
132
|
+
## License
|
133
|
+
|
134
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/dandy.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dandy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'dandy'
|
8
|
+
spec.version = Dandy::VERSION
|
9
|
+
spec.authors = ['Vladimir Kalinkin']
|
10
|
+
spec.email = ['vova.kalinkin@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Dandy is a minimalistic web API framework.'
|
13
|
+
spec.description = 'The philosophy of Dandy is to provide minimalistic' \
|
14
|
+
' middleware between your model and API client.'
|
15
|
+
|
16
|
+
spec.homepage = 'https://github.com/cylon-v/dandy'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
21
|
+
else
|
22
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
23
|
+
'public gem pushes.'
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = 'exe'
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ['lib']
|
32
|
+
|
33
|
+
spec.add_dependency 'hypo', '~> 0.8.5'
|
34
|
+
spec.add_dependency 'rack', '~> 2.0.3'
|
35
|
+
spec.add_dependency 'thor', '~> 0.20.0'
|
36
|
+
spec.add_dependency 'treetop', '~> 1.6.8'
|
37
|
+
spec.add_dependency 'jbuilder', '~> 2.7.0'
|
38
|
+
spec.add_dependency 'rack-parser', '~> 0.7.0'
|
39
|
+
spec.add_dependency 'terminal-table', '~> 1.8.0'
|
40
|
+
|
41
|
+
spec.add_development_dependency 'bundler', '~> 1.15'
|
42
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
43
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
|
+
spec.add_development_dependency 'rspec-mocks', '~> 3.6'
|
45
|
+
spec.add_development_dependency 'simplecov', '~> 0.15'
|
46
|
+
end
|
data/exe/dandy
ADDED
data/lib/dandy.rb
ADDED
data/lib/dandy/app.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'hypo'
|
2
|
+
require 'json'
|
3
|
+
require 'rack/parser'
|
4
|
+
require 'dandy/loaders/type_loader'
|
5
|
+
require 'dandy/loaders/dependency_loader'
|
6
|
+
require 'dandy/loaders/template_loader'
|
7
|
+
require 'dandy/config'
|
8
|
+
require 'dandy/request'
|
9
|
+
require 'dandy/template_registry'
|
10
|
+
require 'dandy/view_builder_registry'
|
11
|
+
require 'dandy/view_factory'
|
12
|
+
require 'dandy/chain_factory'
|
13
|
+
require 'dandy/view_builders/json'
|
14
|
+
require 'dandy/routing/routing'
|
15
|
+
|
16
|
+
module Dandy
|
17
|
+
class App
|
18
|
+
attr_reader :routes
|
19
|
+
|
20
|
+
def initialize(container = Hypo::Container.new)
|
21
|
+
@container = container
|
22
|
+
|
23
|
+
register_dependencies
|
24
|
+
load_basic_dependencies
|
25
|
+
parse_routes
|
26
|
+
add_view_builders
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
request = Request.new(@route_matcher, @container, @chain_factory, @view_factory)
|
31
|
+
request.handle(env)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def add_view_builder(view_builder, format)
|
37
|
+
@view_builder_registry.add(view_builder, format)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def add_view_builders
|
43
|
+
add_view_builder(ViewBuilders::Json, 'json')
|
44
|
+
end
|
45
|
+
|
46
|
+
def register_dependencies
|
47
|
+
instances = {
|
48
|
+
config_file_path: 'dandy.yml',
|
49
|
+
dandy_env: ENV['DANDY_ENV'] || 'development'
|
50
|
+
}
|
51
|
+
|
52
|
+
instances.keys.each do |name|
|
53
|
+
@container.register_instance(instances[name], name)
|
54
|
+
end
|
55
|
+
|
56
|
+
singletons = {
|
57
|
+
dandy_config: Config,
|
58
|
+
type_loader: TypeLoader,
|
59
|
+
dependency_loader: DependencyLoader,
|
60
|
+
template_loader: TemplateLoader,
|
61
|
+
template_registry: TemplateRegistry,
|
62
|
+
view_builder_registry: ViewBuilderRegistry,
|
63
|
+
view_factory: ViewFactory,
|
64
|
+
chain_factory: ChainFactory,
|
65
|
+
file_reader: Routing::FileReader,
|
66
|
+
syntax_parser: SyntaxParser,
|
67
|
+
syntax_error_interpreter: Routing::SyntaxErrorInterpreter,
|
68
|
+
routes_builder: Routing::Builder,
|
69
|
+
route_parser: Routing::Parser
|
70
|
+
}
|
71
|
+
|
72
|
+
singletons.keys.each do |name|
|
73
|
+
@container.register(singletons[name], name)
|
74
|
+
.using_lifetime(:singleton)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_basic_dependencies
|
79
|
+
@chain_factory = @container.resolve(:chain_factory)
|
80
|
+
@view_factory = @container.resolve(:view_factory)
|
81
|
+
@dependency_loader = @container.resolve(:dependency_loader)
|
82
|
+
@view_builder_registry = @container.resolve(:view_builder_registry)
|
83
|
+
@route_parser = @container.resolve(:route_parser)
|
84
|
+
|
85
|
+
@dependency_loader.load_components
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_routes
|
89
|
+
@routes = @route_parser.parse
|
90
|
+
@route_matcher = Routing::Matcher.new(@routes)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Dandy
|
2
|
+
class HandleErrors
|
3
|
+
def initialize(container, dandy_error)
|
4
|
+
@container = container
|
5
|
+
@dandy_error = dandy_error
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
def set_http_status(status_code)
|
10
|
+
@container
|
11
|
+
.register_instance(status_code, :dandy_status)
|
12
|
+
.using_lifetime(:scope)
|
13
|
+
.bound_to(:dandy_request)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/dandy/chain.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Dandy
|
4
|
+
class Chain
|
5
|
+
def initialize(container, dandy_config, commands, catch_command = nil)
|
6
|
+
@commands = commands
|
7
|
+
@container = container
|
8
|
+
@catch_command = catch_command
|
9
|
+
@async_timeout = dandy_config[:action][:async_timeout]
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
if @catch_command.nil?
|
14
|
+
run_commands
|
15
|
+
else
|
16
|
+
begin
|
17
|
+
run_commands
|
18
|
+
rescue Exception => error
|
19
|
+
@container
|
20
|
+
.register_instance(error, :dandy_error)
|
21
|
+
.using_lifetime(:scope)
|
22
|
+
.bound_to(:dandy_request)
|
23
|
+
|
24
|
+
action = @container.resolve(@catch_command.name.to_sym)
|
25
|
+
action.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def run_commands
|
33
|
+
threads = []
|
34
|
+
Thread.abort_on_exception = true
|
35
|
+
@commands.each_with_index do |command, index|
|
36
|
+
if command.sequential?
|
37
|
+
# all previous parallel commands should be done before the current sequential
|
38
|
+
threads.each {|t| t.join}
|
39
|
+
threads = []
|
40
|
+
|
41
|
+
run_command(command)
|
42
|
+
else
|
43
|
+
thread = Thread.new {
|
44
|
+
Timeout::timeout(@async_timeout) {
|
45
|
+
run_command(command)
|
46
|
+
}
|
47
|
+
}
|
48
|
+
threads << thread if command.parallel?
|
49
|
+
end
|
50
|
+
|
51
|
+
# if it's last item in chain then wait until parallel commands are done
|
52
|
+
if index == @commands.length - 1
|
53
|
+
threads.each {|t| t.join}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_command(command)
|
59
|
+
action = @container.resolve(command.name.to_sym)
|
60
|
+
result = action.call
|
61
|
+
|
62
|
+
@container
|
63
|
+
.register_instance(result, command.result_name.to_sym)
|
64
|
+
.using_lifetime(:scope)
|
65
|
+
.bound_to(:dandy_request)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|