rodakase 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.rspec +3 -0
- data/.travis.yml +29 -0
- data/CHANGELOG.md +8 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +24 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +22 -0
- data/README.md +114 -0
- data/Rakefile +4 -0
- data/benchmarks/templates/button.erb +1 -0
- data/benchmarks/view.rb +27 -0
- data/lib/roda/plugins/flow.rb +71 -0
- data/lib/rodakase.rb +8 -0
- data/lib/rodakase/application.rb +32 -0
- data/lib/rodakase/cli.rb +9 -0
- data/lib/rodakase/component.rb +40 -0
- data/lib/rodakase/config.rb +21 -0
- data/lib/rodakase/container.rb +127 -0
- data/lib/rodakase/transaction.rb +43 -0
- data/lib/rodakase/transaction/matcher.rb +31 -0
- data/lib/rodakase/transaction/result.rb +52 -0
- data/lib/rodakase/version.rb +3 -0
- data/lib/rodakase/view.rb +2 -0
- data/lib/rodakase/view/layout.rb +98 -0
- data/lib/rodakase/view/part.rb +53 -0
- data/lib/rodakase/view/renderer.rb +67 -0
- data/rodakase.gemspec +34 -0
- data/skeletons/simple/Gemfile +9 -0
- data/skeletons/simple/Rakefile +4 -0
- data/skeletons/simple/bin/console +6 -0
- data/skeletons/simple/config.ru +3 -0
- data/skeletons/simple/config/application.yml +4 -0
- data/skeletons/simple/core/boot.rb +12 -0
- data/skeletons/simple/core/simple/application.rb +16 -0
- data/skeletons/simple/core/simple/container.rb +11 -0
- data/skeletons/simple/core/simple/import.rb +9 -0
- data/skeletons/simple/lib/entities/user.rb +3 -0
- data/skeletons/simple/log/.gitkeep +0 -0
- data/skeletons/simple/spec/routes/heartbeat_spec.rb +9 -0
- data/skeletons/simple/spec/spec_helper.rb +12 -0
- data/skeletons/simple/spec/unit/user_spec.rb +17 -0
- data/skeletons/simple/spec/web_helper.rb +20 -0
- data/skeletons/simple/web/routes/root.rb +3 -0
- data/spec/dummy/apps/main/core/boot.rb +9 -0
- data/spec/dummy/apps/main/core/main/application.rb +17 -0
- data/spec/dummy/apps/main/core/main/container.rb +12 -0
- data/spec/dummy/apps/main/core/main/import.rb +9 -0
- data/spec/dummy/apps/main/core/main/requests.rb +21 -0
- data/spec/dummy/apps/main/core/main/view.rb +19 -0
- data/spec/dummy/apps/main/lib/entities/user.rb +3 -0
- data/spec/dummy/apps/main/lib/persistence/repositories/users.rb +14 -0
- data/spec/dummy/apps/main/lib/transaction.rb +11 -0
- data/spec/dummy/apps/main/lib/transactions/register_user.rb +17 -0
- data/spec/dummy/apps/main/lib/views/users/index.rb +15 -0
- data/spec/dummy/apps/main/requests/users.rb +5 -0
- data/spec/dummy/apps/main/web/routes/users.rb +21 -0
- data/spec/dummy/apps/main/web/templates/layouts/app.slim +6 -0
- data/spec/dummy/apps/main/web/templates/users/index.slim +3 -0
- data/spec/dummy/apps/main/web/templates/users/index/_list.slim +3 -0
- data/spec/dummy/apps/main/web/templates/users/index/_list_item.slim +1 -0
- data/spec/dummy/bin/console +6 -0
- data/spec/dummy/config.ru +3 -0
- data/spec/dummy/config/application.yml +4 -0
- data/spec/dummy/core/boot.rb +15 -0
- data/spec/dummy/core/dummy/container.rb +14 -0
- data/spec/dummy/core/dummy/import.rb +9 -0
- data/spec/dummy/lib/persistence/db.rb +13 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/fixtures/templates/hello.slim +1 -0
- data/spec/fixtures/templates/layouts/app.slim +6 -0
- data/spec/fixtures/templates/shared/_index_table.slim +2 -0
- data/spec/fixtures/templates/shared/_shared_hello.slim +1 -0
- data/spec/fixtures/templates/tasks.slim +3 -0
- data/spec/fixtures/templates/user.slim +2 -0
- data/spec/fixtures/templates/users.slim +3 -0
- data/spec/fixtures/templates/users/_row.slim +2 -0
- data/spec/fixtures/templates/users/_tbody.slim +5 -0
- data/spec/fixtures/test/core/boot/bar.rb +5 -0
- data/spec/fixtures/test/lib/test/dep.rb +4 -0
- data/spec/fixtures/test/lib/test/foo.rb +5 -0
- data/spec/fixtures/test/lib/test/models.rb +4 -0
- data/spec/fixtures/test/lib/test/models/book.rb +6 -0
- data/spec/fixtures/test/lib/test/models/user.rb +6 -0
- data/spec/integration/application_spec.rb +21 -0
- data/spec/integration/transaction_spec.rb +85 -0
- data/spec/integration/view_spec.rb +65 -0
- data/spec/request/users_spec.rb +35 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/helpers.rb +15 -0
- data/spec/unit/component_spec.rb +60 -0
- data/spec/unit/container_spec.rb +51 -0
- data/spec/unit/view/layout_spec.rb +49 -0
- data/spec/unit/view/part_spec.rb +55 -0
- data/spec/unit/view/renderer_spec.rb +29 -0
- 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,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
|