jinx 2.1.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.
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