jinx 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/.gitignore +14 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +27 -0
  6. data/History.md +6 -0
  7. data/LEGAL +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +44 -0
  10. data/Rakefile +41 -0
  11. data/examples/family/README.md +10 -0
  12. data/examples/family/ext/build.xml +35 -0
  13. data/examples/family/ext/src/family/Address.java +68 -0
  14. data/examples/family/ext/src/family/Child.java +24 -0
  15. data/examples/family/ext/src/family/DomainObject.java +26 -0
  16. data/examples/family/ext/src/family/Household.java +36 -0
  17. data/examples/family/ext/src/family/Parent.java +48 -0
  18. data/examples/family/ext/src/family/Person.java +42 -0
  19. data/examples/family/lib/family.rb +15 -0
  20. data/examples/family/lib/family/address.rb +6 -0
  21. data/examples/family/lib/family/domain_object.rb +6 -0
  22. data/examples/family/lib/family/household.rb +6 -0
  23. data/examples/family/lib/family/parent.rb +16 -0
  24. data/examples/family/lib/family/person.rb +6 -0
  25. data/examples/model/README.md +25 -0
  26. data/examples/model/ext/build.xml +35 -0
  27. data/examples/model/ext/src/domain/Child.java +192 -0
  28. data/examples/model/ext/src/domain/Dependent.java +29 -0
  29. data/examples/model/ext/src/domain/DomainObject.java +26 -0
  30. data/examples/model/ext/src/domain/Independent.java +83 -0
  31. data/examples/model/ext/src/domain/Parent.java +129 -0
  32. data/examples/model/ext/src/domain/Person.java +14 -0
  33. data/examples/model/lib/model.rb +13 -0
  34. data/examples/model/lib/model/child.rb +13 -0
  35. data/examples/model/lib/model/domain_object.rb +6 -0
  36. data/examples/model/lib/model/independent.rb +11 -0
  37. data/examples/model/lib/model/parent.rb +17 -0
  38. data/jinx.gemspec +22 -0
  39. data/lib/jinx.rb +3 -0
  40. data/lib/jinx/active_support/README.txt +2 -0
  41. data/lib/jinx/active_support/core_ext/string.rb +7 -0
  42. data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
  43. data/lib/jinx/active_support/inflections.rb +55 -0
  44. data/lib/jinx/active_support/inflector.rb +398 -0
  45. data/lib/jinx/cli/application.rb +36 -0
  46. data/lib/jinx/cli/command.rb +214 -0
  47. data/lib/jinx/helpers/array.rb +108 -0
  48. data/lib/jinx/helpers/boolean.rb +42 -0
  49. data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
  50. data/lib/jinx/helpers/class.rb +149 -0
  51. data/lib/jinx/helpers/collection.rb +33 -0
  52. data/lib/jinx/helpers/collections.rb +11 -0
  53. data/lib/jinx/helpers/collector.rb +20 -0
  54. data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
  55. data/lib/jinx/helpers/enumerable.rb +242 -0
  56. data/lib/jinx/helpers/enumerate.rb +35 -0
  57. data/lib/jinx/helpers/error.rb +15 -0
  58. data/lib/jinx/helpers/file_separator.rb +65 -0
  59. data/lib/jinx/helpers/filter.rb +52 -0
  60. data/lib/jinx/helpers/flattener.rb +38 -0
  61. data/lib/jinx/helpers/hash.rb +12 -0
  62. data/lib/jinx/helpers/hashable.rb +502 -0
  63. data/lib/jinx/helpers/inflector.rb +36 -0
  64. data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
  65. data/lib/jinx/helpers/lazy_hash.rb +44 -0
  66. data/lib/jinx/helpers/log.rb +106 -0
  67. data/lib/jinx/helpers/math.rb +12 -0
  68. data/lib/jinx/helpers/merge.rb +60 -0
  69. data/lib/jinx/helpers/module.rb +18 -0
  70. data/lib/jinx/helpers/multi_enumerator.rb +31 -0
  71. data/lib/jinx/helpers/options.rb +92 -0
  72. data/lib/jinx/helpers/os.rb +19 -0
  73. data/lib/jinx/helpers/partial_order.rb +37 -0
  74. data/lib/jinx/helpers/pretty_print.rb +207 -0
  75. data/lib/jinx/helpers/set.rb +8 -0
  76. data/lib/jinx/helpers/stopwatch.rb +76 -0
  77. data/lib/jinx/helpers/transformer.rb +24 -0
  78. data/lib/jinx/helpers/transitive_closure.rb +55 -0
  79. data/lib/jinx/helpers/uniquifier.rb +50 -0
  80. data/lib/jinx/helpers/validation.rb +33 -0
  81. data/lib/jinx/helpers/visitor.rb +370 -0
  82. data/lib/jinx/import/class_path_modifier.rb +77 -0
  83. data/lib/jinx/import/java.rb +337 -0
  84. data/lib/jinx/importer.rb +240 -0
  85. data/lib/jinx/metadata.rb +155 -0
  86. data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
  87. data/lib/jinx/metadata/dependency.rb +244 -0
  88. data/lib/jinx/metadata/id_alias.rb +23 -0
  89. data/lib/jinx/metadata/introspector.rb +179 -0
  90. data/lib/jinx/metadata/inverse.rb +170 -0
  91. data/lib/jinx/metadata/java_property.rb +169 -0
  92. data/lib/jinx/metadata/propertied.rb +500 -0
  93. data/lib/jinx/metadata/property.rb +401 -0
  94. data/lib/jinx/metadata/property_characteristics.rb +114 -0
  95. data/lib/jinx/resource.rb +862 -0
  96. data/lib/jinx/resource/copy_visitor.rb +36 -0
  97. data/lib/jinx/resource/inversible.rb +90 -0
  98. data/lib/jinx/resource/match_visitor.rb +180 -0
  99. data/lib/jinx/resource/matcher.rb +20 -0
  100. data/lib/jinx/resource/merge_visitor.rb +73 -0
  101. data/lib/jinx/resource/mergeable.rb +185 -0
  102. data/lib/jinx/resource/reference_enumerator.rb +49 -0
  103. data/lib/jinx/resource/reference_path_visitor.rb +38 -0
  104. data/lib/jinx/resource/reference_visitor.rb +55 -0
  105. data/lib/jinx/resource/unique.rb +35 -0
  106. data/lib/jinx/version.rb +3 -0
  107. data/spec/defaults_spec.rb +30 -0
  108. data/spec/definitions/model/alias/child.rb +5 -0
  109. data/spec/definitions/model/base/child.rb +5 -0
  110. data/spec/definitions/model/base/domain_object.rb +5 -0
  111. data/spec/definitions/model/base/independent.rb +5 -0
  112. data/spec/definitions/model/defaults/child.rb +5 -0
  113. data/spec/definitions/model/dependency/child.rb +5 -0
  114. data/spec/definitions/model/dependency/parent.rb +6 -0
  115. data/spec/definitions/model/inverse/child.rb +5 -0
  116. data/spec/definitions/model/inverse/independent.rb +5 -0
  117. data/spec/definitions/model/inverse/parent.rb +5 -0
  118. data/spec/definitions/model/mandatory/child.rb +6 -0
  119. data/spec/dependency_spec.rb +47 -0
  120. data/spec/family_spec.rb +64 -0
  121. data/spec/inverse_spec.rb +53 -0
  122. data/spec/mandatory_spec.rb +43 -0
  123. data/spec/metadata_spec.rb +68 -0
  124. data/spec/resource_spec.rb +30 -0
  125. data/spec/spec_helper.rb +3 -0
  126. data/spec/support/model.rb +19 -0
  127. data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
  128. data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
  129. data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
  130. data/test/fixtures/mixed/ext/build.xml +35 -0
  131. data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
  132. data/test/helper.rb +7 -0
  133. data/test/lib/jinx/command_test.rb +41 -0
  134. data/test/lib/jinx/helpers/boolean_test.rb +27 -0
  135. data/test/lib/jinx/helpers/class_test.rb +60 -0
  136. data/test/lib/jinx/helpers/collections_test.rb +402 -0
  137. data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
  138. data/test/lib/jinx/helpers/inflector_test.rb +11 -0
  139. data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
  140. data/test/lib/jinx/helpers/module_test.rb +24 -0
  141. data/test/lib/jinx/helpers/options_test.rb +66 -0
  142. data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
  143. data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
  144. data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
  145. data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
  146. data/test/lib/jinx/helpers/visitor_test.rb +288 -0
  147. data/test/lib/jinx/import/java_test.rb +78 -0
  148. data/test/lib/jinx/import/mixed_case_test.rb +16 -0
  149. metadata +272 -0
@@ -0,0 +1,36 @@
1
+ require 'jinx/active_support/inflector'
2
+
3
+ class String
4
+ # @param [Numeric] quantity the amount qualifier
5
+ # @return [String] this String qualified by a plural if the quantity is not 1
6
+ # @example
7
+ # "rose".quantify(3) #=> "roses"
8
+ # "rose".quantify(1 #=> "rose"
9
+ def quantify(quantity)
10
+ Jinx.fail(ArgumentError, "Missing quantity argument") if quantity.nil?
11
+ "#{quantity} #{quantity == 1 ? self : pluralize}"
12
+ end
13
+
14
+ # @return [String] this String with the first letter capitalized and other letters preserved
15
+ # @example
16
+ # "rosesAreRed".capitalize_first #=> "RosesAreRed"
17
+ def capitalize_first
18
+ sub(/(?:^)(.)/) { $1.upcase }
19
+ end
20
+
21
+ # @return [String] this String with a leading indefinite article
22
+ # @example
23
+ # "rose".indefinitize #=> "a rose"
24
+ # "eagle".indefinitize #=> "an eagle"
25
+ def indefinitize
26
+ article = self =~ /^[aeiou]/i ? 'an' : 'a'
27
+ "#{article} #{self}"
28
+ end
29
+
30
+ # @return [String] this String with the first letter decapitalized and other letters preserved
31
+ # @example
32
+ # "RosesAreRed".decapitalize #=> "rosesAreRed"
33
+ def decapitalize
34
+ sub(/(?:^)(.)/) { $1.downcase }
35
+ end
36
+ end
@@ -0,0 +1,43 @@
1
+ require 'jinx/helpers/hashable'
2
+
3
+ module Jinx
4
+ # The KeyTransformerHash class pipes the key access argument into a transformer block before
5
+ # accessing a base Hashable, e.g.:
6
+ # hash = KeyTransformerHash.new { |key| key % 2 }
7
+ # hash[1] = :a
8
+ # hash[3] #=> :a
9
+ class KeyTransformerHash
10
+ include Hashable
11
+
12
+ # Creates a KeyTransformerHash on the optional base hash and required key transformer block.
13
+ #
14
+ # @param [Hash, nil] base the hash to transform
15
+ # @yield [key] transforms the base key
16
+ # @yieldparam key the base key to transform
17
+ def initialize(base={}, &transformer)
18
+ @base = base
19
+ @xfm = transformer
20
+ end
21
+
22
+ # Returns the value at key after this KeyTransformerHash's transformer block is applied to the key,
23
+ # or nil if the base hash does not contain an association for the transformed key.
24
+ def [](key)
25
+ @base[@xfm.call(key)]
26
+ end
27
+
28
+ # Sets the value at key after this KeyTransformerHash's transformer block is applied, or nil
29
+ # if this hash does not contain an association for the transformed key.
30
+ def []=(key, value)
31
+ @base[@xfm.call(key)] = value
32
+ end
33
+
34
+ # Delegates to the base hash.
35
+ # Note that this breaks the standard Hash contract, since
36
+ # all? { |k, v| self[k] }
37
+ # is not necessarily true because the key is transformed on access.
38
+ # @see Accessor for a KeyTransformerHash variant that restores this contract
39
+ def each(&block)
40
+ @base.each(&block)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ require 'jinx/helpers/options'
2
+
3
+ module Jinx
4
+ # A Hash that creates a new entry on demand.
5
+ class LazyHash < Hash
6
+ # Creates a new Hash with the specified value factory proc.
7
+ # The factory proc has one argument, the key.
8
+ # If access by key fails, then a new association is created
9
+ # from the key to the result of calling the factory proc.
10
+ #
11
+ # Example:
12
+ # hash = LazyHash.new { |key| key.to_s }
13
+ # hash[1] = "1"
14
+ # hash[1] #=> "1"
15
+ # hash[2] #=> "2"
16
+ #
17
+ # If a block is not provided, then the default association value is nil, e.g.:
18
+ # hash = LazyHash.new
19
+ # hash.has_key?(1) #=> false
20
+ # hash[1] #=> nil
21
+ # hash.has_key?(1) #=> true
22
+ #
23
+ # A nil key always returns nil. There is no hash entry for nil, e.g.:
24
+ # hash = LazyHash.new { |key| key }
25
+ # hash[nil] #=> nil
26
+ # hash.has_key?(nil) #=> false
27
+ #
28
+ # If the :compact option is set, then an entry is not created
29
+ # if the value initializer result is nil or empty, e.g.:
30
+ # hash = LazyHash.new { |n| 10.div(n) unless n.zero? }
31
+ # hash[0] #=> nil
32
+ # hash.has_key?(0) #=> false
33
+ def initialize(options=nil)
34
+ reject_flag = Options.get(:compact, options)
35
+ # Make the hash with the factory block
36
+ super() do |hash, key|
37
+ if key then
38
+ value = yield key if block_given?
39
+ hash[key] = value unless reject_flag and value.nil_or_empty?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,106 @@
1
+ require 'logger'
2
+ require 'singleton'
3
+ require 'ftools'
4
+ require 'jinx/helpers/collections'
5
+ require 'jinx/helpers/options'
6
+
7
+ # @return [Jinx::MultilineLogger] the global logger
8
+ def logger
9
+ Jinx.logger
10
+ end
11
+
12
+ module Jinx
13
+ # @return (see Log#logger)
14
+ def self.logger
15
+ Log.instance.logger
16
+ end
17
+
18
+ # Extends the standard Logger to format multi-line messages on separate lines.
19
+ class MultilineLogger < ::Logger
20
+ # @see Logger#initialize
21
+ def initialize(*args)
22
+ super
23
+ end
24
+
25
+ # Rackify the logger with a write method, in conformance with
26
+ # the [Rack spec](http://rack.rubyforge.org/doc/SPEC.html).
27
+ alias :write :<<
28
+
29
+ private
30
+
31
+ # Writes msg to the log device. Each line in msg is formatted separately.
32
+ #
33
+ # @param (see Logger#format_message)
34
+ # @return (see Logger#format_message)
35
+ def format_message(severity, datetime, progname, msg)
36
+ if String === msg then
37
+ msg.inject('') { |s, line| s << super(severity, datetime, progname, line.chomp) }
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+
44
+ # Wraps a standard global Logger.
45
+ class Log
46
+ include Singleton
47
+
48
+ # Opens the log.
49
+ #
50
+ # @param [String, IO, nil] dev the log file or device (default STDOUT)
51
+ # @param [Hash, nil] opts the logger options
52
+ # @option opts [Integer] :shift_age the number of log files retained in the rotation
53
+ # @option opts [Integer] :shift_size the maximum size of each log file
54
+ # @option opts [Boolean] :debug whether to include debug messages in the log file
55
+ # @return [MultilineLogger] the global logger
56
+ def open(dev=nil, opts=nil)
57
+ raise RuntimeError.new("Log already open") if open?
58
+ if String === dev then File.makedirs(File.dirname(dev)) end
59
+ # default is 4-file rotation @ 16MB each
60
+ shift_age = Options.get(:shift_age, opts, 4)
61
+ shift_size = Options.get(:shift_size, opts, 16 * 1048576)
62
+ @logger = MultilineLogger.new(dev, shift_age, shift_size)
63
+ @logger.level = Options.get(:debug, opts) ? Logger::DEBUG : Logger::INFO
64
+ @logger.formatter = lambda do |severity, time, progname, msg|
65
+ FORMAT % [
66
+ progname || 'I',
67
+ DateTime.now.strftime("%d/%b/%Y %H:%M:%S"),
68
+ severity,
69
+ msg]
70
+ end
71
+ @dev = dev
72
+ @logger
73
+ end
74
+
75
+ # @return [Boolean] whether the logger is open
76
+ def open?
77
+ !!@logger
78
+ end
79
+
80
+ # Closes and releases the {#logger}.
81
+ def close
82
+ @logger.close
83
+ @logger = nil
84
+ end
85
+
86
+ # @return (see #open)
87
+ def logger
88
+ @logger ||= open
89
+ end
90
+
91
+ # @return [String, nil] the log file, or nil if the log was opened on an IO rather
92
+ # than a String
93
+ def file
94
+ @dev if String === @dev
95
+ end
96
+
97
+ private
98
+
99
+ # Stream-lined log format.
100
+ FORMAT = %{%s [%s] %5s %s\n}
101
+
102
+ def same_file?(f1, f2)
103
+ f1 == f2 or (String === f2 and String === f1 and File.expand_path(f1) == File.expand_path(f2))
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,12 @@
1
+ # Extends the Numeric class with max and min methods.
2
+ class Numeric
3
+ # Returns the minimum of this Numeric and the other Numerics.
4
+ def min(*others)
5
+ others.inject(self) { |min, other| other < min ? other : min }
6
+ end
7
+
8
+ # Returns the minimum of this Numeric and the other Numerics.
9
+ def max(*others)
10
+ others.inject(self) { |max, other| other > max ? other : max }
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ require 'jinx/helpers/options'
2
+ require 'jinx/helpers/collections'
3
+
4
+
5
+ class Hash
6
+ # Returns a new hash which merges the other hash with this hash.
7
+ #
8
+ # Supported options include the following:
9
+ # * :deep - merge values which match on the key.
10
+ # If the :deep option is set, and a key matches both this hash and the other hash
11
+ # on hash values, then the other hash's value is recursively merged into this Hash's
12
+ # value using the non-destructive {#merge} method with the deep option set.
13
+ # If a block is given to this method, then the block is passed to the value merge.
14
+ #
15
+ # @example
16
+ # {:a => [1], :b => [2]}.merge({:b => [3]}, :deep) #=> {:a => [1], :b => [2, 3]}
17
+ # {:a => {:b => [1]}}.merge({:a => {:b => [2]}, :c => 3}, :deep) #=> {:a => {:b => [1, 2]}, :c => 3}
18
+ def merge(other, options=nil, &block)
19
+ dup.merge!(other, options, &block)
20
+ end
21
+
22
+ alias :base__merge! :merge!
23
+ private :base__merge!
24
+
25
+ # Merges the other hash into this hash and returns this modified hash.
26
+ #
27
+ # @see #merge the options and block description
28
+ def merge!(other, options=nil, &block)
29
+ # use the standard Hash merge unless the :deep option is set
30
+ return base__merge!(other, &block) unless Options.get(:deep, options)
31
+ # merge the other entries:
32
+ # if the hash value is a hash, then call merge on that hash value.
33
+ # otherwise, if the hash value understands merge, then call that method.
34
+ # otherwise, if there is a block, then call the block.
35
+ # otherwise, set the the hash value to the other value.
36
+ base__merge!(other) do |key, oldval, newval|
37
+ if Hash === oldval then
38
+ oldval.merge(newval, options, &block)
39
+ elsif oldval.respond_to?(:merge)
40
+ oldval.merge(newval, &block)
41
+ elsif block_given? then
42
+ yield(key, oldval, newval)
43
+ else
44
+ newval
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ class Array
51
+ # Adds the elements in the other Enumerable which are not already included in this Array.
52
+ # Returns this modified Array.
53
+ def merge(other)
54
+ # incompatible merge argument is allowed but ignored
55
+ self unless Enumerable === other
56
+ # concatenate the members of other not in self
57
+ unique = other.to_a - self
58
+ concat(unique)
59
+ end
60
+ end
@@ -0,0 +1,18 @@
1
+ class Module
2
+ # Returns the class or module with the given name defined in this module.
3
+ # The name can qualified by parent modules, e.g. +MyApp::Person+.
4
+ # If name cannot be resolved as a Module, then this method returns nil.
5
+ #
6
+ # @param [String] the class name
7
+ # @return [Module, nil] the class or module defined in this module, or nil if none
8
+ def module_with_name(name)
9
+ name.split('::').inject(self) { |parent, part| parent.const_get(part) } rescue nil
10
+ end
11
+
12
+ # @example
13
+ # A::B.parent_module #=> A
14
+ # @return [Module] this module's definition context
15
+ def parent_module
16
+ Kernel.module_with_name(name.split('::')[0..-2].join('::'))
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ module Jinx
2
+ # A MultiEnumerator iterates over several Enumerators in sequence. Unlike Array#+, MultiEnumerator reflects changes to the
3
+ # underlying enumerators.
4
+ #
5
+ # @example
6
+ # a = [1, 2]
7
+ # b = [4, 5]
8
+ # ab = MultiEnumerator.new(a, b)
9
+ # ab.to_a #=> [1, 2, 4, 5]
10
+ # a << 3; b << 6; ab.to_a #=> [1, 2, 3, 4, 5, 6]
11
+ class MultiEnumerator
12
+ include Collection
13
+
14
+ # @return [<Enumerable>] the enumerated collections
15
+ attr_reader :components
16
+
17
+ # Initializes a new {MultiEnumerator} on the given components.
18
+ #
19
+ # @param [<Enumerable>] the component enumerators to compose
20
+ def initialize(*enums)
21
+ super()
22
+ @components = enums
23
+ @components.compact!
24
+ end
25
+
26
+ # Iterates over each of this MultiEnumerator's Enumerators in sequence.
27
+ def each
28
+ @components.each { |enum| enum.each { |item| yield item } }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,92 @@
1
+ require 'jinx/helpers/collections'
2
+
3
+ require 'jinx/helpers/validation'
4
+ require 'jinx/helpers/merge'
5
+
6
+ # Options is a utility class to support method options.
7
+ class Options
8
+ # Returns the value of option in options as follows:
9
+ # * If options is a hash which contains the option key, then this method returns
10
+ # the option value. A non-collection options[option] value is wrapped as a singleton
11
+ # collection to conform to a collection default type, as shown in the example below.
12
+ # * If options equals the option symbol, then this method returns +true+.
13
+ # * If options is an Array of symbols which includes the given option, then this method
14
+ # returns +true+.
15
+ # * Otherwise, this method returns the default.
16
+ #
17
+ # If default is nil and a block is given to this method, then the default is determined
18
+ # by calling the block with no arguments. The block can also be used to raise a missing
19
+ # option exception, e.g.:
20
+ # Options.get(:userid, options) { Jinx.fail(RuntimeError, "Missing required option: userid") }
21
+ #
22
+ # @example
23
+ # Options.get(:create, {:create => true}) #=> true
24
+ # Options.get(:create, :create) #=> true
25
+ # Options.get(:create, [:create, :compress]) #=> true
26
+ # Options.get(:create, nil) #=> nil
27
+ # Options.get(:create, nil, :false) #=> false
28
+ # Options.get(:create, nil, :true) #=> true
29
+ # Options.get(:values, nil, []) #=> []
30
+ # Options.get(:values, {:values => :a}, []) #=> [:a]
31
+ # Options.get(:values, [:create, {:values => :a}], []) #=> [:a]
32
+ def self.get(option, options, default=nil, &block)
33
+ return default(default, &block) if options.nil?
34
+ case options
35
+ when Hash then
36
+ value = options[option]
37
+ if String === value then value.strip! end
38
+ value.nil_or_empty? ? default(default, &block) : value
39
+ when Enumerable then
40
+ detect_in_enumerable(option, options) or default(default, &block)
41
+ when Symbol then
42
+ option == options or default(default, &block)
43
+ else
44
+ Jinx.fail(ArgumentError, "Options argument type is not supported; expected Hash or Symbol, found: #{options.class}")
45
+ end
46
+ end
47
+
48
+ # Returns the given option list as a hash, determined as follows:
49
+ # * If an item is a hash, then that hash is included in the result
50
+ # * If an item is a symbol _s_, then {_s_ => true} is included in the result
51
+ #
52
+ # @example
53
+ # Options.to_hash() #=> {}
54
+ # Options.to_hash(nil) #=> {}
55
+ # Options.to_hash(:a => 1) #=> {:a => 1}
56
+ # Options.to_hash(:a) #=> {:a => true}
57
+ # Options.to_hash(:a, :b => 2) #=> {:a => true, :b => 2}
58
+ # @param [<[Symbol, Hash]>] opts the option list
59
+ # @return [Hash] the option hash
60
+ def self.to_hash(*opts)
61
+ hash = {}
62
+ opts.compact!
63
+ opts.each do |opt|
64
+ case opt
65
+ when Symbol then hash[opt] = true
66
+ when Hash then hash.merge!(opt)
67
+ else Jinx.fail(ArgumentError, "Expected a symbol or hash option, found #{opt.qp}")
68
+ end
69
+ end
70
+ hash
71
+ end
72
+
73
+ # @param [Hash, Symbol, nil] opts the options to validate
74
+ # @raise [ValidationError] if the given options are not in the given allowable choices
75
+ def self.validate(options, choices)
76
+ to_hash(options).each_key do |opt|
77
+ Jinx.fail(ValidationError, "Option is not supported: #{opt}") unless choices.include?(opt)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def self.detect_in_enumerable(key, options)
84
+ options.detect_value do |opt|
85
+ Hash === opt ? opt[key] : opt == key
86
+ end
87
+ end
88
+
89
+ def self.default(value)
90
+ value.nil? && block_given? ? yield : value
91
+ end
92
+ end