prop_initializer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +3 -0
- data/lib/prop_initializer/null.rb +9 -0
- data/lib/prop_initializer/properties/schema.rb +119 -0
- data/lib/prop_initializer/properties.rb +83 -0
- data/lib/prop_initializer/property.rb +183 -0
- data/lib/prop_initializer/railtie.rb +4 -0
- data/lib/prop_initializer/version.rb +3 -0
- data/lib/prop_initializer.rb +11 -0
- data/lib/tasks/prop_initializer_tasks.rake +4 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 29c51456a07fe5ba7a95870bb2cb7e1a1b586b845c0a74878d3b273bee8dbaae
|
4
|
+
data.tar.gz: a670f466ba0374a53a2f642de194317713a92ea1b081e777f509f354d56a5a67
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d99b4d83625d31aa5c900fc54f3f9251cc7ec389ee11d16bc9d770e736816fc1f549876727500f1a68a53a1e12346ca6637db592457dbd8857ea126f9a45da77
|
7
|
+
data.tar.gz: f68cf05fcc1701e3ca85088b9e41f88622eaf071b20f93d949027899e768aeba05151a364d5e9455472c5e1410c2e93d2429c7060792f5147f424d2b3b213357
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Paul Bob
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# PropInitializer
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem "prop_initializer"
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install prop_initializer
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
class PropInitializer::Properties::Schema
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(properties_index: {}, sorted_properties: [])
|
8
|
+
@properties_index = properties_index
|
9
|
+
@sorted_properties = sorted_properties
|
10
|
+
@mutex = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :properties_index
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@properties_index[key]
|
17
|
+
end
|
18
|
+
|
19
|
+
def <<(value)
|
20
|
+
@mutex.synchronize do
|
21
|
+
@properties_index[value.name] = value
|
22
|
+
# ruby's sort is unstable, this trick makes it stable
|
23
|
+
n = 0
|
24
|
+
@sorted_properties = @properties_index.values.sort_by! { |it| n += 1; [it, n] }
|
25
|
+
end
|
26
|
+
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def dup
|
31
|
+
self.class.new(
|
32
|
+
properties_index: @properties_index.dup,
|
33
|
+
sorted_properties: @sorted_properties.dup,
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def each(&)
|
38
|
+
@sorted_properties.each(&)
|
39
|
+
end
|
40
|
+
|
41
|
+
def size
|
42
|
+
@sorted_properties.size
|
43
|
+
end
|
44
|
+
|
45
|
+
def generate_initializer(buffer = +"")
|
46
|
+
buffer << "def initialize(#{generate_initializer_params})\n"
|
47
|
+
generate_initializer_body(buffer)
|
48
|
+
buffer << "" \
|
49
|
+
" after_initialize if respond_to?(:after_initialize)\n" \
|
50
|
+
"end\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_to_h(buffer = +"")
|
54
|
+
buffer << "def to_h\n" << " {\n"
|
55
|
+
|
56
|
+
sorted_properties = @sorted_properties
|
57
|
+
i, n = 0, sorted_properties.size
|
58
|
+
while i < n
|
59
|
+
property = sorted_properties[i]
|
60
|
+
buffer << " " << property.name.name << ": @" << property.name.name << ",\n"
|
61
|
+
i += 1
|
62
|
+
end
|
63
|
+
|
64
|
+
buffer << " }\n" << "end\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def generate_initializer_params(buffer = +"")
|
70
|
+
sorted_properties = @sorted_properties
|
71
|
+
i, n = 0, sorted_properties.size
|
72
|
+
while i < n
|
73
|
+
property = sorted_properties[i]
|
74
|
+
|
75
|
+
case property.kind
|
76
|
+
when :*
|
77
|
+
buffer << "*" << property.escaped_name
|
78
|
+
when :**
|
79
|
+
buffer << "**" << property.escaped_name
|
80
|
+
when :&
|
81
|
+
buffer << "&" << property.escaped_name
|
82
|
+
when :positional
|
83
|
+
if property.default?
|
84
|
+
buffer << property.escaped_name << " = PropInitializer::Null"
|
85
|
+
else
|
86
|
+
buffer << property.escaped_name << " = nil"
|
87
|
+
end
|
88
|
+
when :keyword
|
89
|
+
if property.default?
|
90
|
+
buffer << property.name.name << ": PropInitializer::Null"
|
91
|
+
else
|
92
|
+
buffer << property.name.name << ": nil"
|
93
|
+
end
|
94
|
+
else
|
95
|
+
raise "You should never see this error."
|
96
|
+
end
|
97
|
+
|
98
|
+
i += 1
|
99
|
+
buffer << ", " if i < n
|
100
|
+
end
|
101
|
+
|
102
|
+
buffer
|
103
|
+
end
|
104
|
+
|
105
|
+
def generate_initializer_body(buffer = +"")
|
106
|
+
buffer << " properties = self.class.prop_initializer_properties.properties_index\n"
|
107
|
+
generate_initializer_handle_properties(@sorted_properties, buffer)
|
108
|
+
end
|
109
|
+
|
110
|
+
def generate_initializer_handle_properties(properties, buffer = +"")
|
111
|
+
i, n = 0, properties.size
|
112
|
+
while i < n
|
113
|
+
properties[i].generate_initializer_handle_property(buffer)
|
114
|
+
i += 1
|
115
|
+
end
|
116
|
+
|
117
|
+
buffer
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module PropInitializer::Properties
|
2
|
+
def prop(name, kind: :keyword, reader: false, writer: false, predicate: false, default: nil, &coercion)
|
3
|
+
if default && !(Proc === default || default.frozen?)
|
4
|
+
raise ArgumentError.new("The default must be a frozen object or a Proc.")
|
5
|
+
end
|
6
|
+
|
7
|
+
unless PropInitializer::Property::VISIBILITY_OPTIONS.include?(reader)
|
8
|
+
raise ArgumentError.new("The reader must be one of #{PropInitializer::Property::VISIBILITY_OPTIONS.map(&:inspect).join(', ')}.")
|
9
|
+
end
|
10
|
+
|
11
|
+
unless PropInitializer::Property::VISIBILITY_OPTIONS.include?(writer)
|
12
|
+
raise ArgumentError.new("The writer must be one of #{PropInitializer::Property::VISIBILITY_OPTIONS.map(&:inspect).join(', ')}.")
|
13
|
+
end
|
14
|
+
|
15
|
+
unless PropInitializer::Property::VISIBILITY_OPTIONS.include?(predicate)
|
16
|
+
raise ArgumentError.new(p"The predicate must be one of #{PropInitializer::Property::VISIBILITY_OPTIONS.map(&:inspect).join(', ')}.")
|
17
|
+
end
|
18
|
+
|
19
|
+
if reader && :class == name
|
20
|
+
raise ArgumentError.new(
|
21
|
+
"The `:class` property should not be defined as a reader because it breaks Ruby's `Object#class` method, which PropInitializer itself depends on.",
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
unless PropInitializer::Property::KIND_OPTIONS.include?(kind)
|
26
|
+
raise ArgumentError.new("The kind must be one of #{PropInitializer::Property::KIND_OPTIONS.map(&:inspect).join(', ')}.")
|
27
|
+
end
|
28
|
+
|
29
|
+
property = __prop__initializer_property_class__.new(
|
30
|
+
name:,
|
31
|
+
kind:,
|
32
|
+
reader:,
|
33
|
+
writer:,
|
34
|
+
predicate:,
|
35
|
+
default:,
|
36
|
+
coercion:,
|
37
|
+
)
|
38
|
+
|
39
|
+
prop_initializer_properties << property
|
40
|
+
__define_prop_initializer_methods__(property)
|
41
|
+
include(__prop__initializer_extension__)
|
42
|
+
end
|
43
|
+
|
44
|
+
def prop_initializer_properties
|
45
|
+
return @prop_initializer_properties if defined?(@prop_initializer_properties)
|
46
|
+
|
47
|
+
if superclass.is_a?(PropInitializer)
|
48
|
+
@prop_initializer_properties = superclass.prop_initializer_properties.dup
|
49
|
+
else
|
50
|
+
@prop_initializer_properties = PropInitializer::Properties::Schema.new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def __prop__initializer_property_class__
|
57
|
+
PropInitializer::Property
|
58
|
+
end
|
59
|
+
|
60
|
+
def __define_prop_initializer_methods__(new_property)
|
61
|
+
__prop__initializer_extension__.module_eval(
|
62
|
+
__generate_prop_initializer_methods__(new_property),
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def __prop__initializer_extension__
|
67
|
+
if defined?(@__prop__initializer_extension__)
|
68
|
+
@__prop__initializer_extension__
|
69
|
+
else
|
70
|
+
@__prop__initializer_extension__ = Module.new
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def __generate_prop_initializer_methods__(new_property, buffer = +"")
|
75
|
+
buffer << "# frozen_string_literal: true\n"
|
76
|
+
prop_initializer_properties.generate_initializer(buffer)
|
77
|
+
prop_initializer_properties.generate_to_h(buffer)
|
78
|
+
new_property.generate_writer_method(buffer) if new_property.writer
|
79
|
+
new_property.generate_reader_method(buffer) if new_property.reader
|
80
|
+
new_property.generate_predicate_method(buffer) if new_property.predicate
|
81
|
+
buffer
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PropInitializer::Property
|
4
|
+
ORDER = { :positional => 0, :* => 1, :keyword => 2, :** => 3, :& => 4 }.freeze
|
5
|
+
RUBY_KEYWORDS = %i[alias and begin break case class def do else elsif end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield].to_h { |k| [k, "__#{k}__"] }.freeze
|
6
|
+
|
7
|
+
VISIBILITY_OPTIONS = Set[false, :private, :protected, :public].freeze
|
8
|
+
KIND_OPTIONS = Set[:positional, :*, :keyword, :**, :&].freeze
|
9
|
+
|
10
|
+
include Comparable
|
11
|
+
|
12
|
+
def initialize(name:, kind:, reader:, writer:, predicate:, default:, coercion:)
|
13
|
+
@name = name
|
14
|
+
@kind = kind
|
15
|
+
@reader = reader
|
16
|
+
@writer = writer
|
17
|
+
@predicate = predicate
|
18
|
+
@default = default
|
19
|
+
@coercion = coercion
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :name, :kind, :reader, :writer, :predicate, :default, :coercion
|
23
|
+
|
24
|
+
def optional?
|
25
|
+
default?
|
26
|
+
end
|
27
|
+
|
28
|
+
def required?
|
29
|
+
!optional?
|
30
|
+
end
|
31
|
+
|
32
|
+
def keyword?
|
33
|
+
@kind == :keyword
|
34
|
+
end
|
35
|
+
|
36
|
+
def positional?
|
37
|
+
@kind == :positional
|
38
|
+
end
|
39
|
+
|
40
|
+
def splat?
|
41
|
+
@kind == :*
|
42
|
+
end
|
43
|
+
|
44
|
+
def double_splat?
|
45
|
+
@kind == :**
|
46
|
+
end
|
47
|
+
|
48
|
+
def block?
|
49
|
+
@kind == :&
|
50
|
+
end
|
51
|
+
|
52
|
+
def default?
|
53
|
+
nil != @default
|
54
|
+
end
|
55
|
+
|
56
|
+
def param
|
57
|
+
case @kind
|
58
|
+
when :*
|
59
|
+
"*#{escaped_name}"
|
60
|
+
when :**
|
61
|
+
"**#{escaped_name}"
|
62
|
+
when :&
|
63
|
+
"&#{escaped_name}"
|
64
|
+
when :positional
|
65
|
+
escaped_name
|
66
|
+
when :keyword
|
67
|
+
"#{@name.name}:"
|
68
|
+
else
|
69
|
+
raise "You should never see this error."
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def <=>(other)
|
74
|
+
ORDER[@kind] <=> ORDER[other.kind]
|
75
|
+
end
|
76
|
+
|
77
|
+
def coerce(value, context:)
|
78
|
+
context.instance_exec(value, &@coercion)
|
79
|
+
end
|
80
|
+
|
81
|
+
def ruby_keyword?
|
82
|
+
!!RUBY_KEYWORDS[@name]
|
83
|
+
end
|
84
|
+
|
85
|
+
def escaped_name
|
86
|
+
RUBY_KEYWORDS[@name] || @name.name
|
87
|
+
end
|
88
|
+
|
89
|
+
def default_value
|
90
|
+
case @default
|
91
|
+
when Proc then @default.call
|
92
|
+
else @default
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_reader_method(buffer = +"")
|
97
|
+
buffer <<
|
98
|
+
(@reader ? @reader.name : "public") <<
|
99
|
+
"\ndef " <<
|
100
|
+
@name.name <<
|
101
|
+
"\n value = @" <<
|
102
|
+
@name.name <<
|
103
|
+
"\n value\nend\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
def generate_writer_method(buffer = +"")
|
107
|
+
buffer <<
|
108
|
+
(@writer ? @writer.name : "public") <<
|
109
|
+
" def " <<
|
110
|
+
@name.name <<
|
111
|
+
"=(value)\n" <<
|
112
|
+
" @#{@name.name} = value\nend\n"
|
113
|
+
end
|
114
|
+
|
115
|
+
def generate_predicate_method(buffer = +"")
|
116
|
+
buffer <<
|
117
|
+
(@predicate ? @predicate.name : "public") <<
|
118
|
+
" def " <<
|
119
|
+
@name.name <<
|
120
|
+
"?\n" <<
|
121
|
+
" !!@" <<
|
122
|
+
@name.name <<
|
123
|
+
"\n" <<
|
124
|
+
"end\n"
|
125
|
+
end
|
126
|
+
|
127
|
+
def generate_initializer_handle_property(buffer = +"")
|
128
|
+
buffer << " # " << @name.name << "\n" <<
|
129
|
+
" property = properties[:" << @name.name << "]\n"
|
130
|
+
|
131
|
+
if @kind == :keyword && ruby_keyword?
|
132
|
+
generate_initializer_escape_keyword(buffer)
|
133
|
+
end
|
134
|
+
|
135
|
+
if default?
|
136
|
+
generate_initializer_assign_default(buffer)
|
137
|
+
end
|
138
|
+
|
139
|
+
if @coercion
|
140
|
+
generate_initializer_coerce_property(buffer)
|
141
|
+
end
|
142
|
+
|
143
|
+
generate_initializer_assign_value(buffer)
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def generate_initializer_escape_keyword(buffer = +"")
|
149
|
+
buffer <<
|
150
|
+
escaped_name <<
|
151
|
+
" = binding.local_variable_get(:" <<
|
152
|
+
@name.name <<
|
153
|
+
")\n"
|
154
|
+
end
|
155
|
+
|
156
|
+
def generate_initializer_coerce_property(buffer = +"")
|
157
|
+
buffer <<
|
158
|
+
escaped_name <<
|
159
|
+
"= property.coerce(" <<
|
160
|
+
escaped_name <<
|
161
|
+
", context: self)\n"
|
162
|
+
end
|
163
|
+
|
164
|
+
def generate_initializer_assign_default(buffer = +"")
|
165
|
+
buffer <<
|
166
|
+
" if " <<
|
167
|
+
((@kind == :&) ? "nil" : "PropInitializer::Null") <<
|
168
|
+
" == " <<
|
169
|
+
escaped_name <<
|
170
|
+
"\n " <<
|
171
|
+
escaped_name <<
|
172
|
+
" = property.default_value\n end\n"
|
173
|
+
end
|
174
|
+
|
175
|
+
def generate_initializer_assign_value(buffer = +"")
|
176
|
+
buffer <<
|
177
|
+
" @" <<
|
178
|
+
@name.name <<
|
179
|
+
" = " <<
|
180
|
+
escaped_name <<
|
181
|
+
"\n"
|
182
|
+
end
|
183
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: prop_initializer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Bob
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-10-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: zeitwerk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.6.18
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.6.18
|
27
|
+
description: PropInitializer provides an easy way to define properties for Ruby classes
|
28
|
+
with options for defaults and customization. It simplifies the Literal gem's functionality
|
29
|
+
by removing strict type requirements and adapting the initializer process for flexibility.
|
30
|
+
email:
|
31
|
+
- paul.ionut.bob@gmail.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- MIT-LICENSE
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- lib/prop_initializer.rb
|
40
|
+
- lib/prop_initializer/null.rb
|
41
|
+
- lib/prop_initializer/properties.rb
|
42
|
+
- lib/prop_initializer/properties/schema.rb
|
43
|
+
- lib/prop_initializer/property.rb
|
44
|
+
- lib/prop_initializer/railtie.rb
|
45
|
+
- lib/prop_initializer/version.rb
|
46
|
+
- lib/tasks/prop_initializer_tasks.rake
|
47
|
+
homepage: https://github.com/avo-hq/prop_initializer
|
48
|
+
licenses:
|
49
|
+
- MIT
|
50
|
+
metadata:
|
51
|
+
homepage_uri: https://github.com/avo-hq/prop_initializer
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubygems_version: 3.5.5
|
68
|
+
signing_key:
|
69
|
+
specification_version: 4
|
70
|
+
summary: A flexible property initializer for Ruby classes inspired by the Literal
|
71
|
+
gem.
|
72
|
+
test_files: []
|