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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +134 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +7 -0
  10. data/bin/setup +8 -0
  11. data/dandy.gemspec +46 -0
  12. data/exe/dandy +5 -0
  13. data/lib/dandy.rb +6 -0
  14. data/lib/dandy/app.rb +93 -0
  15. data/lib/dandy/base/handle_errors.rb +16 -0
  16. data/lib/dandy/chain.rb +68 -0
  17. data/lib/dandy/chain_factory.rb +39 -0
  18. data/lib/dandy/config.rb +15 -0
  19. data/lib/dandy/errors/dandy_error.rb +4 -0
  20. data/lib/dandy/errors/syntax_error.rb +6 -0
  21. data/lib/dandy/errors/view_engine_error.rb +6 -0
  22. data/lib/dandy/extensions/hash.rb +18 -0
  23. data/lib/dandy/generators/cli.rb +58 -0
  24. data/lib/dandy/generators/templates/Gemfile +4 -0
  25. data/lib/dandy/generators/templates/actions/common/handle_errors.rb +2 -0
  26. data/lib/dandy/generators/templates/actions/welcome.tt +9 -0
  27. data/lib/dandy/generators/templates/app/app.rb +4 -0
  28. data/lib/dandy/generators/templates/app/app.routes +4 -0
  29. data/lib/dandy/generators/templates/config.ru +17 -0
  30. data/lib/dandy/generators/templates/silicon.yml +7 -0
  31. data/lib/dandy/generators/templates/views/show_welcome.json.jbuilder +1 -0
  32. data/lib/dandy/loaders/dependency_loader.rb +21 -0
  33. data/lib/dandy/loaders/template_loader.rb +20 -0
  34. data/lib/dandy/loaders/type_loader.rb +25 -0
  35. data/lib/dandy/request.rb +65 -0
  36. data/lib/dandy/routing/builder.rb +96 -0
  37. data/lib/dandy/routing/file_reader.rb +27 -0
  38. data/lib/dandy/routing/match.rb +12 -0
  39. data/lib/dandy/routing/matcher.rb +40 -0
  40. data/lib/dandy/routing/parser.rb +25 -0
  41. data/lib/dandy/routing/route.rb +23 -0
  42. data/lib/dandy/routing/routing.rb +11 -0
  43. data/lib/dandy/routing/syntax.rb +29 -0
  44. data/lib/dandy/routing/syntax/action.rb +30 -0
  45. data/lib/dandy/routing/syntax/actions.rb +15 -0
  46. data/lib/dandy/routing/syntax/after_section.rb +19 -0
  47. data/lib/dandy/routing/syntax/before_section.rb +18 -0
  48. data/lib/dandy/routing/syntax/catch_section.rb +15 -0
  49. data/lib/dandy/routing/syntax/command.rb +36 -0
  50. data/lib/dandy/routing/syntax/commands.rb +7 -0
  51. data/lib/dandy/routing/syntax/node.rb +47 -0
  52. data/lib/dandy/routing/syntax/nodes.rb +13 -0
  53. data/lib/dandy/routing/syntax/primitives/arrow.rb +4 -0
  54. data/lib/dandy/routing/syntax/primitives/back_arrow.rb +4 -0
  55. data/lib/dandy/routing/syntax/primitives/eol.rb +4 -0
  56. data/lib/dandy/routing/syntax/primitives/http_status.rb +13 -0
  57. data/lib/dandy/routing/syntax/primitives/http_verb.rb +4 -0
  58. data/lib/dandy/routing/syntax/primitives/indent.rb +4 -0
  59. data/lib/dandy/routing/syntax/primitives/parameter.rb +4 -0
  60. data/lib/dandy/routing/syntax/primitives/path.rb +5 -0
  61. data/lib/dandy/routing/syntax/primitives/result_name.rb +4 -0
  62. data/lib/dandy/routing/syntax/respond.rb +25 -0
  63. data/lib/dandy/routing/syntax/route.rb +25 -0
  64. data/lib/dandy/routing/syntax/sections.rb +19 -0
  65. data/lib/dandy/routing/syntax/tree_section.rb +11 -0
  66. data/lib/dandy/routing/syntax/view.rb +7 -0
  67. data/lib/dandy/routing/syntax_error_interpreter.rb +28 -0
  68. data/lib/dandy/routing/syntax_grammar.tt +99 -0
  69. data/lib/dandy/template_registry.rb +29 -0
  70. data/lib/dandy/version.rb +3 -0
  71. data/lib/dandy/view_builder.rb +18 -0
  72. data/lib/dandy/view_builder_registry.rb +21 -0
  73. data/lib/dandy/view_builders/json.rb +16 -0
  74. data/lib/dandy/view_factory.rb +21 -0
  75. 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
@@ -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,4 @@
1
+ module Dandy
2
+ class DandyError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ require 'dandy/errors/dandy_error'
2
+
3
+ module Dandy
4
+ class SyntaxError < DandyError
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'dandy/errors/dandy_error'
2
+
3
+ module Dandy
4
+ class ViewEngineError < DandyError
5
+ end
6
+ 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,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rack-cors'
4
+ gem 'dandy'
@@ -0,0 +1,2 @@
1
+ class HandleErrors < Dandy::HandleErrors
2
+ end
@@ -0,0 +1,9 @@
1
+ class Welcome
2
+ def call
3
+ "Welcome to <%= config[:app_name] %>!"
4
+ end
5
+
6
+ def result_name
7
+ 'message'
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ require 'dandy'
2
+
3
+ class App < Dandy::App
4
+ end
@@ -0,0 +1,4 @@
1
+ :receive
2
+ .->
3
+ GET -> welcome -> :respond <- show_welcome
4
+ :catch -> handle_errors
@@ -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,7 @@
1
+ path:
2
+ dependencies:
3
+ - app/actions
4
+ views: app/views
5
+ routes: app/app.routes
6
+ action:
7
+ async_timeout: 10 # in seconds
@@ -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