amber_component 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.solargraph.yml +1 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +9 -9
- data/lib/amber_component/base.rb +31 -12
- data/lib/amber_component/helpers/component_helper.rb +1 -0
- data/lib/amber_component/prop_definition.rb +54 -0
- data/lib/amber_component/props.rb +111 -0
- data/lib/amber_component/typed_content.rb +3 -3
- data/lib/amber_component/version.rb +1 -1
- data/lib/amber_component/views.rb +4 -4
- data/lib/amber_component.rb +8 -9
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45f702b26ec801eb7f67fa45e14c4fac035ff31d9ad6602418b7faa15c8e6004
|
4
|
+
data.tar.gz: 404f922961c15d865144d8f284511f0b93718bc85f93a1fea1c1b013aa6d815f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17df8b345dade33fb27c5167b9ee7d17788b2092ecbff5aeb2f6fbeb12a6dd91813e7c2aa9c52b42aafea6e5965869630960b64c1d3da5be3365d4e3f8702148
|
7
|
+
data.tar.gz: ec46b84d953e9efcc4ae0a36e68f0a733bfd8e76f518eee3cae2c33a655df3be9ec592b09cd8ade8b88707934e55004d03c62c2a48a2152690edd86910b4cd2e
|
data/.solargraph.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
amber_component (0.0.
|
4
|
+
amber_component (0.0.4)
|
5
5
|
actionview (>= 6)
|
6
6
|
activemodel (>= 6)
|
7
7
|
activesupport (>= 6)
|
@@ -73,7 +73,7 @@ GEM
|
|
73
73
|
nokogiri (1.13.8-arm64-darwin)
|
74
74
|
racc (~> 1.4)
|
75
75
|
parallel (1.22.1)
|
76
|
-
parser (3.1.2.
|
76
|
+
parser (3.1.2.1)
|
77
77
|
ast (~> 2.4.1)
|
78
78
|
racc (1.6.0)
|
79
79
|
rack (2.2.4)
|
@@ -98,17 +98,17 @@ GEM
|
|
98
98
|
reverse_markdown (2.1.1)
|
99
99
|
nokogiri
|
100
100
|
rexml (3.2.5)
|
101
|
-
rubocop (1.
|
101
|
+
rubocop (1.36.0)
|
102
102
|
json (~> 2.3)
|
103
103
|
parallel (~> 1.10)
|
104
|
-
parser (>= 3.1.
|
104
|
+
parser (>= 3.1.2.1)
|
105
105
|
rainbow (>= 2.2.2, < 4.0)
|
106
106
|
regexp_parser (>= 1.8, < 3.0)
|
107
107
|
rexml (>= 3.2.5, < 4.0)
|
108
|
-
rubocop-ast (>= 1.
|
108
|
+
rubocop-ast (>= 1.20.1, < 2.0)
|
109
109
|
ruby-progressbar (~> 1.7)
|
110
110
|
unicode-display_width (>= 1.4.0, < 3.0)
|
111
|
-
rubocop-ast (1.
|
111
|
+
rubocop-ast (1.21.0)
|
112
112
|
parser (>= 3.1.1.0)
|
113
113
|
ruby-progressbar (1.11.0)
|
114
114
|
ruby2_keywords (0.0.5)
|
@@ -124,7 +124,7 @@ GEM
|
|
124
124
|
simplecov (~> 0.19)
|
125
125
|
simplecov-html (0.12.3)
|
126
126
|
simplecov_json_formatter (0.1.4)
|
127
|
-
solargraph (0.
|
127
|
+
solargraph (0.46.0)
|
128
128
|
backport (~> 1.2)
|
129
129
|
benchmark
|
130
130
|
bundler (>= 1.17.2)
|
@@ -144,7 +144,7 @@ GEM
|
|
144
144
|
tilt (2.0.11)
|
145
145
|
tzinfo (2.0.5)
|
146
146
|
concurrent-ruby (~> 1.0)
|
147
|
-
unicode-display_width (2.
|
147
|
+
unicode-display_width (2.3.0)
|
148
148
|
webrick (1.7.0)
|
149
149
|
yard (0.9.28)
|
150
150
|
webrick (~> 1.7.0)
|
@@ -168,7 +168,7 @@ DEPENDENCIES
|
|
168
168
|
shoulda-context (~> 2.0)
|
169
169
|
simplecov
|
170
170
|
simplecov-cobertura
|
171
|
-
solargraph
|
171
|
+
solargraph
|
172
172
|
yard
|
173
173
|
|
174
174
|
BUNDLED WITH
|
data/lib/amber_component/base.rb
CHANGED
@@ -48,6 +48,8 @@ module ::AmberComponent
|
|
48
48
|
extend Assets::ClassMethods
|
49
49
|
include Rendering::InstanceMethods
|
50
50
|
extend Rendering::ClassMethods
|
51
|
+
include Props::InstanceMethods
|
52
|
+
extend Props::ClassMethods
|
51
53
|
|
52
54
|
class << self
|
53
55
|
include ::Memery
|
@@ -67,37 +69,51 @@ module ::AmberComponent
|
|
67
69
|
# @return [void]
|
68
70
|
def inherited(subclass)
|
69
71
|
super
|
70
|
-
|
71
|
-
method_body = proc do |**kwargs, &block|
|
72
|
+
method_body = lambda do |**kwargs, &block|
|
72
73
|
subclass.render(**kwargs, &block)
|
73
74
|
end
|
74
75
|
parent_module = subclass.module_parent
|
75
76
|
|
76
77
|
if parent_module.equal?(::Object)
|
77
78
|
method_name = subclass.name
|
78
|
-
define_helper_method(subclass, Helpers::ComponentHelper, method_name, method_body)
|
79
79
|
define_helper_method(subclass, Helpers::ComponentHelper, method_name.underscore, method_body)
|
80
80
|
return
|
81
81
|
end
|
82
82
|
|
83
83
|
method_name = subclass.const_name
|
84
|
-
define_helper_method(subclass, parent_module.singleton_class, method_name, method_body)
|
85
84
|
define_helper_method(subclass, parent_module.singleton_class, method_name.underscore, method_body)
|
86
85
|
end
|
87
86
|
|
88
|
-
#
|
87
|
+
# Gets or defines an anonymous module that
|
88
|
+
# will store all dynamically generated helper methods
|
89
|
+
# for the received module/class.
|
90
|
+
#
|
89
91
|
# @param mod [Module, Class]
|
92
|
+
# @return [Module]
|
93
|
+
def helper_module(mod)
|
94
|
+
ivar_name = :@__amber_component_helper_module
|
95
|
+
mod.instance_variable_get(ivar_name)&.then { return _1 }
|
96
|
+
|
97
|
+
helper_mod = mod.instance_variable_set(ivar_name, ::Module.new)
|
98
|
+
mod.include helper_mod
|
99
|
+
helper_mod
|
100
|
+
end
|
101
|
+
|
102
|
+
# Defines an instance method on the given `mod` Module/Class.
|
103
|
+
#
|
104
|
+
# @param component [Class]
|
105
|
+
# @param target_mod [Module, Class]
|
90
106
|
# @param method_name [String, Symbol]
|
91
107
|
# @param body [Proc]
|
92
|
-
def define_helper_method(component,
|
93
|
-
|
108
|
+
def define_helper_method(component, target_mod, method_name, body)
|
109
|
+
helper_module(target_mod).define_method(method_name, &body)
|
94
110
|
|
95
111
|
return if ::ENV['RAILS_ENV'] == 'production'
|
96
112
|
|
97
|
-
::Warning.warn <<~WARN if
|
113
|
+
::Warning.warn <<~WARN if target_mod.instance_methods.include?(method_name)
|
98
114
|
#{caller(0, 1).first}: warning:
|
99
|
-
`#{component}` shadows the name of an already existing `#{
|
100
|
-
Consider renaming this component, because the original method will be
|
115
|
+
`#{component}` shadows the name of an already existing `#{target_mod}` method `#{method_name}`.
|
116
|
+
Consider renaming this component, because the original method will be overridden.
|
101
117
|
WARN
|
102
118
|
end
|
103
119
|
end
|
@@ -105,9 +121,12 @@ module ::AmberComponent
|
|
105
121
|
define_model_callbacks :initialize, :render
|
106
122
|
|
107
123
|
# @param kwargs [Hash{Symbol => Object}]
|
124
|
+
# @raise [AmberComponent::MissingPropsError] when required props are missing
|
108
125
|
def initialize(**kwargs)
|
109
126
|
run_callbacks :initialize do
|
110
|
-
|
127
|
+
next if bind_props(kwargs)
|
128
|
+
|
129
|
+
bind_instance_variables(kwargs)
|
111
130
|
end
|
112
131
|
end
|
113
132
|
|
@@ -115,7 +134,7 @@ module ::AmberComponent
|
|
115
134
|
|
116
135
|
# @param kwargs [Hash{Symbol => Object}]
|
117
136
|
# @return [void]
|
118
|
-
def
|
137
|
+
def bind_instance_variables(kwargs)
|
119
138
|
kwargs.each do |key, value|
|
120
139
|
instance_variable_set("@#{key}", value)
|
121
140
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ::AmberComponent
|
4
|
+
# Internal class which represents a property
|
5
|
+
# on a component class.
|
6
|
+
class PropDefinition
|
7
|
+
# @return [Symbol]
|
8
|
+
attr_reader :name
|
9
|
+
# @return [Class, nil]
|
10
|
+
attr_reader :type
|
11
|
+
# @return [Boolean]
|
12
|
+
attr_reader :required
|
13
|
+
# @return [Object, Proc, nil]
|
14
|
+
attr_reader :default
|
15
|
+
# @return [Boolean]
|
16
|
+
attr_reader :allow_nil
|
17
|
+
|
18
|
+
# @param name [Symbol]
|
19
|
+
# @param type [Class, nil]
|
20
|
+
# @param required [Boolean]
|
21
|
+
# @param default [Object, Proc, nil]
|
22
|
+
# @param allow_nil [Boolean]
|
23
|
+
def initialize(name:, type: nil, required: false, default: nil, allow_nil: false)
|
24
|
+
@name = name
|
25
|
+
@type = type
|
26
|
+
@required = required
|
27
|
+
@default = default
|
28
|
+
@allow_nil = allow_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
alias required? required
|
32
|
+
alias allow_nil? allow_nil
|
33
|
+
|
34
|
+
# @return [Boolean]
|
35
|
+
def type?
|
36
|
+
!@type.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Boolean]
|
40
|
+
def default?
|
41
|
+
!@default.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Evaluate the default value if it's a `Proc`
|
45
|
+
# and return the result.
|
46
|
+
#
|
47
|
+
# @return [Object]
|
48
|
+
def default!
|
49
|
+
return @default.call if @default.is_a?(::Proc)
|
50
|
+
|
51
|
+
@default
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'prop_definition'
|
4
|
+
|
5
|
+
module ::AmberComponent
|
6
|
+
# Provides a DSL for defining component
|
7
|
+
# properties.
|
8
|
+
module Props
|
9
|
+
# Class methods for component properties.
|
10
|
+
module ClassMethods
|
11
|
+
# @return [Hash{Symbol => AmberComponent::Prop}]
|
12
|
+
attr_reader :prop_definitions
|
13
|
+
|
14
|
+
# @param names [Array<Symbol>]
|
15
|
+
# @param type [Class, nil]
|
16
|
+
# @param required [Boolean]
|
17
|
+
# @param default [Object, Proc, nil]
|
18
|
+
# @param allow_nil [Boolean]
|
19
|
+
def prop(*names, type: nil, required: false, default: nil, allow_nil: false)
|
20
|
+
@prop_definitions ||= {}
|
21
|
+
include(@prop_methods_module = ::Module.new) if @prop_methods_module.nil?
|
22
|
+
|
23
|
+
names.each do |name|
|
24
|
+
@prop_definitions[name] = prop_def = PropDefinition.new(
|
25
|
+
name: name,
|
26
|
+
type: type,
|
27
|
+
required: required,
|
28
|
+
default: default,
|
29
|
+
allow_nil: allow_nil
|
30
|
+
)
|
31
|
+
raise IncorrectPropTypeError, <<~MSG unless type.nil? || type.is_a?(::Class)
|
32
|
+
`type` should be a class but received `#{type.inspect}` (`#{type.class}`)
|
33
|
+
MSG
|
34
|
+
|
35
|
+
@prop_methods_module.attr_reader name
|
36
|
+
next @prop_methods_module.attr_writer(name) unless prop_def.type?
|
37
|
+
|
38
|
+
@prop_methods_module.class_eval( # rubocop:disable Style/DocumentDynamicEvalDefinition
|
39
|
+
# def phone=(val)
|
40
|
+
# raise IncorrectPropTypeError, <<~MSG unless val.nil? || val.is_a?(String)
|
41
|
+
# #{self.class} received `#{val.class}` instead of `String` for `phone` prop
|
42
|
+
# MSG
|
43
|
+
#
|
44
|
+
# @phone = val
|
45
|
+
# end
|
46
|
+
<<~RUB, __FILE__, __LINE__ + 1
|
47
|
+
def #{name}=(val)
|
48
|
+
raise IncorrectPropTypeError, <<~MSG unless #{allow_nil ? 'val.nil? ||' : nil} val.is_a?(#{prop_def.type})
|
49
|
+
\#{self.class} received `\#{val.class}` instead of `#{prop_def.type}` for `#{name}` prop
|
50
|
+
MSG
|
51
|
+
|
52
|
+
@#{name} = val
|
53
|
+
end
|
54
|
+
RUB
|
55
|
+
) # rubocop:disable Layout/HeredocArgumentClosingParenthesis
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Array<Symbol>, nil]
|
60
|
+
def prop_names
|
61
|
+
@prop_definitions.keys
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Array<Symbol>, nil]
|
65
|
+
def required_prop_names
|
66
|
+
@prop_definitions&.filter_map do |name, prop_def|
|
67
|
+
next unless prop_def.required
|
68
|
+
|
69
|
+
name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Instance methods for component properties.
|
75
|
+
module InstanceMethods
|
76
|
+
private
|
77
|
+
|
78
|
+
# @param props [Hash{Symbol => Object}]
|
79
|
+
def initialize(**kwargs)
|
80
|
+
bind_props(kwargs)
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param props [Hash{Symbol => Object}]
|
84
|
+
# @return [Boolean] `false` when there are no props defined on the class
|
85
|
+
# and `true` otherwise
|
86
|
+
# @raise [AmberComponent::MissingPropsError] when required props are missing
|
87
|
+
# @raise [AmberComponent::IncorrectPropTypeError]
|
88
|
+
def bind_props(props)
|
89
|
+
return false if self.class.prop_definitions.nil?
|
90
|
+
|
91
|
+
self.class.prop_definitions.each do |name, prop_def|
|
92
|
+
setter_name = :"#{name}="
|
93
|
+
public_send(setter_name, prop_def.default!) if prop_def.default?
|
94
|
+
|
95
|
+
prop_present = props.include? name
|
96
|
+
|
97
|
+
raise MissingPropsError, <<~MSG if prop_def.required? && !prop_present
|
98
|
+
`#{self.class}` has a missing required prop: `#{name.inspect}`
|
99
|
+
MSG
|
100
|
+
|
101
|
+
next unless prop_present
|
102
|
+
|
103
|
+
value = props[name]
|
104
|
+
public_send(setter_name, value)
|
105
|
+
end
|
106
|
+
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -9,9 +9,9 @@ module ::AmberComponent
|
|
9
9
|
def wrap(val)
|
10
10
|
return val if val.is_a?(self)
|
11
11
|
|
12
|
-
unless val.respond_to?(:[])
|
13
|
-
|
14
|
-
|
12
|
+
raise InvalidTypeError, <<~MSG unless val.respond_to?(:[])
|
13
|
+
`TypedContent` should be a `Hash` or `#{self}` but was `#{val.class}` (#{val.inspect})
|
14
|
+
MSG
|
15
15
|
|
16
16
|
new(type: val[:type], content: val[:content])
|
17
17
|
end
|
@@ -55,7 +55,7 @@ module ::AmberComponent
|
|
55
55
|
# @return [String, nil]
|
56
56
|
def view_file_name
|
57
57
|
files = asset_file_names(VIEW_FILE_REGEXP)
|
58
|
-
raise
|
58
|
+
raise MultipleViewsError, "More than one view file for `#{name}` found!" if files.length > 1
|
59
59
|
|
60
60
|
files.first
|
61
61
|
end
|
@@ -81,7 +81,7 @@ module ::AmberComponent
|
|
81
81
|
view_content = view_from_inline unless view_from_inline.empty?
|
82
82
|
|
83
83
|
if view_content.nil? || view_content.empty?
|
84
|
-
raise
|
84
|
+
raise ViewFileNotFoundError, "View for `#{self.class}` could not be found!"
|
85
85
|
end
|
86
86
|
|
87
87
|
view_content
|
@@ -108,13 +108,13 @@ module ::AmberComponent
|
|
108
108
|
content = content.to_s
|
109
109
|
|
110
110
|
if content.empty?
|
111
|
-
raise
|
111
|
+
raise EmptyViewError, <<~ERR.squish
|
112
112
|
Custom view for `#{self.class}` from view method cannot be empty!
|
113
113
|
ERR
|
114
114
|
end
|
115
115
|
|
116
116
|
unless ALLOWED_VIEW_TYPES.include? type
|
117
|
-
raise
|
117
|
+
raise UnknownViewTypeError, <<~ERR.squish
|
118
118
|
Unknown view type for `#{self.class}` from view method!
|
119
119
|
Check return value of param type in `view :[type] do`
|
120
120
|
ERR
|
data/lib/amber_component.rb
CHANGED
@@ -5,16 +5,14 @@ require 'active_support/core_ext'
|
|
5
5
|
|
6
6
|
module ::AmberComponent
|
7
7
|
class Error < ::StandardError; end
|
8
|
-
class
|
9
|
-
class
|
8
|
+
class MissingPropsError < Error; end
|
9
|
+
class IncorrectPropTypeError < Error; end
|
10
|
+
class ViewFileNotFoundError < Error; end
|
11
|
+
class InvalidTypeError < Error; end
|
10
12
|
|
11
|
-
class
|
12
|
-
class
|
13
|
-
class
|
14
|
-
|
15
|
-
class EmptyStyle < Error; end
|
16
|
-
class UnknownStyleType < Error; end
|
17
|
-
class MultipleStyles < Error; end
|
13
|
+
class EmptyViewError < Error; end
|
14
|
+
class UnknownViewTypeError < Error; end
|
15
|
+
class MultipleViewsError < Error; end
|
18
16
|
end
|
19
17
|
|
20
18
|
require_relative 'amber_component/version'
|
@@ -24,5 +22,6 @@ require_relative 'amber_component/template_handler'
|
|
24
22
|
require_relative 'amber_component/views'
|
25
23
|
require_relative 'amber_component/assets'
|
26
24
|
require_relative 'amber_component/rendering'
|
25
|
+
require_relative 'amber_component/props'
|
27
26
|
require_relative 'amber_component/base'
|
28
27
|
require_relative 'amber_component/railtie' if defined?(::Rails::Railtie)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amber_component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ruby-Amber
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2022-09-
|
13
|
+
date: 2022-09-25 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: actionview
|
@@ -143,6 +143,8 @@ files:
|
|
143
143
|
- lib/amber_component/helpers/class_helper.rb
|
144
144
|
- lib/amber_component/helpers/component_helper.rb
|
145
145
|
- lib/amber_component/helpers/css_helper.rb
|
146
|
+
- lib/amber_component/prop_definition.rb
|
147
|
+
- lib/amber_component/props.rb
|
146
148
|
- lib/amber_component/railtie.rb
|
147
149
|
- lib/amber_component/rendering.rb
|
148
150
|
- lib/amber_component/template_handler.rb
|