switches.rb 0.11.0 → 0.12.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc49f311d672f64884c36a8b1645d17be83d83df5e630a272a4740db00a48448
4
- data.tar.gz: 9a5b8accb318737020f7d1c03dfee96ae21529dafe357aabc62abcbd5314dc5a
3
+ metadata.gz: 60984de78f4a7074bd1f41f4293bcf5c4fb4d837c138bb59f2b7d3519cf17372
4
+ data.tar.gz: ed1fd8d58a77fd2335d047dea552326759291b7f985cc027004b0bde774e603b
5
5
  SHA512:
6
- metadata.gz: 0171a8d12cacc99884b6606f08fb5fbf2ac048e5f9d8ed147e3bfe224a2724d2ecb6fc6d077edc9e533a2a7d4dfd2a0fb9c8563f63824ed0cd78506f6c708795
7
- data.tar.gz: 7888b090acfe50edc7ded58b12b005232be881e0c9489edb07dec7fd126ec7afb8ef1a1cb926682e0eec96bd19efc4a5f26e9a555341f637e3caa60a905f04b8
6
+ metadata.gz: '080a361c32b9e9db115abe54e677cbd4ceff39d8f895c8a2d4e4819bf27b81393e5f3636550f12b52e547d9aa58398302d29506fe67c0ff9e2215a27f78f6aa9'
7
+ data.tar.gz: 4dc836fb4dc0195323b31013af2367657eb1d8ac21cb87a1ec09d0dc7b2d0e6aebe164ae49475a866053765ec5b9047734d64e580076fcf78f75c802107dc569
data/CHANGELOG CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## 20260816
4
4
 
5
+ 0.12.0: Refuse a switch whose accessor is a method the instance already has, rather than losing it.
6
+
7
+ 1. + lib/Switches.rb: Switches#check_switch_names, called first in #do_set and #do_action, which raises where respond_to?(accessor) is true, #method_missing firing only where no method exists.
8
+ 2. + lib/Switches.rb: SwitchNameCollision < ArgumentError.
9
+ 3. ~ README.md: + a Notes entry for the refusal, and for the booleans and OptionParser names it does not cover.
10
+ 4. + spec/all.rb: the refusal through #set and #perform, the message, that nothing reaches the parser first, and the two legal cases.
11
+
5
12
  0.11.0: Forward to OptionParser, which #method_missing has never actually done.
6
13
 
7
14
  1. ~ lib/Switches.rb: #method_missing tested (@op.methods - Switches.instance_methods).include?(method_name.to_s), which compares a String against an array of Symbols and so was never true. Every call fell through to the settings OpenStruct, which accepts anything, so s.banner = '...' — documented in the README's "With a banner" example — silently became a setting named banner while the parser kept its default of "Usage: <program_name> [options]". The comparison is now made in symbols, and the forwarding works: #banner=, #separator, #program_name= and the other 58 OptionParser methods reach the parser.
data/README.md CHANGED
@@ -192,6 +192,7 @@ switches.class # => Hash
192
192
  ## Notes
193
193
 
194
194
  - The switch `-?` can't be used, since there is no Ruby method `#?` for it to map to.
195
+ - A switch whose accessor would be a method the instance already has is refused at declaration, with a `SwitchNameCollision`. `s.set(:tap)` raises, `#tap` being `Kernel#tap` and so never reaching the settings; so do `Switches`' own methods, `s.set(:settings)` and `s.set(:set)` among them. The same name is fine as a boolean, since `s.boolean(:tap)` reads back through `#tap?`, and fine where the collision is with `OptionParser` rather than the instance, a declared switch winning over the parser.
195
196
  - A call which is neither a switch nor a `Switches` method is forwarded to the underlying `OptionParser`, which is what makes `s.banner = '...'` and `s.separator '...'` work. A declared switch wins over an `OptionParser` method of the same name, so `s.set(:version)` still reads back through `#version`; without that rule a switch called `--version`, `--help`, `--load` or `--order` would be shadowed by the parser, all four being names `OptionParser` has. The names Ruby may call implicitly — `#to_a`, `#to_s` and the rest of `Switches::NEVER_FORWARDED` — are never forwarded, since `OptionParser#to_a` answers with the help and would otherwise be what `Array(switches)` and `[*switches]` gave you.
196
197
  - There is a clear demarcation between whether a switch's argument is required and whether the switch itself is required.
197
198
  - The bang interface methods (for example `set!` and `required!`) modify the switch in place with a required argument, rather than relying on a default or a value set elsewhere and later.
@@ -2,5 +2,5 @@
2
2
  # Switches::VERSION
3
3
 
4
4
  class Switches
5
- VERSION = '0.11.0'
5
+ VERSION = '0.12.0'
6
6
  end
data/lib/Switches.rb CHANGED
@@ -150,6 +150,7 @@ module CastingInterfaceMethods
150
150
  end
151
151
 
152
152
  class RequiredSwitchMissing < RuntimeError; end
153
+ class SwitchNameCollision < ArgumentError; end
153
154
 
154
155
  class Switches
155
156
 
@@ -257,6 +258,7 @@ class Switches
257
258
  private
258
259
 
259
260
  def do_set(requires_argument, *attrs, &block)
261
+ check_switch_names(*attrs)
260
262
  set_if_required_switch(*attrs)
261
263
  options = attrs.extract_options!
262
264
  @all_switches = @all_switches + attrs.collect{|a| a.to_s}
@@ -268,6 +270,7 @@ class Switches
268
270
  end
269
271
 
270
272
  def do_action(requires_argument, *attrs, &block)
273
+ check_switch_names(*attrs)
271
274
  attrs.each do |attr|
272
275
  @settings.send(attr.to_s + '=', nil) # Needs to be set prior to checking for required switches, since that check relies upon the key having been set in @settings.
273
276
  end
@@ -299,6 +302,14 @@ class Switches
299
302
  @all_switches.include?(method_name.to_s.delete_suffix('='))
300
303
  end
301
304
 
305
+ def check_switch_names(*attrs)
306
+ attrs.each do |attr|
307
+ next unless attr.is_a?(Symbol) || attr.is_a?(String)
308
+ next unless respond_to?(attr.to_s)
309
+ raise SwitchNameCollision, "switch, #{switch_string(attr)}, is already #{method(attr.to_s).owner}##{attr}, so it would never reach the settings"
310
+ end
311
+ end
312
+
302
313
  def switch_string(attr)
303
314
  attr = attr.to_s
304
315
  "-#{attr.long_switch? ? '-' : ''}#{attr.delete('?')}"
data/spec/all.rb CHANGED
@@ -164,6 +164,51 @@ describe Switches do
164
164
  end
165
165
  end
166
166
 
167
+ describe "switch names which collide with a method" do
168
+ def build(&block)
169
+ ARGV.clear
170
+ Switches.new(&block)
171
+ end
172
+
173
+ it "refuses a switch named after a core method" do
174
+ expect{build{|s| s.set(:tap)}}.to raise_error(SwitchNameCollision, /Kernel#tap/)
175
+ end
176
+
177
+ it "refuses a switch named after a Switches method" do
178
+ expect{build{|s| s.set(:settings)}}.to raise_error(SwitchNameCollision, /Switches#settings/)
179
+ end
180
+
181
+ it "refuses a colliding name declared through #perform" do
182
+ expect{build{|s| s.perform(:display){}}}.to raise_error(SwitchNameCollision)
183
+ end
184
+
185
+ it "names the switch in the form it would be supplied in" do
186
+ expect{build{|s| s.set(:instance_variable_get)}}.to raise_error(SwitchNameCollision, /--instance_variable_get/)
187
+ end
188
+
189
+ it "raises before the switch is declared to the parser" do
190
+ switches = Switches.new
191
+ begin; switches.set(:tap); rescue SwitchNameCollision; end
192
+ expect(switches.to_h).to eq({})
193
+ end
194
+
195
+ it "accepts the name as a boolean, the accessor then carrying a question mark" do
196
+ ARGV.clear
197
+ ARGV << '--tap'
198
+ expect(Switches.new{|s| s.boolean(:tap)}.tap?).to eq(true)
199
+ end
200
+
201
+ it "accepts a name OptionParser has, a declared switch winning over the parser" do
202
+ ARGV.clear
203
+ %w{--banner text}.each{|a| ARGV << a}
204
+ expect(Switches.new{|s| s.set(:banner)}.banner).to eq('text')
205
+ end
206
+
207
+ it "raises a SwitchNameCollision, which is an ArgumentError" do
208
+ expect(SwitchNameCollision.ancestors).to include(ArgumentError)
209
+ end
210
+ end
211
+
167
212
  describe "OptionParser methods" do
168
213
  def build(&block)
169
214
  ARGV.clear
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switches.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran