attr_bool 0.2.1 → 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,64 +1,57 @@
1
1
  # encoding: UTF-8
2
2
  # frozen_string_literal: true
3
3
 
4
- #--
5
- # This file is part of AttrBool.
6
- # Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
7
- #
8
- # AttrBool is free software: you can redistribute it and/or modify it under
9
- # the terms of the MIT License.
10
- #
11
- # You should have received a copy of the MIT License along with AttrBool.
12
- # If not, see <https://choosealicense.com/licenses/mit/>.
13
- #++
14
-
15
-
16
4
  require_relative 'lib/attr_bool/version'
17
5
 
18
- Gem::Specification.new() do |spec|
6
+ Gem::Specification.new do |spec|
19
7
  spec.name = 'attr_bool'
20
8
  spec.version = AttrBool::VERSION
21
- spec.authors = ['Jonathan Bradley Whited (@esotericpig)']
22
- spec.email = ['bradley@esotericpig.com']
9
+ spec.authors = ['Bradley Whited']
10
+ spec.email = ['code@esotericpig.com']
23
11
  spec.licenses = ['MIT']
24
12
  spec.homepage = 'https://github.com/esotericpig/attr_bool'
25
- spec.summary = 'Finally attr_accessor & attr_reader with question marks for booleans!?'
26
- spec.description = <<-EOD.gsub(/\s+/,' ').strip()
13
+ spec.summary = 'Finally attr_accessor? & attr_reader? with question marks for booleans/predicates!?'
14
+ spec.description = <<~DESC
27
15
  #{spec.summary}
28
- Simply use: attr_accessor?, attr_reader?, attr_bool, attr_bool?.
29
- Default values can also be passed in as the last argument
30
- or with the 'default: ' keyword argument.
31
- In a Module/Class, extend 'AttrBool::Ext',
32
- or in the file, require 'attr_bool/core_ext'.
33
- EOD
34
-
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
26
+ DESC
27
+
35
28
  spec.metadata = {
36
- 'homepage_uri' => 'https://github.com/esotericpig/attr_bool',
37
- 'source_code_uri' => 'https://github.com/esotericpig/attr_bool',
38
- 'bug_tracker_uri' => 'https://github.com/esotericpig/attr_bool/issues',
39
- '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',
40
34
  }
41
-
42
- spec.required_ruby_version = '>= 2.4'
35
+
36
+ spec.required_ruby_version = '>= 3.1'
43
37
  spec.require_paths = ['lib']
44
-
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
49
+
45
50
  spec.files = [
46
- Dir.glob(File.join("{#{spec.require_paths.join(',')}}",'**','*.{erb,rb}')),
47
- %W[ Gemfile #{spec.name}.gemspec Rakefile ],
48
- %w[ LICENSE.txt ],
49
- ].flatten()
50
-
51
- spec.add_development_dependency 'bundler' ,'~> 2.1'
52
- spec.add_development_dependency 'minitest' ,'~> 5.14'
53
- spec.add_development_dependency 'rake' ,'~> 13.0'
54
- spec.add_development_dependency 'rdoc' ,'~> 6.2' # YARDoc RDoc (*.rb)
55
- spec.add_development_dependency 'redcarpet' ,'~> 3.5' # YARDoc Markdown (*.md)
56
- spec.add_development_dependency 'yard' ,'~> 0.9' # Doc
57
-
58
- spec.extra_rdoc_files = %w[ LICENSE.txt ]
59
-
60
- spec.rdoc_options = [
61
- '--hyperlink-all','--show-hash',
62
- '--title',"AttrBool v#{AttrBool::VERSION} Doc",
63
- ]
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,
56
+ ].flatten
64
57
  end
@@ -1,29 +1,14 @@
1
- #!/usr/bin/env ruby
2
1
  # encoding: UTF-8
3
2
  # frozen_string_literal: true
4
3
 
5
4
  #--
6
5
  # This file is part of AttrBool.
7
- # Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
8
- #
9
- # AttrBool is free software: you can redistribute it and/or modify it under
10
- # the terms of the MIT License.
11
- #
12
- # You should have received a copy of the MIT License along with AttrBool.
13
- # If not, see <https://choosealicense.com/licenses/mit/>.
6
+ # Copyright (c) 2020 Bradley Whited
7
+ #
8
+ # SPDX-License-Identifier: MIT
14
9
  #++
15
10
 
16
-
17
11
  require 'attr_bool'
18
12
 
19
- module AttrBool
20
- ###
21
- # @author Jonathan Bradley Whited (@esotericpig)
22
- # @since 0.2.0
23
- ###
24
- module CoreExt
25
- end
26
- end
27
-
28
- # This works for both +class+ & +module+ because +class+ extends +module+.
29
- Module.prepend AttrBool::Ext
13
+ # This works for both classes & modules because Class is a child of Module.
14
+ Module.prepend(AttrBool::Ext)
@@ -1,19 +1,13 @@
1
- #!/usr/bin/env ruby
2
1
  # encoding: UTF-8
3
2
  # frozen_string_literal: true
4
3
 
5
4
  #--
6
5
  # This file is part of AttrBool.
7
- # Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
8
- #
9
- # AttrBool is free software: you can redistribute it and/or modify it under
10
- # the terms of the MIT License.
11
- #
12
- # You should have received a copy of the MIT License along with AttrBool.
13
- # If not, see <https://choosealicense.com/licenses/mit/>.
6
+ # Copyright (c) 2020 Bradley Whited
7
+ #
8
+ # SPDX-License-Identifier: MIT
14
9
  #++
15
10
 
16
-
17
11
  module AttrBool
18
- VERSION = '0.2.1'
12
+ VERSION = '0.3.0'
19
13
  end
data/lib/attr_bool.rb CHANGED
@@ -1,187 +1,229 @@
1
- #!/usr/bin/env ruby
2
1
  # encoding: UTF-8
3
2
  # frozen_string_literal: true
4
3
 
5
4
  #--
6
5
  # This file is part of AttrBool.
7
- # Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
8
- #
9
- # AttrBool is free software: you can redistribute it and/or modify it under
10
- # the terms of the MIT License.
11
- #
12
- # You should have received a copy of the MIT License along with AttrBool.
13
- # If not, see <https://choosealicense.com/licenses/mit/>.
6
+ # Copyright (c) 2020 Bradley Whited
7
+ #
8
+ # SPDX-License-Identifier: MIT
14
9
  #++
15
10
 
16
-
17
11
  require 'attr_bool/version'
18
12
 
19
- ###
20
- # @author Jonathan Bradley Whited (@esotericpig)
21
- # @since 0.1.0
22
- ###
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
+ # ```
23
42
  module AttrBool
24
- ###
25
- # Benchmarks are kind of meaningless, but after playing around with some,
26
- # I found the following to be the case on my system:
27
- # - +define_method+ is faster than +module_eval+ & +class_eval+
28
- # - +? true : false+ (ternary operator) is faster than +!!+ (surprisingly)
29
- #
30
- # To run benchmark code:
31
- # $ bundle exec rake benchmark
32
- #
33
- # @author Jonathan Bradley Whited (@esotericpig)
34
- # @since 0.2.0
35
- ###
43
+ ##
44
+ # Example usage:
45
+ # ```
46
+ # class TheTodd
47
+ # extend AttrBool::Ext
48
+ #
49
+ # attr_accessor? :headband
50
+ # attr_reader? :banana_hammock
51
+ # attr_writer? :high_five
52
+ #
53
+ # attr_bool :bounce_pecs
54
+ # attr_bool? :cat_fight
55
+ # attr_bool! :hot_tub
56
+ # end
57
+ # ```
36
58
  module Ext
37
- def attr_accessor?(*var_ids,default: nil,reader: nil,writer: nil,&block)
38
- if block
39
- reader = block if reader.nil?()
40
- writer = block if writer.nil?()
41
- end
42
-
43
- if default.nil?() && reader.nil?()
44
- last = var_ids[-1]
45
-
46
- if !last.is_a?(String) && !last.is_a?(Symbol)
47
- default = var_ids.pop()
48
- end
49
- end
50
-
51
- attr_reader?(*var_ids,default: default,&reader)
52
- 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)
53
67
  end
54
-
55
- def attr_reader?(*var_ids,default: nil,&block)
56
- no_default = (default.nil?() && !block)
57
-
58
- if no_default
59
- last = var_ids[-1]
60
-
61
- if !last.is_a?(String) && !last.is_a?(Symbol)
62
- default = var_ids.pop()
63
- no_default = false
64
- end
65
- end
66
-
67
- var_ids.each() do |var_id|
68
- var_id_q = :"#{var_id}?"
69
-
70
- if no_default
71
- define_method(var_id_q) do
72
- instance_variable_get(:"@#{var_id}")
68
+
69
+ def included(mod)
70
+ super
71
+ __attr_bool_extended(mod)
72
+ end
73
+
74
+ def prepended(mod)
75
+ super
76
+ __attr_bool_extended(mod)
77
+ end
78
+
79
+ def attr_accessor?(*names,reader: nil,writer: nil)
80
+ return __attr_bool(names,reader: reader,writer: writer)
81
+ end
82
+
83
+ def attr_reader?(*names,&reader)
84
+ return __attr_bool(names,reader: reader)
85
+ end
86
+
87
+ def attr_writer?(*names,&writer)
88
+ return __attr_bool(names,writer: writer)
89
+ end
90
+
91
+ def attr_bool(*names,reader: nil,writer: nil)
92
+ return __attr_bool(names,reader: reader,writer: writer,force_bool: true)
93
+ end
94
+
95
+ def attr_bool?(*names,&reader)
96
+ return __attr_bool(names,reader: reader,force_bool: true)
97
+ end
98
+
99
+ def attr_bool!(*names,&writer)
100
+ return __attr_bool(names,writer: writer,force_bool: true)
101
+ end
102
+
103
+ private
104
+
105
+ def __attr_bool_extended(mod)
106
+ mod.extend(AttrBool::Ext) unless mod.singleton_class.ancestors.include?(AttrBool::Ext)
107
+ end
108
+
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 = []
113
+
114
+ # noinspection RubySimplifyBooleanInspection
115
+ names.map do |name|
116
+ ivar = :"@#{name}"
117
+
118
+ if reader != false # false, nil, or Proc.
119
+ name_q = :"#{name}?"
120
+ method_names << name_q
121
+
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
73
136
  end
74
- else
75
- if block
76
- define_method(var_id_q,&block)
77
- else
78
- at_var_id = :"@#{var_id}"
79
-
80
- define_method(var_id_q) do
81
- instance_variable_defined?(at_var_id) ? instance_variable_get(at_var_id) : default
137
+ end
138
+
139
+ if writer != false # false, nil, or Proc.
140
+ name_eq = :"#{name}="
141
+ method_names << name_eq
142
+
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) }
82
156
  end
83
157
  end
84
158
  end
85
159
  end
160
+
161
+ return method_names
86
162
  end
87
-
88
- # This should only be used when you want to pass in a block/proc.
89
- def attr_writer?(*var_ids,&block)
90
- if block
91
- var_ids.each() do |var_id|
92
- define_method(:"#{var_id}=",&block)
163
+ end
164
+
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)
93
193
  end
94
- else
95
- last = var_ids[-1]
96
-
97
- if !last.is_a?(String) && !last.is_a?(Symbol)
98
- raise ArgumentError,'default value not allowed for writer'
194
+
195
+ def included(mod)
196
+ super
197
+ __attr_bool_extended(mod)
99
198
  end
100
-
101
- attr_writer(*var_ids)
102
- end
103
- end
104
-
105
- def attr_bool(*var_ids,default: nil,reader: nil,writer: nil,&block)
106
- if block
107
- reader = block if reader.nil?()
108
- writer = block if writer.nil?()
109
- end
110
-
111
- if default.nil?() && reader.nil?()
112
- last = var_ids[-1]
113
-
114
- if !last.is_a?(String) && !last.is_a?(Symbol)
115
- default = var_ids.pop()
199
+
200
+ def prepended(mod)
201
+ super
202
+ __attr_bool_extended(mod)
116
203
  end
117
- end
118
-
119
- attr_bool?(*var_ids,default: default,&reader)
120
- attr_booler(*var_ids,&writer)
121
- end
122
- alias_method :attr_boolor,:attr_bool
123
-
124
- def attr_bool?(*var_ids,default: nil,&block)
125
- no_default = default.nil?()
126
-
127
- if no_default
128
- no_default = !block
129
-
130
- if no_default
131
- last = var_ids[-1]
132
-
133
- if !last.is_a?(String) && !last.is_a?(Symbol)
134
- default = var_ids.pop() ? true : false
135
- no_default = false
136
- end
204
+
205
+ def attr_accessor?(*names,reader: nil,writer: nil)
206
+ return __attr_bool(names,reader: reader,writer: writer)
137
207
  end
138
- else
139
- default = default ? true : false
140
- end
141
-
142
- var_ids.each() do |var_id|
143
- var_id_q = :"#{var_id}?"
144
-
145
- if no_default
146
- define_method(var_id_q) do
147
- instance_variable_get(:"@#{var_id}") ? true : false
148
- end
149
- else
150
- if block
151
- define_method(var_id_q,&block)
152
- else
153
- at_var_id = :"@#{var_id}"
154
-
155
- define_method(var_id_q) do
156
- if instance_variable_defined?(at_var_id)
157
- instance_variable_get(at_var_id) ? true : false
158
- else
159
- default
160
- end
161
- end
162
- end
208
+
209
+ def attr_reader?(*names,&reader)
210
+ return __attr_bool(names,reader: reader)
163
211
  end
164
- end
165
- end
166
-
167
- def attr_booler(*var_ids,&block)
168
- if !block
169
- last = var_ids[-1]
170
-
171
- if !last.is_a?(String) && !last.is_a?(Symbol)
172
- raise ArgumentError,'default value not allowed for writer'
212
+
213
+ def attr_writer?(*names,&writer)
214
+ return __attr_bool(names,writer: writer)
173
215
  end
174
- end
175
-
176
- var_ids.each() do |var_id|
177
- var_id_eq = :"#{var_id}="
178
-
179
- if block
180
- define_method(var_id_eq,&block)
181
- else
182
- define_method(var_id_eq) do |value|
183
- instance_variable_set(:"@#{var_id}",value ? true : false)
184
- 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)
185
227
  end
186
228
  end
187
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