rodakase 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +29 -0
  5. data/CHANGELOG.md +8 -0
  6. data/CODE_OF_CONDUCT.md +13 -0
  7. data/Gemfile +24 -0
  8. data/LICENSE +22 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +114 -0
  11. data/Rakefile +4 -0
  12. data/benchmarks/templates/button.erb +1 -0
  13. data/benchmarks/view.rb +27 -0
  14. data/lib/roda/plugins/flow.rb +71 -0
  15. data/lib/rodakase.rb +8 -0
  16. data/lib/rodakase/application.rb +32 -0
  17. data/lib/rodakase/cli.rb +9 -0
  18. data/lib/rodakase/component.rb +40 -0
  19. data/lib/rodakase/config.rb +21 -0
  20. data/lib/rodakase/container.rb +127 -0
  21. data/lib/rodakase/transaction.rb +43 -0
  22. data/lib/rodakase/transaction/matcher.rb +31 -0
  23. data/lib/rodakase/transaction/result.rb +52 -0
  24. data/lib/rodakase/version.rb +3 -0
  25. data/lib/rodakase/view.rb +2 -0
  26. data/lib/rodakase/view/layout.rb +98 -0
  27. data/lib/rodakase/view/part.rb +53 -0
  28. data/lib/rodakase/view/renderer.rb +67 -0
  29. data/rodakase.gemspec +34 -0
  30. data/skeletons/simple/Gemfile +9 -0
  31. data/skeletons/simple/Rakefile +4 -0
  32. data/skeletons/simple/bin/console +6 -0
  33. data/skeletons/simple/config.ru +3 -0
  34. data/skeletons/simple/config/application.yml +4 -0
  35. data/skeletons/simple/core/boot.rb +12 -0
  36. data/skeletons/simple/core/simple/application.rb +16 -0
  37. data/skeletons/simple/core/simple/container.rb +11 -0
  38. data/skeletons/simple/core/simple/import.rb +9 -0
  39. data/skeletons/simple/lib/entities/user.rb +3 -0
  40. data/skeletons/simple/log/.gitkeep +0 -0
  41. data/skeletons/simple/spec/routes/heartbeat_spec.rb +9 -0
  42. data/skeletons/simple/spec/spec_helper.rb +12 -0
  43. data/skeletons/simple/spec/unit/user_spec.rb +17 -0
  44. data/skeletons/simple/spec/web_helper.rb +20 -0
  45. data/skeletons/simple/web/routes/root.rb +3 -0
  46. data/spec/dummy/apps/main/core/boot.rb +9 -0
  47. data/spec/dummy/apps/main/core/main/application.rb +17 -0
  48. data/spec/dummy/apps/main/core/main/container.rb +12 -0
  49. data/spec/dummy/apps/main/core/main/import.rb +9 -0
  50. data/spec/dummy/apps/main/core/main/requests.rb +21 -0
  51. data/spec/dummy/apps/main/core/main/view.rb +19 -0
  52. data/spec/dummy/apps/main/lib/entities/user.rb +3 -0
  53. data/spec/dummy/apps/main/lib/persistence/repositories/users.rb +14 -0
  54. data/spec/dummy/apps/main/lib/transaction.rb +11 -0
  55. data/spec/dummy/apps/main/lib/transactions/register_user.rb +17 -0
  56. data/spec/dummy/apps/main/lib/views/users/index.rb +15 -0
  57. data/spec/dummy/apps/main/requests/users.rb +5 -0
  58. data/spec/dummy/apps/main/web/routes/users.rb +21 -0
  59. data/spec/dummy/apps/main/web/templates/layouts/app.slim +6 -0
  60. data/spec/dummy/apps/main/web/templates/users/index.slim +3 -0
  61. data/spec/dummy/apps/main/web/templates/users/index/_list.slim +3 -0
  62. data/spec/dummy/apps/main/web/templates/users/index/_list_item.slim +1 -0
  63. data/spec/dummy/bin/console +6 -0
  64. data/spec/dummy/config.ru +3 -0
  65. data/spec/dummy/config/application.yml +4 -0
  66. data/spec/dummy/core/boot.rb +15 -0
  67. data/spec/dummy/core/dummy/container.rb +14 -0
  68. data/spec/dummy/core/dummy/import.rb +9 -0
  69. data/spec/dummy/lib/persistence/db.rb +13 -0
  70. data/spec/dummy/log/.gitkeep +0 -0
  71. data/spec/fixtures/templates/hello.slim +1 -0
  72. data/spec/fixtures/templates/layouts/app.slim +6 -0
  73. data/spec/fixtures/templates/shared/_index_table.slim +2 -0
  74. data/spec/fixtures/templates/shared/_shared_hello.slim +1 -0
  75. data/spec/fixtures/templates/tasks.slim +3 -0
  76. data/spec/fixtures/templates/user.slim +2 -0
  77. data/spec/fixtures/templates/users.slim +3 -0
  78. data/spec/fixtures/templates/users/_row.slim +2 -0
  79. data/spec/fixtures/templates/users/_tbody.slim +5 -0
  80. data/spec/fixtures/test/core/boot/bar.rb +5 -0
  81. data/spec/fixtures/test/lib/test/dep.rb +4 -0
  82. data/spec/fixtures/test/lib/test/foo.rb +5 -0
  83. data/spec/fixtures/test/lib/test/models.rb +4 -0
  84. data/spec/fixtures/test/lib/test/models/book.rb +6 -0
  85. data/spec/fixtures/test/lib/test/models/user.rb +6 -0
  86. data/spec/integration/application_spec.rb +21 -0
  87. data/spec/integration/transaction_spec.rb +85 -0
  88. data/spec/integration/view_spec.rb +65 -0
  89. data/spec/request/users_spec.rb +35 -0
  90. data/spec/spec_helper.rb +44 -0
  91. data/spec/support/helpers.rb +15 -0
  92. data/spec/unit/component_spec.rb +60 -0
  93. data/spec/unit/container_spec.rb +51 -0
  94. data/spec/unit/view/layout_spec.rb +49 -0
  95. data/spec/unit/view/part_spec.rb +55 -0
  96. data/spec/unit/view/renderer_spec.rb +29 -0
  97. metadata +359 -0
@@ -0,0 +1,40 @@
1
+ require 'inflecto'
2
+
3
+ module Rodakase
4
+ def self.Component(input)
5
+ Component.new(Component.identifier(input), Component.path(input))
6
+ end
7
+
8
+ class Component
9
+ IDENTIFIER_SEPARATOR = '.'.freeze
10
+ PATH_SEPARATOR = '/'.freeze
11
+
12
+ attr_reader :identifier, :path, :file
13
+
14
+ def self.identifier(input)
15
+ input.gsub(PATH_SEPARATOR, IDENTIFIER_SEPARATOR)
16
+ end
17
+
18
+ def self.path(input)
19
+ input.gsub(IDENTIFIER_SEPARATOR, PATH_SEPARATOR)
20
+ end
21
+
22
+ def initialize(identifier, path)
23
+ @identifier = identifier
24
+ @path = path
25
+ @file = "#{path}.rb"
26
+ end
27
+
28
+ def name
29
+ Inflecto.camelize(path)
30
+ end
31
+
32
+ def constant
33
+ Inflecto.constantize(name)
34
+ end
35
+
36
+ def instance(*args)
37
+ constant.new(*args)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ require 'yaml'
2
+
3
+ module Rodakase
4
+ class Config
5
+ extend Dry::Configurable
6
+
7
+ def self.load(root, env)
8
+ path = root.join('config').join('application.yml')
9
+
10
+ return unless File.exist?(path)
11
+
12
+ yaml = YAML.load_file(path)
13
+
14
+ yaml.fetch(env.to_s).each do |key, value|
15
+ setting key.downcase.to_sym, ENV.fetch(key, value)
16
+ end
17
+
18
+ config
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,127 @@
1
+ require 'inflecto'
2
+ require 'dry-container'
3
+ require 'dry-auto_inject'
4
+
5
+ require 'rodakase/component'
6
+ require 'rodakase/config'
7
+
8
+ module Rodakase
9
+ class Container
10
+ extend Dry::Container::Mixin
11
+
12
+ setting :env, ENV.fetch('RACK_ENV', :development).to_sym
13
+ setting :root, Pathname.pwd.freeze
14
+ setting :auto_register
15
+ setting :app
16
+
17
+ def self.configure(env = config.env, &block)
18
+ if !configured?
19
+ super() do |config|
20
+ app_config = Config.load(root, env)
21
+ config.app = app_config if app_config
22
+ end
23
+
24
+ load_paths!('core')
25
+
26
+ @_configured = true
27
+ end
28
+
29
+ yield(self)
30
+
31
+ self
32
+ end
33
+
34
+ def self.configured?
35
+ @_configured
36
+ end
37
+
38
+ def self.finalize!(&block)
39
+ yield(self) if block
40
+
41
+ Dir[root.join('core/boot/**/*.rb')].each(&method(:require))
42
+
43
+ if config.auto_register
44
+ Array(config.auto_register).each(&method(:auto_register!))
45
+ end
46
+
47
+ freeze
48
+ end
49
+
50
+ def self.import_module
51
+ auto_inject = Dry::AutoInject(self)
52
+
53
+ -> *keys {
54
+ keys.each { |key| load_component(key) unless key?(key) }
55
+ auto_inject[*keys]
56
+ }
57
+ end
58
+
59
+ def self.auto_register!(dir, &block)
60
+ dir_root = root.join(dir.to_s.split('/')[0])
61
+
62
+ Dir["#{root}/#{dir}/**/*.rb"].each do |path|
63
+ component_path = path.to_s.gsub("#{dir_root}/", '').gsub('.rb', '')
64
+ component = Rodakase.Component(component_path)
65
+
66
+ next if key?(component.identifier)
67
+
68
+ Kernel.require component.path
69
+
70
+ if block
71
+ register(component.identifier, yield(component.constant))
72
+ else
73
+ register(component.identifier) { component.instance }
74
+ end
75
+ end
76
+
77
+ self
78
+ end
79
+
80
+ def self.boot!(name)
81
+ require "core/boot/#{name}.rb"
82
+ end
83
+
84
+ def self.require(*paths)
85
+ paths
86
+ .flat_map { |path|
87
+ path.include?('*') ? Dir[root.join(path)] : root.join(path)
88
+ }
89
+ .each { |path|
90
+ Kernel.require path
91
+ }
92
+ end
93
+
94
+ def self.load_component(key)
95
+ require_component(key) { |klass| register(key) { klass.new } }
96
+ end
97
+
98
+ def self.require_component(key, &block)
99
+ component = Rodakase.Component(key)
100
+ path = load_paths.detect { |p| p.join(component.file).exist? }
101
+
102
+ if path
103
+ Kernel.require component.path
104
+ yield(component.constant) if block
105
+ else
106
+ raise ArgumentError, "could not resolve require file for #{key}"
107
+ end
108
+ end
109
+
110
+ def self.root
111
+ config.root
112
+ end
113
+
114
+ def self.load_paths!(*dirs)
115
+ dirs.map(&:to_s).each do |dir|
116
+ path = root.join(dir)
117
+ load_paths << path
118
+ $LOAD_PATH.unshift(path.to_s)
119
+ end
120
+ self
121
+ end
122
+
123
+ def self.load_paths
124
+ @_load_paths ||= []
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,43 @@
1
+ require 'delegate'
2
+ require 'transflow'
3
+
4
+ require 'rodakase/transaction/result'
5
+ require 'rodakase/transaction/matcher'
6
+
7
+ module Rodakase
8
+ module Transaction
9
+ class Flow
10
+ extend Forwardable
11
+
12
+ def_delegator :@transaction, :subscribe
13
+
14
+ attr_reader :transaction
15
+
16
+ def initialize(transaction)
17
+ @transaction = transaction
18
+ end
19
+
20
+ def call(*args, &block)
21
+ result = transaction.call(*args)
22
+
23
+ if block
24
+ yield(Matcher.new(result))
25
+ else
26
+ result
27
+ end
28
+ end
29
+ end
30
+
31
+ class Composer
32
+ attr_reader :container
33
+
34
+ def initialize(container)
35
+ @container = container
36
+ end
37
+
38
+ def define(&block)
39
+ Flow.new(Transflow(container: container, &block))
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ module Rodakase
2
+ module Transaction
3
+ class Matcher
4
+ attr_reader :result
5
+
6
+ class Error
7
+ attr_reader :result
8
+
9
+ def initialize(result)
10
+ @result = result
11
+ end
12
+
13
+ def on(code, &block)
14
+ yield(result.value) if result.code == code
15
+ end
16
+ end
17
+
18
+ def initialize(result)
19
+ @result = result
20
+ end
21
+
22
+ def success(&block)
23
+ yield(result.value) if result.success?
24
+ end
25
+
26
+ def failure(&block)
27
+ yield(Error.new(result)) if result.failure?
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ module Rodakase
2
+ module Transaction
3
+ class Success
4
+ attr_reader :value
5
+
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ def fmap
11
+ Success.new(yield(value))
12
+ end
13
+
14
+ def success?
15
+ true
16
+ end
17
+
18
+ def failure?
19
+ false
20
+ end
21
+ end
22
+
23
+ class Failure
24
+ attr_reader :code, :error
25
+ alias_method :value, :error
26
+
27
+ def initialize(*args)
28
+ @code, @error = args
29
+ end
30
+
31
+ def fmap
32
+ self
33
+ end
34
+
35
+ def success?
36
+ false
37
+ end
38
+
39
+ def failure?
40
+ true
41
+ end
42
+ end
43
+
44
+ def self.Failure(code, error)
45
+ Failure.new(code, error)
46
+ end
47
+
48
+ def self.Success(value)
49
+ Success.new(value)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Rodakase
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'rodakase/view/renderer'
2
+ require 'rodakase/view/layout'
@@ -0,0 +1,98 @@
1
+ require 'dry-configurable'
2
+ require 'dry-equalizer'
3
+
4
+ require 'rodakase/view/part'
5
+ require 'rodakase/view/renderer'
6
+
7
+ module Rodakase
8
+ module View
9
+ class Layout
10
+ include Dry::Equalizer(:config)
11
+
12
+ Scope = Struct.new(:page)
13
+
14
+ DEFAULT_SCOPE = Object.new.freeze
15
+ DEFAULT_DIR = 'layouts'.freeze
16
+
17
+ extend Dry::Configurable
18
+
19
+ setting :engine
20
+ setting :root
21
+ setting :renderer
22
+ setting :name
23
+ setting :template
24
+ setting :scope
25
+
26
+ def self.configure(&block)
27
+ super do |config|
28
+ yield(config)
29
+
30
+ unless config.renderer
31
+ config.renderer = Renderer.new(config.root, engine: config.engine)
32
+ end
33
+ end
34
+ end
35
+
36
+ attr_reader :config, :renderer, :scope,
37
+ :layout_dir, :layout_path, :template_path
38
+
39
+ def initialize
40
+ @config = self.class.config
41
+ @renderer = @config.renderer
42
+ @layout_dir = DEFAULT_DIR
43
+ @layout_path = "#{layout_dir}/#{config.name}"
44
+ @template_path = config.template
45
+ @scope = config.scope
46
+ end
47
+
48
+ def call(options = {})
49
+ renderer.(layout_path, layout_scope(options)) do
50
+ renderer.(template_path, template_scope(options))
51
+ end
52
+ end
53
+
54
+ def layout_scope(options)
55
+ Scope.new(layout_part(:page, options.fetch(:scope, scope)))
56
+ end
57
+
58
+ def template_scope(options)
59
+ parts(locals(options))
60
+ end
61
+
62
+ def locals(options)
63
+ options.fetch(:locals, {})
64
+ end
65
+
66
+ def parts(locals)
67
+ return DEFAULT_SCOPE unless locals.any?
68
+
69
+ part_hash = locals.each_with_object({}) do |(key, value), result|
70
+ part =
71
+ case value
72
+ when Array
73
+ el_key = Inflecto.singularize(key).to_sym
74
+ template_part(key, value.map { |element| template_part(el_key, element) })
75
+ else
76
+ template_part(key, value)
77
+ end
78
+
79
+ result[key] = part
80
+ end
81
+
82
+ part(template_path, part_hash)
83
+ end
84
+
85
+ def layout_part(name, value)
86
+ part(layout_dir, name => value)
87
+ end
88
+
89
+ def template_part(name, value)
90
+ part(template_path, name => value)
91
+ end
92
+
93
+ def part(dir, value)
94
+ Part.new(renderer.chdir(dir), value)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,53 @@
1
+ require 'dry-equalizer'
2
+
3
+ module Rodakase
4
+ module View
5
+ class Part
6
+ include Dry::Equalizer(:renderer, :_data, :_value)
7
+
8
+ attr_reader :renderer, :_data, :_value
9
+
10
+ def initialize(renderer, data)
11
+ @renderer = renderer
12
+ @_data = data
13
+ @_value = data.values[0]
14
+ end
15
+
16
+ def [](key)
17
+ _value[key]
18
+ end
19
+
20
+ def each(&block)
21
+ _value.each(&block)
22
+ end
23
+
24
+ def render(path, &block)
25
+ renderer.render(path, self, &block)
26
+ end
27
+
28
+ def template?(name)
29
+ renderer.lookup("_#{name}")
30
+ end
31
+
32
+ def respond_to_missing?(meth, include_private = false)
33
+ super || _data.key?(meth) || template?(meth)
34
+ end
35
+
36
+ private
37
+
38
+ def method_missing(meth, *args, &block)
39
+ template_path = template?(meth)
40
+
41
+ if template_path
42
+ render(template_path, &block)
43
+ elsif _data.key?(meth)
44
+ _data[meth]
45
+ elsif _value.respond_to?(meth)
46
+ _value.public_send(meth, *args, &block)
47
+ else
48
+ super
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end