option_list 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OWQxY2I0ZTM2NjM1OTBiZDU2MWNmZDk4MjcyMDQ1ZGUzYWQzNDcyMA==
4
+ NDlkMWJhNDU5YmJmODU0N2IyMmEyZjNiYjcxZTc4MTI4NmJkY2RlMA==
5
5
  data.tar.gz: !binary |-
6
- NDUyZWQxNmQ5MzMwZTkzZThhZTFjNzhjMTNmN2Y4ZmY1NDU2N2RmNQ==
6
+ MTZiNTIxY2M0MzcyM2Q4NGNiN2E5NDcyNDY2ZWVjYjI3MjM3NDE0Zg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZGNhMjA2NjExMzNjNDAzNzdjNmZmODJkODg1NmQwZDYzOTg2MTkxNTFiMDUx
10
- MmU1NGFkMTMzZGE1MjI1MzU3MWE2M2U3MzEwMDEwYWYyNDI0YmIzYjk4MGQx
11
- NDM3NjEwNTEwYzkxY2YxNjY5MWJmZjc0NDY3NmU3MmMyZmE4NGU=
9
+ ZmUzZDYyM2MyYTBjYzVhMDQ4MjFkNDZjOWE4MmFlN2ZmMTQyNmU0MjQ3MWJm
10
+ YWE1ZjE0N2YyZmNmZWRhNGNlNTJmMmIxYjBlODAyZWQ2NmQwMjUzZDE4Yjk2
11
+ YjJlNWY4M2JjY2EyMTcwMmFiZTA1MDY2MTkxNzZjMTZkMTVmNjI=
12
12
  data.tar.gz: !binary |-
13
- ZDViOTVkN2U5NjMyZDNlYjliZDdlODk2NTM2MzFiZTJmMDExOThjNzhhOTMw
14
- MTM1Yjk5ZTEwNjBiYTE2NmQzNzRkYWI4OWIyM2JhNGIwMGY2NzRiNGE2Zjli
15
- MGJmMjMyYjliNDAzNmVmYmY2MGM1MzBmMzg1ODE1NGEwMDgzZTM=
13
+ Yzk2OTZjNDBhYWMzNDFhNzFlZWQ5OTAxOTUwNzcxZDFhZDI3NjhiMjIzYjFh
14
+ MGIxZmFjY2M3OGNlYjdmMGYzMmMxZDRjZjIzYjFmOWU2YTUyYzYwYWI1MDI4
15
+ YTllOGEzYTI5ZWQzZDM0YjIyYWVlZDE1ZTJmOTBhMGFkZGYzZWU=
data/README ADDED
@@ -0,0 +1,3 @@
1
+ This project contains the Ruby OptionList gem. A gem used to facilitate the
2
+ passing of complex parameter options with validation and checking and
3
+ minimal effort on the part of the caller and the callee.
@@ -4,40 +4,12 @@ require 'set'
4
4
  #This file contains the code for the \OptionList class that implements smart
5
5
  #and easy options for functions. It has the virtue of defining function
6
6
  #options clearly in a single place instead of spread out in complex code.
7
- #A classic but unclear sequence in many functions is the use of boolean values
8
- #to control or switch on or off some option. For example consider the well
9
- #known readline function:
10
- # cmd = readline(">", false)
11
- #See the boolean at the end? It says "false". What is "false"? Who knows? You
12
- #need to dig deeper or guess or just cut and paste without understanding. What
13
- #if, instead, the code looked like:
14
- # cmd = readline(">", :nohistory)
15
- #The boolean has been replaced by something a lot clearer. The problem with
16
- #such clarity is that it involves a lot more effort than the lazy boolean. The
17
- #\OptionList class aims to solve that issue by making useful, flexible options
18
- #just about as easy to use as the lazy way.
19
- #==== Warning: This code is most opinionated! ;-)
20
- #The philosophy and opinion expressed through this code is that errors should
21
- #be detected as close to the point of error as possible and that an error is
22
- #preferable to running with potentially incorrect results. As a result, it
23
- #will be noted that the programmer does not hesitate to use the fAE, a wrapper
24
- #of the "fail" keyword, liberally throughout the code to achieve this end.
25
- #Complimentary to this is the idea that the library (gem) writer should do as
26
- #much of the "heavy lifting" as possible, with clear, detailed documentation so
27
- #that the library (gem) user does not have to work hard or be lost or confused.
28
- #Only time will tell to what extent these lofty goals have been achieved.
29
- #Hopefully, with good feedback, this and other code libraries will improve.
30
- #=== Version 1.0.1
31
- #This version represents a major overhaul of the \OptionList class. In
32
- #particular, the selected option data and the dynamic methods used to access
33
- #that data are no longer contained in the option list object but instead in
34
- #a singleton subclass of Hash that is returned by the select method. There
35
- #are several advantages to this, but the main one is that since \OptionList
36
- #objects now only contain specification and default values, they are much more
37
- #thread safe than before. While there is nothing multi-threaded about the
38
- #\OptionList class itself, it is reasonable to assume that a function option
39
- #handler could very easily end up embedded in such an environment. In fact, my
40
- #first major test of the class ran into this exact issue.
7
+ #==== User's Guide
8
+ #Most of the documentation for this gem has been moved to
9
+ #{Option List User's Guide}[http://teuthida-technologies.com/guides/OL_UG_Version_1_1_1.pdf]
10
+ #====Version 1.1.1
11
+ #Embraced reek and git and created the user's guide to pair down rdoc mark up
12
+ #to be less obtrusive to the code.
41
13
  #=== Version 1.1.0
42
14
  #Added a default value of false to signify that no default exists for a
43
15
  #mandatory parameter.
@@ -46,7 +18,7 @@ class OptionList
46
18
 
47
19
  #The option list code version.
48
20
  def self.version
49
- '1.1.0'
21
+ '1.1.1'
50
22
  end
51
23
 
52
24
  #The option list code version. This is a redirect to the class method.
@@ -54,29 +26,7 @@ class OptionList
54
26
  self.class.version
55
27
  end
56
28
 
57
- #Create an option list from an array of option specifications. These
58
- #specifications consist of a number of array specifications and hash
59
- #specifications. These are described below:
60
- #==== Array Specification
61
- #Array specifications are used in cases where an option category may be one
62
- #of a fixed number of distinct symbol values or optionally nil to represent
63
- #no selection.
64
- #In this form of specification, the first element of the array is the
65
- #category of the option, and the second and following entries are the
66
- #allowed values for that category. The second element has a special role. It
67
- #is the default value for the category. This value may be one of:
68
- #* A symbol - The default symbolic value for this option.
69
- #* nil - The default value is nil and this setting is optional.
70
- #* false - There is no default value and this setting is mandatory.
71
- #The symbols used in each category must be unique across
72
- #all of the categories in the option list.
73
- #==== Hash Specification
74
- #Hash specifications are used to specify options that do not have a fixed set
75
- #of possible values. In this form of specification, the hash key symbol is
76
- #the category and the hash value is the default value for the category. It
77
- #may be nil or any other type or value. Allowed values are not listed.
78
- #==== Example:
79
- # @opt_list = OptionList.new([:history, :history, :nohistory], {:page_len => 42})
29
+ #Create an option list from an array of option specifications.
80
30
  #==== Parameters:
81
31
  #* option_specs - The comma separated option specifications, made into an
82
32
  # array by the splat operator.
@@ -84,19 +34,10 @@ class OptionList
84
34
  # have been made. This allows for custom validations to be applied at that
85
35
  # point. This block should accept one argument, a reference to the option
86
36
  # list value object that is calling it for validation.
87
- #==== Dynamic Methods:
88
- #As option specifications are added, new methods are created in the value
89
- #object to allow for easy access to the option information. If the above
90
- #example were processed, the following methods would be added to the value
91
- #returned by the select method:
92
- #* history - would return the value of the :history category.
93
- #* history? - would return true if the :history option where active.
94
- #* nohistory? - would return true if the :nohistory option were active.
95
- #* page_len - would return the value of the :page_len category.
96
37
  #==== Exceptions:
97
38
  #* ArgumentError for a number of invalid argument conditions.
98
39
  def initialize(*option_specs, &select_block)
99
- fAE "Missing option specifications." if option_specs.empty?
40
+ error "Missing option specifications." if option_specs.empty?
100
41
  @mandatory = Array.new
101
42
  @categories = Hash.new
102
43
  @default = Hash.new
@@ -107,7 +48,7 @@ class OptionList
107
48
  elsif spec.is_a?(Array)
108
49
  array_spec(spec)
109
50
  else
110
- fAE "Found #{spec.class} instead of Hash or Array."
51
+ error "Found #{spec.class} instead of Hash or Array."
111
52
  end
112
53
  end
113
54
 
@@ -115,47 +56,7 @@ class OptionList
115
56
  end
116
57
 
117
58
  #From the possible options, select the actual, in force, options and return
118
- #an access object for use in the code. These options may take several forms:
119
- #=== Types of option data:
120
- #* A symbol - For categories with a specified symbol list, simply list one
121
- # of the allowed symbols.
122
- #* A hash - For any type of category, a hash may be used in the the form
123
- # {category => value}. Note that for categories with a specified symbol list
124
- # the value must be a symbol in the list.
125
- #* Mixed - Symbols and hashes can be mixed in an array (see below on how this
126
- # is done) Note: Option order does not matter.
127
- #Some examples:
128
- # o = foo(:sedan, :turbo)
129
- # o = foo({:style=>:sedan})
130
- # o = foo(page_len: 60)
131
- # o = foo(:sedan, :turbo, page_len: 60)
132
- # o = foo(style: :sedan, page_len: 60)
133
- #=== Passing in option data:
134
- #The caller of this method may pass these options in a number of ways:
135
- #==== Array (via a splat)
136
- #Given the example shown in the new method, consider:
137
- # def test(count, *opt)
138
- # @opt_list.select(opt)
139
- # #etc, etc, etc...
140
- # end
141
- #With the caller now looking like:
142
- # test(43, :history)
143
- #==== Array (explicit)
144
- #An alternative strategy, where the use of splat is not desired, is to simply
145
- #have an array argument, as below:
146
- # def test(*arg1, opt)
147
- # @opt_list.select(opt)
148
- # #etc, etc, etc...
149
- # end
150
- #With the caller now looking like:
151
- # test(34, 53, 76, 'hike!', [:history])
152
- #==== Hash
153
- #Options may also be passed via a hash.
154
- # test(34, 53, 76, 'hike!', {:history=>:history, :page_len=>55})
155
- # test(34, 53, 76, 'hike!', history: :history, page_len: 55)
156
- #==== Symbol
157
- #If only a single option is required, it can be passed simply as:
158
- # test(34, 53, 76, 'hike!', :history)
59
+ #an access object for use in the code.
159
60
  #==== Parameters:
160
61
  #* selections - An array of the options passed into the client function, usually
161
62
  # with the splat operator. Note that if select is called with no arguments,
@@ -168,30 +69,36 @@ class OptionList
168
69
  #After processing the selections, the selection validation block is called
169
70
  #if one was defined for the constructor.
170
71
  def select(selections=[])
171
- selected = @default.clone
172
72
  selections = [selections] unless selections.is_a?(Array)
173
- dup = Set.new
73
+ selected = process_selections(selections)
74
+
75
+ @mandatory.each do |cat|
76
+ error "Missing mandatory setting #{cat}" unless selected[cat]
77
+ end
78
+
79
+ @select_block.call(selected) if @select_block
80
+ selected
81
+ end
82
+
83
+ private #Private stuff follows.
174
84
 
85
+ #Process a list of option selections.
86
+ def process_selections(selections)
87
+ selected, dup = @default.clone, Set.new
88
+
175
89
  selections.each do |opt|
176
90
  if opt.is_a?(Symbol)
177
91
  symbolic_selection(opt, selected, dup)
178
92
  elsif opt.is_a?(Hash)
179
93
  hash_selections(opt, selected, dup)
180
94
  else
181
- fAE "Found #{opt.class} instead of Hash or Symbol."
95
+ error "Found #{opt.class} instead of Hash or Symbol."
182
96
  end
183
97
  end
184
98
 
185
- @mandatory.each do |cat|
186
- fAE "Missing mandatory setting #{cat}" unless selected[cat]
187
- end
188
-
189
- @select_block.call(selected) unless @select_block.nil?
190
99
  selected
191
100
  end
192
101
 
193
- private #Private stuff follows.
194
-
195
102
  #Return a internal category marker constant for value entries.
196
103
  def value_entry
197
104
  'A value entry.'
@@ -200,58 +107,66 @@ class OptionList
200
107
  #Process an array spec that lists all the valid values for an option. See
201
108
  #the new method for more information on these specs.
202
109
  def array_spec(spec)
203
- cat = spec[0]
204
- spec = spec[1...spec.length]
205
-
206
- fAE "Found #{cat.class}, expected Symbol." unless cat.is_a?(Symbol)
207
- fAE "Duplicate category: #{cat}" if @default.has_key?(cat)
208
- fAE "Invalid number of entries for #{cat}." unless spec.length > 1
209
- @default.define_singleton_method(cat) { self[cat] }
210
-
211
- spec.each_with_index do |opt, index|
110
+ category, default, spec_len = spec[0], spec[1], spec.length
111
+ error "Invalid number of entries for #{category}." unless spec_len > 2
112
+ add_option_reader(category)
113
+ array_spec_default(category, default)
114
+ array_spec_tail_rest(category, spec[2...spec_len])
115
+ end
116
+
117
+ #Process the first element of the array spec tail.
118
+ def array_spec_default(category, opt)
119
+ opt && array_spec_single(category, opt)
120
+ @default[category] = opt
121
+ @mandatory << category if opt == false
122
+ end
123
+
124
+ #Process the rest of the array spec tail.
125
+ def array_spec_tail_rest(category, spec_tail_rest)
126
+ spec_tail_rest.each do |opt|
212
127
  if opt
213
- fAE "Found #{opt.class}, expected Symbol." unless opt.is_a?(Symbol)
214
- fAE "Duplicate option: #{opt}" if @categories.has_key?(opt)
215
-
216
- @categories[opt] = cat
217
- qry = (opt.to_s + '?').to_sym
218
- @default.define_singleton_method(qry) { self[cat] == opt }
219
- @default[cat] = opt if index == 0
220
- elsif index == 0
221
- @default[cat] = opt
222
- @mandatory << cat if opt == false
128
+ array_spec_single(category, opt)
223
129
  else
224
- fAE "The values nil/false are only allowed as the default option."
130
+ error "The values nil/false are only allowed as the default option."
225
131
  end
226
132
  end
227
133
  end
228
134
 
135
+ #Process a single array spec option
136
+ def array_spec_single(category, opt)
137
+ duplicate_entry_check(@categories, opt, 'option')
138
+ @categories[opt] = category
139
+ add_option_tester(category, opt)
140
+ end
141
+
229
142
  #Process a hash spec that lists only the default value for an option. See
230
143
  #the new method for more information on these specs.
231
144
  def hash_spec(spec)
232
- fAE "Hash contains no specs." unless spec.length > 0
145
+ error "Hash contains no specs." unless spec.length > 0
233
146
 
234
- spec.each do |cat, value|
235
- fAE "Found #{cat.class}, expected Symbol." unless cat.is_a?(Symbol)
236
- fAE "Duplicate category: #{cat}" if @default.has_key?(cat)
237
-
238
- @default.define_singleton_method(cat) { self[cat] }
239
- @categories[cat] = value_entry
240
- @default[cat] = value
147
+ spec.each do |category, value|
148
+ add_option_reader(category)
149
+ set_default_option_value(category, value)
150
+ @mandatory << category if value == false
241
151
  end
242
152
  end
243
153
 
154
+ #Set the default value of a value entry.
155
+ def set_default_option_value(category, value)
156
+ @categories[category] = value_entry
157
+ @default[category] = value
158
+ end
159
+
244
160
  #Process a symbolic option selection.
245
161
  #==== Parameters:
246
162
  #* option - a symbol to process.
247
163
  #* selected - a hash of selected data.
248
164
  #* dup - a set of categories that have been set. Used to detect duplicates.
249
- def symbolic_selection(option, selected, dup)
250
- fAE "Unknown option: #{option}." unless @categories.has_key?(option)
251
- cat = @categories[option]
252
- fAE "Category #{cat} has multiple values." if dup.include?(cat)
253
- dup.add(cat)
254
- selected[cat] = option
165
+ def symbolic_selection(symbol_option, selected, dup)
166
+ missing_entry_check(@categories, symbol_option, 'option')
167
+ category = @categories[symbol_option]
168
+ hash_option_dup_check(category, dup)
169
+ selected[category] = symbol_option
255
170
  end
256
171
 
257
172
  #Process a hash of option selection values.
@@ -259,23 +174,54 @@ class OptionList
259
174
  #* options - a hash of options to process.
260
175
  #* selected - a hash of selected data.
261
176
  #* dup - a set of categories that have been set. Used to detect duplicates.
262
- def hash_selections(options, selected, dup)
263
- options.each do |cat, value|
264
- fAE "Not a category: #{cat}." unless @default.has_key?(cat)
265
- fAE "Category #{cat} has multiple values." if dup.include?(cat)
266
-
267
- unless (@categories[cat] == value_entry) || value.nil?
268
- fAE "Found #{opt.class}, expected Symbol." unless value.is_a?(Symbol)
269
- fAE "Invalid option: #{value}." unless @categories[value] == cat
270
- end
271
-
272
- dup.add(cat)
273
- selected[cat] = value
177
+ def hash_selections(hash_options, selected, dup)
178
+ hash_options.each do |category, value|
179
+ missing_entry_check(@default, category, 'category')
180
+
181
+ hash_option_value_check(category, value)
182
+ hash_option_dup_check(category, dup)
183
+ selected[category] = value
184
+ end
185
+ end
186
+
187
+ #Validate a hash option value.
188
+ def hash_option_value_check(value_category, value)
189
+ if (@categories[value_category] != value_entry) && value
190
+ error "Found #{value.class}, expected Symbol." unless value.is_a?(Symbol)
191
+ error "Invalid option: #{value}." unless @categories[value] == value_category
274
192
  end
275
193
  end
194
+
195
+ #Add to set with no duplicates allowed.
196
+ def hash_option_dup_check(category, dup)
197
+ error "Category #{category} has multiple values." unless dup.add?(category)
198
+ end
199
+
200
+ #Add query method for the selected category.
201
+ def add_option_reader(name)
202
+ duplicate_entry_check(@default, name, 'category')
203
+ @default.define_singleton_method(name) { self[name] }
204
+ end
205
+
206
+ #Add a query method (eg: has_stuff? ) for the selected option.
207
+ def add_option_tester(target, value)
208
+ qry = (value.to_s + '?').to_sym
209
+ @default.define_singleton_method(qry) { self[target] == value}
210
+ end
211
+
212
+ #Flag any duplicate entry errors.
213
+ def duplicate_entry_check(target, entry, detail)
214
+ error "Found #{entry.class}, expected Symbol." unless entry.is_a?(Symbol)
215
+ error "Duplicate #{detail}: #{entry}" if target.has_key?(entry)
216
+ end
217
+
218
+ #Flag any missing entry errors.
219
+ def missing_entry_check(target, entry, detail)
220
+ error "Unknown #{detail}: #{entry}" unless target.has_key?(entry)
221
+ end
276
222
 
277
223
  #Fail with an argument error.
278
- def fAE(msg)
279
- fail(ArgumentError, msg, caller)
224
+ def error(messsage)
225
+ fail(ArgumentError, messsage, caller)
280
226
  end
281
227
  end
@@ -1,6 +1,6 @@
1
1
  === The MIT License (MIT).
2
2
 
3
- Copyright (c) 2013 Peter Camilleri
3
+ Copyright (c) 2013, 2014 Peter Camilleri
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -5,7 +5,7 @@ require 'rdoc/task'
5
5
  RDoc::Task.new do |rdoc|
6
6
  rdoc.rdoc_dir = "rdoc"
7
7
  #rdoc.main = "option_list.rb"
8
- rdoc.rdoc_files = ['lib/option_list.rb', 'tests/option_list_test.rb', 'license.txt']
8
+ rdoc.rdoc_files = ['lib/option_list.rb', 'license.txt']
9
9
  rdoc.options << '--visibility' << 'private'
10
10
  end
11
11
 
@@ -13,3 +13,7 @@ Rake::TestTask.new do |t|
13
13
  t.test_files = ['tests/option_list_test.rb']
14
14
  t.verbose = false
15
15
  end
16
+
17
+ task :reek do |t|
18
+ `reek lib\\*.rb > reek.txt`
19
+ end
@@ -0,0 +1 @@
1
+ lib/option_list.rb -- 0 warnings
@@ -211,6 +211,12 @@ class OptionListTester < MiniTest::Unit::TestCase
211
211
  assert_equal(o.history, :nohistory)
212
212
 
213
213
  assert_raises(ArgumentError) { ol2.select() }
214
+
215
+ ol3 = OptionList.new(page_len: false)
216
+ o = ol3.select(page_len: 42)
217
+ assert_equal(o.page_len, 42)
218
+
219
+ assert_raises(ArgumentError) { ol3.select() }
214
220
  end
215
221
 
216
222
  def test_that_it_does_not_munge_parms
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: option_list
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Camilleri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-20 00:00:00.000000000 Z
11
+ date: 2014-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: reek
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: minitest
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +52,21 @@ dependencies:
38
52
  - - ! '>='
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
41
- description: ! 'A unified handler for flexible function option parameters. '
55
+ - !ruby/object:Gem::Dependency
56
+ name: awesome_print
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: ! 'Flexible, Easy Function Parameters with Validation. '
42
70
  email: peter.c.camilleri@gmail.com
43
71
  executables: []
44
72
  extensions: []
@@ -49,6 +77,8 @@ files:
49
77
  - tests/option_list_test.rb
50
78
  - rakefile.rb
51
79
  - license.txt
80
+ - README
81
+ - reek.txt
52
82
  homepage: http://teuthida-technologies.com/
53
83
  licenses:
54
84
  - MIT
@@ -72,6 +102,6 @@ rubyforge_project:
72
102
  rubygems_version: 2.1.4
73
103
  signing_key:
74
104
  specification_version: 4
75
- summary: A unified handler for flexible function option parameters.
105
+ summary: Flexible, Easy Function Parameters with Validation.
76
106
  test_files:
77
107
  - tests/option_list_test.rb