amber_component 0.0.3 → 0.0.4
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.
- 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
|