prop_initializer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PropInitializer::Null
4
+ def self.inspect
5
+ "PropInitializer::Null"
6
+ end
7
+
8
+ freeze
9
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ module PropInitializer
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module PropInitializer
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,11 @@
1
+ require "prop_initializer/version"
2
+ require "prop_initializer/railtie"
3
+
4
+ require "zeitwerk"
5
+
6
+ loader = Zeitwerk::Loader.for_gem
7
+ loader.setup
8
+
9
+ module PropInitializer
10
+ # Your code goes here...
11
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :prop_initializer do
3
+ # # Task goes here
4
+ # 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: []