Module: OptionInitializer

Defined in:
lib/option_initializer.rb,
lib/option_initializer/version.rb

Defined Under Namespace

Modules: MethodCallShortcut Classes: ClassMatch

Constant Summary collapse

VERSION =
"1.5.1"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/option_initializer.rb', line 120

def self.included base
  unless base.constants.map(&:to_sym).include?(:OptionInitializing)
    base.const_set :OptionInitializing, oi = OptionInitializingTemplate.dup
    oi.class_eval do
      const_set :VALIDATORS, {}
      const_set :ARG_VALIDATORS, {}
    end
  end

  base.class_eval do
    class << self
      [:option_initializer, :option_initializer!, :option_validator].each do |m|
        undef_method(m) if method_defined?(m)
      end
    end

    def base.option_validator sym = nil, &block
      raise ArgumentError, "block must be given" unless block
      a = sym ? 1 : 2
      raise ArgumentError, "invalid arity (expected: #{a})" unless block.arity == a
      oi = self.const_get(:OptionInitializing)
      oi.const_get(:VALIDATORS)[sym] = block
    end

    def base.option_initializer *syms
      oi = self.const_get(:OptionInitializing)

      pairs = syms.inject([]) { |arr, sym|
        case sym
        when Symbol, String
          arr << [sym.to_sym, 1]
        when Hash
          arr.concat sym.map { |k, v|
            case v
            when Fixnum
              raise ArgumentError, "invalid number of arguments specified for #{k}" if v <= 0
            when Range
              raise ArgumentError, "invalid number of arguments specified for #{k}" if v.begin < 0
            when Set
              raise ArgumentError, "empty set of values specified for #{k}" if v.length == 0
            when Array
              unless v.all? { |e| [Class, Set, ClassMatch].any? { |kl| e.is_a?(kl) } }
                raise ArgumentError, "invalid option definition: `#{v}'"
              end
            when Class, :*, :&
              # noop
            when ClassMatch
              # noop
            else
              raise ArgumentError, "invalid option definition: `#{v}'"
            end
            [k.to_sym, v]
          }
        else
          raise ArgumentError, "invalid option definition"
        end
      }

      # Setup validators
      vals = oi.const_get(:ARG_VALIDATORS)
      pairs.each do |pair|
        sym, nargs = pair
        case nargs
        when :&
          vals[sym] = proc { |v|
            if !v.is_a?(Proc)
              raise TypeError, "wrong argument type #{v.class} (expected Proc)"
            end
          }
        when 1
          # good to go
          vals.delete sym
        when :*
          vals[sym] = proc { |v|
            if !v.is_a?(Array)
              raise ArgumentError, "wrong number of arguments (1 for #{nargs})"
            end
          }
        when Fixnum
          vals[sym] = proc { |v|
            if !v.is_a?(Array)
              raise ArgumentError, "wrong number of arguments (1 for #{nargs})"
            elsif nargs != v.length
              raise ArgumentError, "wrong number of arguments (#{v.length} for #{nargs})"
            end
          }
        when Range
          vals[sym] = proc { |v|
            if !v.is_a?(Array)
              raise ArgumentError, "wrong number of arguments (1 for #{nargs})"
            elsif !nargs.include?(v.length)
              raise ArgumentError, "wrong number of arguments (#{v.length} for #{nargs})"
            end
          }
        when Set
          vals[sym] = proc { |v|
            if !nargs.include?(v)
              raise ArgumentError, "invalid option value: `#{v}' (expected one of #{nargs.to_a.inspect})"
            end
          }
        when Array
          vals[sym] = proc { |v|
            if !v.is_a?(Array)
              raise ArgumentError, "wrong number of arguments (1 for #{nargs.length})"
            elsif nargs.length != v.length
              raise ArgumentError, "wrong number of arguments (#{v.length} for #{nargs.length})"
            else
              v.zip(nargs).each do |ec|
                e, c = ec
                case c
                when Class
                  raise TypeError, "wrong argument type #{e.class} (expected #{c})" unless e.is_a?(c)
                when Set
                  unless c.include?(e)
                    raise ArgumentError, "invalid option value: `#{e}' (expected one of #{c.to_a.inspect})"
                  end
                when ClassMatch
                  unless c.match e
                    raise TypeError, "wrong argument type #{e.class} (expected #{c})"
                  end
                end
              end
            end
          }
        when Class
          vals[sym] = proc { |v|
            if !v.is_a?(nargs)
              raise TypeError, "wrong argument type #{v.class} (expected #{nargs})"
            end
          }
        when ClassMatch
          vals[sym] = proc { |v|
            unless nargs.match v
              raise TypeError, "wrong argument type #{v.class} (expected #{nargs})"
            end
          }
        end
      end

      # Class methods
      pairs.each do |pair|
        sym = pair.first

        self.class_eval do
          # define_singleton_method not available on 1.8
          singleton = class << self; self end
          singleton.send :undef_method, sym if singleton.method_defined?(sym)
          singleton.send :define_method, sym do |*v, &b|
            oi.new(self, {}, false).send(sym, *v, &b)
          end
        end
      end

      # Instance methods
      oi.class_eval do
        pairs.each do |pair|
          sym, nargs = pair
          undef_method(sym) if method_defined?(sym)
          define_method(sym) do |*v, &b|
            if nargs == :&
              if v.empty?
                merge(sym => b)
              else
                raise ArgumentError, "wrong number of arguments (#{v.length} for 0)"
              end
            elsif b
              raise ArgumentError, "block not expected"
            else
              case nargs
              when 1, Class, Set, ClassMatch
                if v.length == 1
                  merge(sym => v.first)
                else
                  raise ArgumentError, "wrong number of arguments (#{v.length} for 1)"
                end
              else
                merge(sym => v)
              end
            end
          end
        end
      end
    end

    def base.option_initializer! *syms
      option_initializer(*syms)
      oi = self.const_get(:OptionInitializing)
      oi.class_eval do
        include OptionInitializer::MethodCallShortcut
      end
    end
  end
end

Instance Method Details

#validate_options(options) ⇒ Object

Raises:

  • (TypeError)


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/option_initializer.rb', line 103

def validate_options options
  raise TypeError,
    "wrong argument type #{options.class} (expected Hash)" unless
      options.is_a?(Hash)
  return if options.respond_to?(:option_validated?) && options.option_validated?
  avals, vals = [:ARG_VALIDATORS, :VALIDATORS].map { |s|
    self.class.const_get(:OptionInitializing).const_get(s)
  }
  options.each do |k, v|
    avals[k]  && avals[k].call(v)
    vals[k]   && vals[k].call(v)
    vals[nil] && vals[nil].call(k, v)
  end
  options
end