attr_bool 0.2.2 → 0.3.0

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.
data/attr_bool.gemspec CHANGED
@@ -1,53 +1,57 @@
1
1
  # encoding: UTF-8
2
2
  # frozen_string_literal: true
3
3
 
4
-
5
4
  require_relative 'lib/attr_bool/version'
6
5
 
7
6
  Gem::Specification.new do |spec|
8
7
  spec.name = 'attr_bool'
9
8
  spec.version = AttrBool::VERSION
10
- spec.authors = ['Jonathan Bradley Whited']
9
+ spec.authors = ['Bradley Whited']
11
10
  spec.email = ['code@esotericpig.com']
12
11
  spec.licenses = ['MIT']
13
12
  spec.homepage = 'https://github.com/esotericpig/attr_bool'
14
- spec.summary = 'Finally attr_accessor & attr_reader with question marks for booleans!?'
15
- spec.description = <<-DESC.gsub(/\s+/,' ').strip
13
+ spec.summary = 'Finally attr_accessor? & attr_reader? with question marks for booleans/predicates!?'
14
+ spec.description = <<~DESC
16
15
  #{spec.summary}
17
- Simply use: attr_accessor?, attr_reader?, attr_bool, attr_bool?.
18
- Default values can also be passed in as the last argument
19
- or with the 'default: ' keyword argument.
20
- In a Module/Class, extend 'AttrBool::Ext',
21
- or in the file, require 'attr_bool/core_ext'.
16
+
17
+ Pick one:
18
+ (1) in your top module, add `using AttrBool::Ref`,
19
+ or (2) in your class/module, add `extend AttrBool::Ext`,
20
+ or (3) in your app/script (not library), include `require 'attr_bool/core_ext'`.
21
+
22
+ Now simply use any:
23
+ [ attr_accessor?, attr_reader?, attr_writer?, attr_bool, attr_bool?, attr_bool! ].
24
+
25
+ Keywords: attr, attribute, attributes, bool, boolean, booleans, predicate, predicates
22
26
  DESC
23
27
 
24
28
  spec.metadata = {
25
- 'homepage_uri' => 'https://github.com/esotericpig/attr_bool',
26
- 'source_code_uri' => 'https://github.com/esotericpig/attr_bool',
27
- 'bug_tracker_uri' => 'https://github.com/esotericpig/attr_bool/issues',
28
- 'changelog_uri' => 'https://github.com/esotericpig/attr_bool/blob/master/CHANGELOG.md',
29
+ 'rubygems_mfa_required' => 'true',
30
+ 'homepage_uri' => spec.homepage,
31
+ 'source_code_uri' => 'https://github.com/esotericpig/attr_bool',
32
+ 'bug_tracker_uri' => 'https://github.com/esotericpig/attr_bool/issues',
33
+ 'changelog_uri' => 'https://github.com/esotericpig/attr_bool/blob/main/CHANGELOG.md',
29
34
  }
30
35
 
31
- spec.required_ruby_version = '>= 2.4'
36
+ spec.required_ruby_version = '>= 3.1'
32
37
  spec.require_paths = ['lib']
38
+ spec.bindir = 'bin'
39
+ spec.executables = []
40
+
41
+ spec.extra_rdoc_files = %w[LICENSE.txt README.md]
42
+ spec.rdoc_options = [
43
+ %w[--embed-mixins --hyperlink-all --line-numbers --show-hash],
44
+ '--encoding','UTF-8',
45
+ '--markup','markdown',
46
+ '--title',"AttrBool v#{AttrBool::VERSION}",
47
+ '--main','README.md',
48
+ ].flatten
33
49
 
34
50
  spec.files = [
35
- Dir.glob(File.join("{#{spec.require_paths.join(',')}}",'**','*.{erb,rb}')),
36
- %W[ Gemfile #{spec.name}.gemspec Rakefile ],
37
- %w[ LICENSE.txt ],
51
+ Dir.glob("{#{spec.require_paths.join(',')}}/**/*.{erb,rb}"),
52
+ Dir.glob("#{spec.bindir}/*"),
53
+ Dir.glob('{spec,test}/**/*.{erb,rb}'),
54
+ %W[.rdoc_options Gemfile #{spec.name}.gemspec Rakefile],
55
+ spec.extra_rdoc_files,
38
56
  ].flatten
39
-
40
- spec.add_development_dependency 'bundler' ,'~> 2.2'
41
- spec.add_development_dependency 'minitest' ,'~> 5.14'
42
- spec.add_development_dependency 'rake' ,'~> 13.0'
43
- spec.add_development_dependency 'rdoc' ,'~> 6.3' # YARDoc RDoc (*.rb)
44
- spec.add_development_dependency 'redcarpet' ,'~> 3.5' # YARDoc Markdown (*.md)
45
- spec.add_development_dependency 'yard' ,'~> 0.9' # Doc
46
-
47
- spec.extra_rdoc_files = %w[ LICENSE.txt ]
48
-
49
- spec.rdoc_options = [
50
- '--hyperlink-all','--show-hash',
51
- '--title',"AttrBool v#{AttrBool::VERSION} Doc",
52
- ]
53
57
  end
@@ -3,22 +3,12 @@
3
3
 
4
4
  #--
5
5
  # This file is part of AttrBool.
6
- # Copyright (c) 2020-2021 Jonathan Bradley Whited
6
+ # Copyright (c) 2020 Bradley Whited
7
7
  #
8
8
  # SPDX-License-Identifier: MIT
9
9
  #++
10
10
 
11
-
12
11
  require 'attr_bool'
13
12
 
14
- module AttrBool
15
- ###
16
- # @author Jonathan Bradley Whited
17
- # @since 0.2.0
18
- ###
19
- module CoreExt
20
- end
21
- end
22
-
23
- # This works for both +class+ & +module+ because +class+ extends +module+.
24
- Module.prepend AttrBool::Ext
13
+ # This works for both classes & modules because Class is a child of Module.
14
+ Module.prepend(AttrBool::Ext)
@@ -3,12 +3,11 @@
3
3
 
4
4
  #--
5
5
  # This file is part of AttrBool.
6
- # Copyright (c) 2020-2021 Jonathan Bradley Whited
6
+ # Copyright (c) 2020 Bradley Whited
7
7
  #
8
8
  # SPDX-License-Identifier: MIT
9
9
  #++
10
10
 
11
-
12
11
  module AttrBool
13
- VERSION = '0.2.2'
12
+ VERSION = '0.3.0'
14
13
  end
data/lib/attr_bool.rb CHANGED
@@ -3,180 +3,227 @@
3
3
 
4
4
  #--
5
5
  # This file is part of AttrBool.
6
- # Copyright (c) 2020-2021 Jonathan Bradley Whited
6
+ # Copyright (c) 2020 Bradley Whited
7
7
  #
8
8
  # SPDX-License-Identifier: MIT
9
9
  #++
10
10
 
11
-
12
11
  require 'attr_bool/version'
13
12
 
14
- ###
15
- # @author Jonathan Bradley Whited
16
- # @since 0.1.0
17
- ###
13
+ ##
14
+ # Example usage:
15
+ # ```
16
+ # require 'attr_bool'
17
+ #
18
+ # class TheTodd
19
+ # extend AttrBool::Ext
20
+ # #using AttrBool::Ref # Can use refinements instead.
21
+ #
22
+ # # Can use multiple symbols and/or strings.
23
+ # attr_accessor? :flexing, 'bounce_pecs'
24
+ #
25
+ # # Can do DSL chaining.
26
+ # protected attr_accessor? :high_five, 'fist_bump'
27
+ #
28
+ # # Can do custom logic.
29
+ # attr_accessor? :headband, 'banana_hammock',
30
+ # reader: -> { @wearing == :flaming },
31
+ # writer: ->(value) { @wearing = value }
32
+ #
33
+ # attr_reader?(:cat_fights) { @cat_fights % 69 }
34
+ # attr_writer?(:hot_surgeries) { |count| @hot_surgeries += count }
35
+ #
36
+ # # Can force bool values (i.e., only `true` or `false`).
37
+ # attr_bool :carla_kiss # Accessor.
38
+ # attr_bool? :elliot_kiss # Reader.
39
+ # attr_bool! :thumbs_up # Writer.
40
+ # end
41
+ # ```
18
42
  module AttrBool
19
- ###
20
- # Benchmarks are kind of meaningless, but after playing around with some,
21
- # I found the following to be the case on my system:
22
- # - +define_method+ is faster than +module_eval+ & +class_eval+
23
- # - +? true : false+ (ternary operator) is faster than +!!+ (surprisingly)
43
+ ##
44
+ # Example usage:
45
+ # ```
46
+ # class TheTodd
47
+ # extend AttrBool::Ext
24
48
  #
25
- # To run benchmark code:
26
- # $ bundle exec rake benchmark
49
+ # attr_accessor? :headband
50
+ # attr_reader? :banana_hammock
51
+ # attr_writer? :high_five
27
52
  #
28
- # @author Jonathan Bradley Whited
29
- # @since 0.2.0
30
- ###
53
+ # attr_bool :bounce_pecs
54
+ # attr_bool? :cat_fight
55
+ # attr_bool! :hot_tub
56
+ # end
57
+ # ```
31
58
  module Ext
32
- def attr_accessor?(*var_ids,default: nil,reader: nil,writer: nil,&block)
33
- if block
34
- reader = block if reader.nil?
35
- writer = block if writer.nil?
36
- end
37
-
38
- if default.nil? && reader.nil?
39
- last = var_ids[-1]
40
-
41
- if !last.is_a?(String) && !last.is_a?(Symbol)
42
- default = var_ids.pop
43
- end
44
- end
45
-
46
- attr_reader?(*var_ids,default: default,&reader)
47
- attr_writer?(*var_ids,&writer)
59
+ #--
60
+ # NOTE: Not using `self.` for extended/included/prepended() so that including a module that extends
61
+ # `AttrBool::Ext` works without having to extend `AttrBool::Ext` again.
62
+ #++
63
+
64
+ def extended(mod)
65
+ super
66
+ __attr_bool_extended(mod)
48
67
  end
49
68
 
50
- def attr_reader?(*var_ids,default: nil,&block)
51
- no_default = (default.nil? && !block)
52
-
53
- if no_default
54
- last = var_ids[-1]
55
-
56
- if !last.is_a?(String) && !last.is_a?(Symbol)
57
- default = var_ids.pop
58
- no_default = false
59
- end
60
- end
69
+ def included(mod)
70
+ super
71
+ __attr_bool_extended(mod)
72
+ end
61
73
 
62
- var_ids.each do |var_id|
63
- var_id_q = :"#{var_id}?"
74
+ def prepended(mod)
75
+ super
76
+ __attr_bool_extended(mod)
77
+ end
64
78
 
65
- if no_default
66
- define_method(var_id_q) do
67
- instance_variable_get(:"@#{var_id}")
68
- end
69
- else
70
- if block
71
- define_method(var_id_q,&block)
72
- else
73
- at_var_id = :"@#{var_id}"
74
-
75
- define_method(var_id_q) do
76
- instance_variable_defined?(at_var_id) ? instance_variable_get(at_var_id) : default
77
- end
78
- end
79
- end
80
- end
79
+ def attr_accessor?(*names,reader: nil,writer: nil)
80
+ return __attr_bool(names,reader: reader,writer: writer)
81
81
  end
82
82
 
83
- # This should only be used when you want to pass in a block/proc.
84
- def attr_writer?(*var_ids,&block)
85
- if block
86
- var_ids.each do |var_id|
87
- define_method(:"#{var_id}=",&block)
88
- end
89
- else
90
- last = var_ids[-1]
83
+ def attr_reader?(*names,&reader)
84
+ return __attr_bool(names,reader: reader)
85
+ end
91
86
 
92
- if !last.is_a?(String) && !last.is_a?(Symbol)
93
- raise ArgumentError,'default value not allowed for writer'
94
- end
87
+ def attr_writer?(*names,&writer)
88
+ return __attr_bool(names,writer: writer)
89
+ end
95
90
 
96
- attr_writer(*var_ids)
97
- end
91
+ def attr_bool(*names,reader: nil,writer: nil)
92
+ return __attr_bool(names,reader: reader,writer: writer,force_bool: true)
98
93
  end
99
94
 
100
- def attr_bool(*var_ids,default: nil,reader: nil,writer: nil,&block)
101
- if block
102
- reader = block if reader.nil?
103
- writer = block if writer.nil?
104
- end
95
+ def attr_bool?(*names,&reader)
96
+ return __attr_bool(names,reader: reader,force_bool: true)
97
+ end
105
98
 
106
- if default.nil? && reader.nil?
107
- last = var_ids[-1]
99
+ def attr_bool!(*names,&writer)
100
+ return __attr_bool(names,writer: writer,force_bool: true)
101
+ end
108
102
 
109
- if !last.is_a?(String) && !last.is_a?(Symbol)
110
- default = var_ids.pop
111
- end
112
- end
103
+ private
113
104
 
114
- attr_bool?(*var_ids,default: default,&reader)
115
- attr_booler(*var_ids,&writer)
105
+ def __attr_bool_extended(mod)
106
+ mod.extend(AttrBool::Ext) unless mod.singleton_class.ancestors.include?(AttrBool::Ext)
116
107
  end
117
- alias_method :attr_boolor,:attr_bool
118
108
 
119
- def attr_bool?(*var_ids,default: nil,&block)
120
- no_default = default.nil?
109
+ def __attr_bool(names,reader: false,writer: false,force_bool: false)
110
+ # For DSL chaining, must return the method names created, like core `attr_accessor`/etc. does.
111
+ # Example: protected attr_bool :banana_hammock,:bounce_pecs
112
+ method_names = []
121
113
 
122
- if no_default
123
- no_default = !block
114
+ # noinspection RubySimplifyBooleanInspection
115
+ names.map do |name|
116
+ ivar = :"@#{name}"
124
117
 
125
- if no_default
126
- last = var_ids[-1]
118
+ if reader != false # false, nil, or Proc.
119
+ name_q = :"#{name}?"
120
+ method_names << name_q
127
121
 
128
- if !last.is_a?(String) && !last.is_a?(Symbol)
129
- default = var_ids.pop ? true : false
130
- no_default = false
122
+ if reader # Proc?
123
+ if force_bool
124
+ define_method(name_q) { instance_exec(&reader) ? true : false }
125
+ else
126
+ define_method(name_q,&reader)
127
+ end
128
+ else # nil?
129
+ instance_variable_get(ivar) # Fail fast if `ivar` is invalid.
130
+
131
+ if force_bool
132
+ define_method(name_q) { instance_variable_get(ivar) ? true : false }
133
+ else
134
+ define_method(name_q) { instance_variable_get(ivar) }
135
+ end
131
136
  end
132
137
  end
133
- else
134
- default = default ? true : false
135
- end
136
138
 
137
- var_ids.each do |var_id|
138
- var_id_q = :"#{var_id}?"
139
+ if writer != false # false, nil, or Proc.
140
+ name_eq = :"#{name}="
141
+ method_names << name_eq
139
142
 
140
- if no_default
141
- define_method(var_id_q) do
142
- instance_variable_get(:"@#{var_id}") ? true : false
143
- end
144
- else
145
- if block
146
- define_method(var_id_q,&block)
147
- else
148
- at_var_id = :"@#{var_id}"
149
-
150
- define_method(var_id_q) do
151
- if instance_variable_defined?(at_var_id)
152
- instance_variable_get(at_var_id) ? true : false
153
- else
154
- default
155
- end
143
+ if writer # Proc?
144
+ if force_bool
145
+ define_method(name_eq) { |value| instance_exec(value ? true : false,&writer) }
146
+ else
147
+ define_method(name_eq,&writer)
148
+ end
149
+ else # nil?
150
+ instance_variable_get(ivar) # Fail fast if `ivar` is invalid.
151
+
152
+ if force_bool
153
+ define_method(name_eq) { |value| instance_variable_set(ivar,value ? true : false) }
154
+ else
155
+ define_method(name_eq) { |value| instance_variable_set(ivar,value) }
156
156
  end
157
157
  end
158
158
  end
159
159
  end
160
+
161
+ return method_names
160
162
  end
163
+ end
161
164
 
162
- def attr_booler(*var_ids,&block)
163
- if !block
164
- last = var_ids[-1]
165
+ ##
166
+ # Example usage:
167
+ # ```
168
+ # module TheToddMod
169
+ # using AttrBool::Ref
170
+ #
171
+ # class TheTodd
172
+ # attr_accessor? :headband
173
+ # attr_reader? :banana_hammock
174
+ # attr_writer? :high_five
175
+ #
176
+ # attr_bool :bounce_pecs
177
+ # attr_bool? :cat_fight
178
+ # attr_bool! :hot_tub
179
+ # end
180
+ # end
181
+ # ```
182
+ module Ref
183
+ # This works for both classes & modules because Class is a child of Module.
184
+ refine Module do
185
+ import_methods AttrBool::Ext
186
+
187
+ # NOTE: JRuby (and maybe other implementations?) has a bug with importing methods that internally
188
+ # call other refined methods, so this is the workaround.
189
+ if RUBY_PLATFORM == 'java'
190
+ def extended(mod)
191
+ super
192
+ __attr_bool_extended(mod)
193
+ end
165
194
 
166
- if !last.is_a?(String) && !last.is_a?(Symbol)
167
- raise ArgumentError,'default value not allowed for writer'
195
+ def included(mod)
196
+ super
197
+ __attr_bool_extended(mod)
168
198
  end
169
- end
170
199
 
171
- var_ids.each do |var_id|
172
- var_id_eq = :"#{var_id}="
200
+ def prepended(mod)
201
+ super
202
+ __attr_bool_extended(mod)
203
+ end
173
204
 
174
- if block
175
- define_method(var_id_eq,&block)
176
- else
177
- define_method(var_id_eq) do |value|
178
- instance_variable_set(:"@#{var_id}",value ? true : false)
179
- end
205
+ def attr_accessor?(*names,reader: nil,writer: nil)
206
+ return __attr_bool(names,reader: reader,writer: writer)
207
+ end
208
+
209
+ def attr_reader?(*names,&reader)
210
+ return __attr_bool(names,reader: reader)
211
+ end
212
+
213
+ def attr_writer?(*names,&writer)
214
+ return __attr_bool(names,writer: writer)
215
+ end
216
+
217
+ def attr_bool(*names,reader: nil,writer: nil)
218
+ return __attr_bool(names,reader: reader,writer: writer,force_bool: true)
219
+ end
220
+
221
+ def attr_bool?(*names,&reader)
222
+ return __attr_bool(names,reader: reader,force_bool: true)
223
+ end
224
+
225
+ def attr_bool!(*names,&writer)
226
+ return __attr_bool(names,writer: writer,force_bool: true)
180
227
  end
181
228
  end
182
229
  end
@@ -0,0 +1,17 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ #--
5
+ # This file is part of AttrBool.
6
+ # Copyright (c) 2020 Bradley Whited
7
+ #
8
+ # SPDX-License-Identifier: MIT
9
+ #++
10
+
11
+ require 'test_helper'
12
+
13
+ describe AttrBool do
14
+ it 'has the version' do
15
+ _(AttrBool::VERSION).must_match(/\d+\.\d+\.\d+(-[0-9A-Za-z\-.]+)?(\+[0-9A-Za-z\-.]+)?/)
16
+ end
17
+ end
@@ -0,0 +1,103 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ #--
5
+ # This file is part of AttrBool.
6
+ # Copyright (c) 2020 Bradley Whited
7
+ #
8
+ # SPDX-License-Identifier: MIT
9
+ #++
10
+
11
+ require 'test_helper'
12
+
13
+ require 'attr_bool/core_ext'
14
+
15
+ describe 'attr_bool/core_ext' do
16
+ it 'monkey-patches the core Class & Module only once' do
17
+ _(Class.ancestors.count(AttrBool::Ext)).must_equal(1)
18
+ _(Module.ancestors.count(AttrBool::Ext)).must_equal(1)
19
+ end
20
+
21
+ def self.it_has_the_attr_bools
22
+ it 'has the attr accessors' do
23
+ _(@sut).must_respond_to(:acc01=)
24
+ _(@sut).must_respond_to(:acc02=)
25
+
26
+ @sut.acc01 = :acc01_value
27
+ @sut.acc02 = :acc02_value
28
+
29
+ _(@sut.acc01?).must_equal(:acc01_value)
30
+ _(@sut.acc02?).must_equal(true)
31
+ end
32
+
33
+ it 'has the attr readers' do
34
+ _(@sut.read01?).must_equal(:read01_value)
35
+ _(@sut.read02?).must_equal(true)
36
+ end
37
+
38
+ it 'has the attr writers' do
39
+ _(@sut).must_respond_to(:write01=)
40
+ _(@sut).must_respond_to(:write02=)
41
+
42
+ @sut.write01 = :write01_value
43
+ @sut.write02 = :write02_value
44
+
45
+ _(@sut.instance_variable_get(:@write01)).must_equal(:write01_value)
46
+ _(@sut.instance_variable_get(:@write02)).must_equal(true)
47
+ end
48
+ end
49
+
50
+ describe 'core class' do
51
+ before do
52
+ @sut = CoreExtTest::TestBag.new
53
+ end
54
+
55
+ it_has_the_attr_bools
56
+ end
57
+
58
+ describe 'core module' do
59
+ before do
60
+ @sut = CoreExtTest::TestBagWithMixin.new
61
+ end
62
+
63
+ it_has_the_attr_bools
64
+ end
65
+ end
66
+
67
+ module CoreExtTest
68
+ class TestBag
69
+ attr_accessor? :acc01
70
+ attr_bool :acc02
71
+ attr_reader? :read01
72
+ attr_bool? :read02
73
+ attr_writer? :write01
74
+ attr_bool! :write02
75
+
76
+ def initialize
77
+ super
78
+
79
+ @read01 = :read01_value
80
+ @read02 = :read02_value
81
+ end
82
+ end
83
+
84
+ module TestBagMixin
85
+ attr_accessor? :acc01
86
+ attr_bool :acc02
87
+ attr_reader? :read01
88
+ attr_bool? :read02
89
+ attr_writer? :write01
90
+ attr_bool! :write02
91
+
92
+ def initialize
93
+ super
94
+
95
+ @read01 = :read01_value
96
+ @read02 = :read02_value
97
+ end
98
+ end
99
+
100
+ class TestBagWithMixin
101
+ include TestBagMixin
102
+ end
103
+ end