switches.rb 0.11.0 → 0.12.1
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 +4 -4
- data/CHANGELOG +21 -11
- data/README.md +1 -0
- data/lib/Switches/VERSION.rb +1 -1
- data/lib/Switches.rb +11 -9
- data/spec/all.rb +45 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43cb8cbe7416baef56a0b3da428ad241fcd8b4622d926e6c64e0db0aa311d439
|
|
4
|
+
data.tar.gz: cbd74c3f9d883474c4e207ae1974f9f8271d2fb25865cf606c48794bc1498a61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06ab95f84758ab089a5ce0665ed43e5177b9fd638a0a663ade5ab52c94d587447ccd25fa890c26cd126a44b9da801bbb4aa0d1b647cc3d3423d6095cebdd64e8
|
|
7
|
+
data.tar.gz: bdc919b1bb6d903752337f606c91cd8cde56df35185c5b3b22a54aa5e7ef69bc30ac4553a03de60ff65773f4460bc905e6f5324c8ddf01d0deec0a8a7b55a29c
|
data/CHANGELOG
CHANGED
|
@@ -2,23 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## 20260816
|
|
4
4
|
|
|
5
|
-
0.
|
|
5
|
+
0.12.1: Move the reasoning out of the CHANGELOG and the header into a ROADMAP.
|
|
6
|
+
|
|
7
|
+
1. + ROADMAP.md: the design philosophy, a section per shipped version carrying why it went the way it did, the open questions, and the settled items. Not listed in the gemspec, so it does not ship. Follows namo's ROADMAP.md.
|
|
8
|
+
2. - lib/Switches.rb: the # Todo: and # Ideas: header sections, now in ROADMAP.md, finishing what 0.10.2 began.
|
|
9
|
+
3. ~ CHANGELOG: the 0.10.5 and 0.11.0 entries carried reasoning, measurement and outstanding work in prose between their numbered items; that is now in ROADMAP.md, and the entries record what changed.
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
2. ~ lib/Switches.rb: a declared switch takes precedence over an OptionParser method of the same name, + Switches#declared_switch?. Making the forwarding work otherwise shadows any switch sharing a name with the parser, and OptionParser has version, help, release, load, order, top, base, parse, warn, abort and banner among its 61, several of which are switches a program would reasonably want. The rule is that the program's own declarations beat the wrapper's interface, which also leaves any existing program's switches reading as they did.
|
|
9
|
-
3. ~ lib/Switches.rb: + Switches::NEVER_FORWARDED, the names Ruby may call implicitly, which are excluded from the forwarded set. The set has always been built by subtraction — everything the parser responds to, less everything Switches defines — which admits any such name Switches does not happen to define. OptionParser#to_a is `summarize("#{banner}".split(/^/))`, so without the exclusion Array(switches) and [*switches] answer with the parser's help. That was latent for as long as the branch was dead and became reachable the moment it was not, so it is part of this fix rather than a consequence of it. to_h was never at risk, but only because Switches happens to define it; the constant makes the exclusion deliberate rather than incidental.
|
|
10
|
-
4. ~ lib/Switches.rb: + Switches#option_parser_methods, memoised, since the difference was recomputed upon every settings read, and + Switches#option_parser_method?.
|
|
11
|
-
5. + spec/all.rb: forwarding of a writer, of a method taking an argument, and of a reader, each checked through the parser's own help output rather than by reading back what was written; that a declared switch wins over the OptionParser method of the same name, read and written; that a name which is neither still reaches the settings; and that #to_a, Array() and the splat are answered by the switches rather than the parser.
|
|
12
|
-
6. ~ README.md: the Notes entry claiming a Switches instance can't define a method whose name matches an OptionParser method now describes what happens, which is the reverse: the switch wins.
|
|
11
|
+
0.12.0: Refuse a switch whose accessor is a method the instance already has, rather than losing it.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
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.
|
|
14
|
+
2. + lib/Switches.rb: SwitchNameCollision < ArgumentError.
|
|
15
|
+
3. ~ README.md: + a Notes entry for the refusal, and for the booleans and OptionParser names it does not cover.
|
|
16
|
+
4. + spec/all.rb: the refusal through #set and #perform, the message, that nothing reaches the parser first, and the two legal cases.
|
|
17
|
+
5. + ROADMAP.md, and - the # Todo: and # Ideas: sections from the lib/Switches.rb header, which is where they were. Not listed in the gemspec, so it does not ship.
|
|
18
|
+
|
|
19
|
+
0.11.0: Forward to OptionParser, which #method_missing has never actually done.
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
1. ~ lib/Switches.rb: #method_missing compared method_name.to_s against @op.methods, an array of Symbols, so the forwarding branch was never true; now compared in symbols.
|
|
22
|
+
2. ~ lib/Switches.rb: + Switches#declared_switch?, giving a declared switch precedence over the OptionParser method of the same name.
|
|
23
|
+
3. + lib/Switches.rb: Switches::NEVER_FORWARDED, the names Ruby may call implicitly, excluded from the forwarded set, OptionParser#to_a otherwise answering Array() with the help.
|
|
24
|
+
4. ~ lib/Switches.rb: + Switches#option_parser_methods, memoised, and + Switches#option_parser_method?.
|
|
25
|
+
5. + spec/all.rb: the forwarding, the precedence rule, the fall-through, and #to_a.
|
|
26
|
+
6. ~ README.md: the Notes entry describing forwarding, which had it backwards.
|
|
17
27
|
|
|
18
28
|
0.10.5: Specify and document that a multi-word switch may be supplied hyphenated, and name that form when it is missing.
|
|
19
29
|
|
|
20
|
-
1. + spec/all.rb: multi-word switches
|
|
21
|
-
2. ~ lib/Switches.rb: the missing-switch message names the hyphenated form, so a switch declared :access_token is reported as --access-token, in place of the --access_token built from the symbol verbatim.
|
|
30
|
+
1. + spec/all.rb: multi-word switches in both their underscored and their hyphenated form, for a boolean, an optional argument (#set), a required argument (#set!) and a required switch (#required); that either form counts as supplied; and that the setting is named for the symbol declared. The behaviour is OptionParser's, a long switch being filed under a lookup key with its underscores turned to hyphens.
|
|
31
|
+
2. ~ lib/Switches.rb: the missing-switch message names the hyphenated form, so a switch declared :access_token is reported as --access-token, in place of the --access_token built from the symbol verbatim.
|
|
22
32
|
3. ~ lib/Switches.rb: + Switches#switch_string, which builds the switch string for an attribute, in place of the identical construction #on_args and #check_required_switches each carried.
|
|
23
33
|
4. ~ README.md: + a multi-word switch to the Usage section, and + a note recording where the behaviour comes from, that the setting is named for the symbol declared, and that declaring the attribute hyphenated buys nothing and costs the accessor.
|
|
24
34
|
|
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.
|
data/lib/Switches/VERSION.rb
CHANGED
data/lib/Switches.rb
CHANGED
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
# Description: Switches provides for a nice wrapper to OptionParser to also act as a store for switches supplied.
|
|
5
5
|
|
|
6
|
-
# Todo:
|
|
7
|
-
# 1. Clean up #set. Done as of 0.4.0.
|
|
8
|
-
# 2. Reinstitute some specs. Done as of 0.9.8.
|
|
9
|
-
|
|
10
|
-
# Ideas:
|
|
11
|
-
# 1. Use ! for options with required switches? Done as of 0.6.0. (Changed to being for required arguments in 0.9.0 however.)
|
|
12
|
-
# 2. Do away with optional arguments entirely. Since when does anyone want to specify a non-boolean switch and then supply no arguments anyway?... OK, maybe sometimes, but this is pretty obscure IMO. OK, bad idea. These can be used as action oriented sub-commands.
|
|
13
|
-
# 3. Allow for any one of the switches OpenStruct methods to assign values for any of the other associated methods, so as it is more than a read once switch and can be used for storage through out the application; although this might be stepping on Attributes.rb's toes?...
|
|
14
|
-
|
|
15
6
|
# Dependencies:
|
|
16
7
|
# 1. Standard Ruby Library.
|
|
17
8
|
# 2. ostruct, which ceased to be a default gem as of Ruby 4.0 and so is declared in the gemspec.
|
|
@@ -150,6 +141,7 @@ module CastingInterfaceMethods
|
|
|
150
141
|
end
|
|
151
142
|
|
|
152
143
|
class RequiredSwitchMissing < RuntimeError; end
|
|
144
|
+
class SwitchNameCollision < ArgumentError; end
|
|
153
145
|
|
|
154
146
|
class Switches
|
|
155
147
|
|
|
@@ -257,6 +249,7 @@ class Switches
|
|
|
257
249
|
private
|
|
258
250
|
|
|
259
251
|
def do_set(requires_argument, *attrs, &block)
|
|
252
|
+
check_switch_names(*attrs)
|
|
260
253
|
set_if_required_switch(*attrs)
|
|
261
254
|
options = attrs.extract_options!
|
|
262
255
|
@all_switches = @all_switches + attrs.collect{|a| a.to_s}
|
|
@@ -268,6 +261,7 @@ class Switches
|
|
|
268
261
|
end
|
|
269
262
|
|
|
270
263
|
def do_action(requires_argument, *attrs, &block)
|
|
264
|
+
check_switch_names(*attrs)
|
|
271
265
|
attrs.each do |attr|
|
|
272
266
|
@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
267
|
end
|
|
@@ -299,6 +293,14 @@ class Switches
|
|
|
299
293
|
@all_switches.include?(method_name.to_s.delete_suffix('='))
|
|
300
294
|
end
|
|
301
295
|
|
|
296
|
+
def check_switch_names(*attrs)
|
|
297
|
+
attrs.each do |attr|
|
|
298
|
+
next unless attr.is_a?(Symbol) || attr.is_a?(String)
|
|
299
|
+
next unless respond_to?(attr.to_s)
|
|
300
|
+
raise SwitchNameCollision, "switch, #{switch_string(attr)}, is already #{method(attr.to_s).owner}##{attr}, so it would never reach the settings"
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
302
304
|
def switch_string(attr)
|
|
303
305
|
attr = attr.to_s
|
|
304
306
|
"-#{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
|