rodakase 0.0.1

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 (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