aequitas 0.0.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 (216) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE +21 -0
  5. data/README.rdoc +125 -0
  6. data/Rakefile +25 -0
  7. data/VERSION +1 -0
  8. data/aequitas.gemspec +20 -0
  9. data/lib/aequitas.rb +80 -0
  10. data/lib/aequitas/class_methods.rb +26 -0
  11. data/lib/aequitas/context.rb +53 -0
  12. data/lib/aequitas/contextual_rule_set.rb +221 -0
  13. data/lib/aequitas/exceptions.rb +10 -0
  14. data/lib/aequitas/macros.rb +466 -0
  15. data/lib/aequitas/message_transformer.rb +127 -0
  16. data/lib/aequitas/rule.rb +153 -0
  17. data/lib/aequitas/rule/absence.rb +14 -0
  18. data/lib/aequitas/rule/absence/blank.rb +21 -0
  19. data/lib/aequitas/rule/absence/nil.rb +21 -0
  20. data/lib/aequitas/rule/acceptance.rb +36 -0
  21. data/lib/aequitas/rule/block.rb +33 -0
  22. data/lib/aequitas/rule/confirmation.rb +41 -0
  23. data/lib/aequitas/rule/format.rb +81 -0
  24. data/lib/aequitas/rule/format/proc.rb +28 -0
  25. data/lib/aequitas/rule/format/regexp.rb +44 -0
  26. data/lib/aequitas/rule/formats/email.rb +52 -0
  27. data/lib/aequitas/rule/formats/url.rb +14 -0
  28. data/lib/aequitas/rule/guard.rb +51 -0
  29. data/lib/aequitas/rule/length.rb +87 -0
  30. data/lib/aequitas/rule/length/equal.rb +46 -0
  31. data/lib/aequitas/rule/length/maximum.rb +46 -0
  32. data/lib/aequitas/rule/length/minimum.rb +46 -0
  33. data/lib/aequitas/rule/length/range.rb +46 -0
  34. data/lib/aequitas/rule/method.rb +31 -0
  35. data/lib/aequitas/rule/numericalness.rb +85 -0
  36. data/lib/aequitas/rule/numericalness/equal.rb +30 -0
  37. data/lib/aequitas/rule/numericalness/greater_than.rb +30 -0
  38. data/lib/aequitas/rule/numericalness/greater_than_or_equal.rb +30 -0
  39. data/lib/aequitas/rule/numericalness/integer.rb +37 -0
  40. data/lib/aequitas/rule/numericalness/less_than.rb +30 -0
  41. data/lib/aequitas/rule/numericalness/less_than_or_equal.rb +30 -0
  42. data/lib/aequitas/rule/numericalness/non_integer.rb +64 -0
  43. data/lib/aequitas/rule/numericalness/not_equal.rb +30 -0
  44. data/lib/aequitas/rule/presence.rb +14 -0
  45. data/lib/aequitas/rule/presence/not_blank.rb +21 -0
  46. data/lib/aequitas/rule/presence/not_nil.rb +21 -0
  47. data/lib/aequitas/rule/primitive_type.rb +28 -0
  48. data/lib/aequitas/rule/skip_condition.rb +104 -0
  49. data/lib/aequitas/rule/within.rb +28 -0
  50. data/lib/aequitas/rule/within/range.rb +53 -0
  51. data/lib/aequitas/rule/within/range/bounded.rb +25 -0
  52. data/lib/aequitas/rule/within/range/unbounded_begin.rb +25 -0
  53. data/lib/aequitas/rule/within/range/unbounded_end.rb +25 -0
  54. data/lib/aequitas/rule/within/set.rb +39 -0
  55. data/lib/aequitas/rule_set.rb +80 -0
  56. data/lib/aequitas/support/blank.rb +22 -0
  57. data/lib/aequitas/support/equalizable.rb +113 -0
  58. data/lib/aequitas/support/ordered_hash.rb +438 -0
  59. data/lib/aequitas/version.rb +5 -0
  60. data/lib/aequitas/violation.rb +116 -0
  61. data/lib/aequitas/violation_set.rb +123 -0
  62. data/lib/aequitas/virtus.rb +29 -0
  63. data/lib/aequitas/virtus/inline_attribute_rule_extractor.rb +41 -0
  64. data/lib/aequitas/virtus/inline_attribute_rule_extractor/boolean.rb +17 -0
  65. data/lib/aequitas/virtus/inline_attribute_rule_extractor/object.rb +25 -0
  66. data/lib/aequitas/virtus/inline_attribute_rule_extractor/string.rb +27 -0
  67. data/spec/integration/aequitas/macros/validates_absence_of_spec.rb +19 -0
  68. data/spec/integration/aequitas/macros/validates_acceptance_of_spec.rb +19 -0
  69. data/spec/integration/aequitas/macros/validates_confirmation_of_spec.rb +25 -0
  70. data/spec/integration/aequitas/macros/validates_format_of_spec.rb +87 -0
  71. data/spec/integration/aequitas/macros/validates_length_of.rb +77 -0
  72. data/spec/integration/aequitas/macros/validates_numericalness_of_spec.rb +221 -0
  73. data/spec/integration/aequitas/macros/validates_presence_of_spec.rb +19 -0
  74. data/spec/integration/aequitas/macros/validates_with_block.rb +23 -0
  75. data/spec/integration/aequitas/macros/validates_with_method.rb +19 -0
  76. data/spec/integration/aequitas/macros/validates_within.rb +87 -0
  77. data/spec/integration/shared/macros/integration_spec.rb +67 -0
  78. data/spec/integration/virtus/boolean/presence_spec.rb +49 -0
  79. data/spec/integration/virtus/string/format/email_address_spec.rb +55 -0
  80. data/spec/integration/virtus/string/format/regexp_spec.rb +55 -0
  81. data/spec/integration/virtus/string/format/url_spec.rb +55 -0
  82. data/spec/integration/virtus/string/length/equal_spec.rb +49 -0
  83. data/spec/integration/virtus/string/length/range_spec.rb +49 -0
  84. data/spec/integration/virtus/string/presence_spec.rb +49 -0
  85. data/spec/rcov.opts +6 -0
  86. data/spec/spec_helper.rb +5 -0
  87. data/spec/suite.rb +5 -0
  88. data/spec/unit/aequitas/rule/absence/blank_spec.rb +45 -0
  89. data/spec/unit/aequitas/rule/acceptance_spec.rb +71 -0
  90. data/spec/unit/aequitas/rule/confirmation_spec.rb +80 -0
  91. data/spec/unit/aequitas/rule/guard_spec.rb +120 -0
  92. data/spec/unit/aequitas/rule/skip_condition_spec.rb +170 -0
  93. data/spec/unit/aequitas/rule_spec.rb +40 -0
  94. data/spec/unit/aequitas/support/blank_spec.rb +83 -0
  95. data/spec/unit/aequitas/support/equalizable/equalizer_spec.rb +21 -0
  96. data/spec/unit/aequitas/support/equalizable_spec.rb +67 -0
  97. data/spec/unit/aequitas/violation_set_spec.rb +154 -0
  98. data/spec_legacy/fixtures/barcode.rb +40 -0
  99. data/spec_legacy/fixtures/basketball_court.rb +58 -0
  100. data/spec_legacy/fixtures/basketball_player.rb +34 -0
  101. data/spec_legacy/fixtures/beta_tester_account.rb +33 -0
  102. data/spec_legacy/fixtures/bill_of_landing.rb +47 -0
  103. data/spec_legacy/fixtures/boat_dock.rb +26 -0
  104. data/spec_legacy/fixtures/city.rb +24 -0
  105. data/spec_legacy/fixtures/company.rb +93 -0
  106. data/spec_legacy/fixtures/corporate_world.rb +39 -0
  107. data/spec_legacy/fixtures/country.rb +24 -0
  108. data/spec_legacy/fixtures/ethernet_frame.rb +56 -0
  109. data/spec_legacy/fixtures/event.rb +44 -0
  110. data/spec_legacy/fixtures/g3_concert.rb +57 -0
  111. data/spec_legacy/fixtures/jabberwock.rb +27 -0
  112. data/spec_legacy/fixtures/kayak.rb +28 -0
  113. data/spec_legacy/fixtures/lernean_hydra.rb +39 -0
  114. data/spec_legacy/fixtures/llama_spaceship.rb +15 -0
  115. data/spec_legacy/fixtures/mathematical_function.rb +34 -0
  116. data/spec_legacy/fixtures/memory_object.rb +30 -0
  117. data/spec_legacy/fixtures/mittelschnauzer.rb +39 -0
  118. data/spec_legacy/fixtures/motor_launch.rb +21 -0
  119. data/spec_legacy/fixtures/multibyte.rb +16 -0
  120. data/spec_legacy/fixtures/page.rb +32 -0
  121. data/spec_legacy/fixtures/phone_number.rb +28 -0
  122. data/spec_legacy/fixtures/pirogue.rb +28 -0
  123. data/spec_legacy/fixtures/programming_language.rb +83 -0
  124. data/spec_legacy/fixtures/reservation.rb +38 -0
  125. data/spec_legacy/fixtures/scm_operation.rb +56 -0
  126. data/spec_legacy/fixtures/sms_message.rb +22 -0
  127. data/spec_legacy/fixtures/udp_packet.rb +49 -0
  128. data/spec_legacy/integration/absent_field_validator/absent_field_validator_spec.rb +90 -0
  129. data/spec_legacy/integration/absent_field_validator/spec_helper.rb +7 -0
  130. data/spec_legacy/integration/acceptance_validator/acceptance_validator_spec.rb +196 -0
  131. data/spec_legacy/integration/acceptance_validator/spec_helper.rb +7 -0
  132. data/spec_legacy/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +57 -0
  133. data/spec_legacy/integration/automatic_validation/disabling_inferred_validation_spec.rb +49 -0
  134. data/spec_legacy/integration/automatic_validation/inferred_boolean_properties_validation_spec.rb +100 -0
  135. data/spec_legacy/integration/automatic_validation/inferred_float_property_validation_spec.rb +45 -0
  136. data/spec_legacy/integration/automatic_validation/inferred_format_validation_spec.rb +35 -0
  137. data/spec_legacy/integration/automatic_validation/inferred_integer_properties_validation_spec.rb +70 -0
  138. data/spec_legacy/integration/automatic_validation/inferred_length_validation_spec.rb +142 -0
  139. data/spec_legacy/integration/automatic_validation/inferred_presence_validation_spec.rb +45 -0
  140. data/spec_legacy/integration/automatic_validation/inferred_primitive_validation_spec.rb +22 -0
  141. data/spec_legacy/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +48 -0
  142. data/spec_legacy/integration/automatic_validation/inferred_within_validation_spec.rb +35 -0
  143. data/spec_legacy/integration/automatic_validation/spec_helper.rb +57 -0
  144. data/spec_legacy/integration/block_validator/block_validator_spec.rb +32 -0
  145. data/spec_legacy/integration/block_validator/spec_helper.rb +5 -0
  146. data/spec_legacy/integration/conditional_validation/if_condition_spec.rb +63 -0
  147. data/spec_legacy/integration/conditional_validation/spec_helper.rb +5 -0
  148. data/spec_legacy/integration/confirmation_validator/confirmation_validator_spec.rb +76 -0
  149. data/spec_legacy/integration/confirmation_validator/spec_helper.rb +5 -0
  150. data/spec_legacy/integration/datamapper_models/association_validation_spec.rb +29 -0
  151. data/spec_legacy/integration/datamapper_models/inheritance_spec.rb +82 -0
  152. data/spec_legacy/integration/dirty_attributes/dirty_attributes_spec.rb +13 -0
  153. data/spec_legacy/integration/duplicated_validations/duplicated_validations_spec.rb +24 -0
  154. data/spec_legacy/integration/duplicated_validations/spec_helper.rb +5 -0
  155. data/spec_legacy/integration/format_validator/email_format_validator_spec.rb +139 -0
  156. data/spec_legacy/integration/format_validator/format_validator_spec.rb +64 -0
  157. data/spec_legacy/integration/format_validator/regexp_validator_spec.rb +33 -0
  158. data/spec_legacy/integration/format_validator/spec_helper.rb +5 -0
  159. data/spec_legacy/integration/format_validator/url_format_validator_spec.rb +93 -0
  160. data/spec_legacy/integration/length_validator/default_value_spec.rb +14 -0
  161. data/spec_legacy/integration/length_validator/equality_spec.rb +87 -0
  162. data/spec_legacy/integration/length_validator/error_message_spec.rb +22 -0
  163. data/spec_legacy/integration/length_validator/maximum_spec.rb +49 -0
  164. data/spec_legacy/integration/length_validator/minimum_spec.rb +54 -0
  165. data/spec_legacy/integration/length_validator/range_spec.rb +87 -0
  166. data/spec_legacy/integration/length_validator/spec_helper.rb +7 -0
  167. data/spec_legacy/integration/method_validator/method_validator_spec.rb +242 -0
  168. data/spec_legacy/integration/method_validator/spec_helper.rb +5 -0
  169. data/spec_legacy/integration/numeric_validator/equality_with_float_type_spec.rb +65 -0
  170. data/spec_legacy/integration/numeric_validator/equality_with_integer_type_spec.rb +41 -0
  171. data/spec_legacy/integration/numeric_validator/float_type_spec.rb +90 -0
  172. data/spec_legacy/integration/numeric_validator/gt_with_float_type_spec.rb +37 -0
  173. data/spec_legacy/integration/numeric_validator/gte_with_float_type_spec.rb +37 -0
  174. data/spec_legacy/integration/numeric_validator/integer_only_true_spec.rb +91 -0
  175. data/spec_legacy/integration/numeric_validator/integer_type_spec.rb +86 -0
  176. data/spec_legacy/integration/numeric_validator/lt_with_float_type_spec.rb +37 -0
  177. data/spec_legacy/integration/numeric_validator/lte_with_float_type_spec.rb +37 -0
  178. data/spec_legacy/integration/numeric_validator/spec_helper.rb +5 -0
  179. data/spec_legacy/integration/primitive_validator/primitive_validator_spec.rb +92 -0
  180. data/spec_legacy/integration/primitive_validator/spec_helper.rb +5 -0
  181. data/spec_legacy/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb +118 -0
  182. data/spec_legacy/integration/required_field_validator/association_spec.rb +69 -0
  183. data/spec_legacy/integration/required_field_validator/boolean_type_value_spec.rb +164 -0
  184. data/spec_legacy/integration/required_field_validator/date_type_value_spec.rb +127 -0
  185. data/spec_legacy/integration/required_field_validator/datetime_type_value_spec.rb +127 -0
  186. data/spec_legacy/integration/required_field_validator/float_type_value_spec.rb +131 -0
  187. data/spec_legacy/integration/required_field_validator/integer_type_value_spec.rb +99 -0
  188. data/spec_legacy/integration/required_field_validator/plain_old_ruby_object_spec.rb +35 -0
  189. data/spec_legacy/integration/required_field_validator/shared_examples.rb +26 -0
  190. data/spec_legacy/integration/required_field_validator/spec_helper.rb +7 -0
  191. data/spec_legacy/integration/required_field_validator/string_type_value_spec.rb +167 -0
  192. data/spec_legacy/integration/required_field_validator/text_type_value_spec.rb +49 -0
  193. data/spec_legacy/integration/shared/default_validation_context.rb +13 -0
  194. data/spec_legacy/integration/shared/valid_and_invalid_model.rb +35 -0
  195. data/spec_legacy/integration/uniqueness_validator/spec_helper.rb +5 -0
  196. data/spec_legacy/integration/uniqueness_validator/uniqueness_validator_spec.rb +116 -0
  197. data/spec_legacy/integration/within_validator/spec_helper.rb +5 -0
  198. data/spec_legacy/integration/within_validator/within_validator_spec.rb +168 -0
  199. data/spec_legacy/public/resource_spec.rb +105 -0
  200. data/spec_legacy/spec.opts +4 -0
  201. data/spec_legacy/spec_helper.rb +29 -0
  202. data/spec_legacy/unit/contextual_validators/emptiness_spec.rb +50 -0
  203. data/spec_legacy/unit/contextual_validators/execution_spec.rb +48 -0
  204. data/spec_legacy/unit/contextual_validators/spec_helper.rb +37 -0
  205. data/spec_legacy/unit/generic_validator/equality_operator_spec.rb +26 -0
  206. data/spec_legacy/unit/generic_validator/optional_spec.rb +54 -0
  207. data/spec_legacy/unit/validators/within_validator_spec.rb +23 -0
  208. data/spec_legacy/unit/violation_set/adding_spec.rb +54 -0
  209. data/spec_legacy/unit/violation_set/emptiness_spec.rb +38 -0
  210. data/spec_legacy/unit/violation_set/enumerable_spec.rb +32 -0
  211. data/spec_legacy/unit/violation_set/reading_spec.rb +35 -0
  212. data/spec_legacy/unit/violation_set/respond_to_spec.rb +15 -0
  213. data/tasks/spec.rake +38 -0
  214. data/tasks/yard.rake +9 -0
  215. data/tasks/yardstick.rake +19 -0
  216. metadata +302 -0
@@ -0,0 +1,22 @@
1
+ module Aequitas
2
+ # Determines whether the specified +value+ is blank.
3
+ #
4
+ # An object is blank if it's false, empty, or a whitespace string.
5
+ # For example, "", " ", +nil+, [], and {} are blank.
6
+ #
7
+ # @api semipublic
8
+ def self.blank?(value)
9
+ case value
10
+ when ::NilClass, ::FalseClass
11
+ true
12
+ when ::TrueClass, ::Numeric
13
+ false
14
+ when ::Array, ::Hash
15
+ value.empty?
16
+ when ::String
17
+ value !~ /\S/
18
+ else
19
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,113 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Aequitas
4
+ # TODO: rename ValueObject (?)
5
+ module Equalizable
6
+
7
+ # An Equalizer module which defines #inspect, #eql?, #== and #hash
8
+ # for instances of this class
9
+ attr_reader :equalizer
10
+
11
+ # Define and include a module that provides Value Object semantics for
12
+ # this class. Included module will have #inspect, #eql?, #== and #hash
13
+ # methods whose definition is based on the _keys_ argument
14
+ #
15
+ # @param [Array(Symbol)] keys
16
+ # List of keys that will be used to define #inspect, #eql?, #==, and #hash
17
+ #
18
+ # @return [self]
19
+ #
20
+ # @api public
21
+ def equalize_on(*keys)
22
+ @equalizer = Equalizer.new(keys)
23
+ @equalizer.compile
24
+ include @equalizer
25
+
26
+ self
27
+ end
28
+
29
+ class Equalizer < Module
30
+ # List of methods that will be used to compile #inspect,
31
+ # #eql?, #== and #hash methods
32
+ #
33
+ # @return [Array(Symbol)]
34
+ attr_reader :keys
35
+
36
+ def initialize(keys)
37
+ @keys = keys
38
+ end
39
+
40
+ # Compile the equalizer methods based on #keys
41
+ #
42
+ # @return [self]
43
+ def compile
44
+ define_inspect_method
45
+ define_eql_method
46
+ define_equivalent_method
47
+ define_hash_method
48
+
49
+ self
50
+ end
51
+
52
+ private
53
+
54
+ # Define an inspect method that reports the values of
55
+ # the instance's methods identified by #keys
56
+ #
57
+ # @return [self]
58
+ def define_inspect_method
59
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
60
+ def inspect
61
+ "#<\#{self.class.inspect} #{keys.map { |k| "#{k}=\#{#{k}.inspect}" }.join(' ')}>"
62
+ end
63
+ RUBY
64
+ end
65
+
66
+ # Define an #eql? method based on the instance's values identified by #keys
67
+ #
68
+ # @return [self]
69
+ def define_eql_method
70
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
71
+ def eql?(other)
72
+ return true if equal?(other)
73
+ instance_of?(other.class) &&
74
+ #{keys.map { |key| "#{key}.eql?(other.#{key})" }.join(' && ')}
75
+ end
76
+ RUBY
77
+ end
78
+
79
+ # Define an #== method based on the instance's values identified by #keys
80
+ #
81
+ # @return [self]
82
+ def define_equivalent_method
83
+ respond_to = []
84
+ equivalent = []
85
+
86
+ keys.each do |key|
87
+ respond_to << "other.respond_to?(#{key.inspect})"
88
+ equivalent << "#{key} == other.#{key}"
89
+ end
90
+
91
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
92
+ def ==(other)
93
+ return true if equal?(other)
94
+ return false unless kind_of?(other.class) || other.kind_of?(self.class)
95
+ #{respond_to.join(' && ')} &&
96
+ #{equivalent.join(' && ')}
97
+ end
98
+ RUBY
99
+ end
100
+
101
+ # Define a #hash method based on the instance's values identified by #keys
102
+ #
103
+ # @return [self]
104
+ def define_hash_method
105
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
106
+ def hash
107
+ self.class.hash ^ #{keys.map { |key| "#{key}.hash" }.join(' ^ ')}
108
+ end
109
+ RUBY
110
+ end
111
+ end # class Equalizer
112
+ end # module Equalizable
113
+ end # module Aequitas
@@ -0,0 +1,438 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Aequitas
4
+
5
+ # TITLE:
6
+ #
7
+ # OrderedHash (originally Dictionary)
8
+ #
9
+ # AUTHORS:
10
+ #
11
+ # - Jan Molic
12
+ # - Thomas Sawyer
13
+ #
14
+ # CREDIT:
15
+ #
16
+ # - Andrew Johnson (merge, to_a, inspect, shift and Hash[])
17
+ # - Jeff Sharpe (reverse and reverse!)
18
+ # - Thomas Leitner (has_key? and key?)
19
+ #
20
+ # LICENSE:
21
+ #
22
+ # Copyright (c) 2005 Jan Molic, Thomas Sawyer
23
+ #
24
+ # Ruby License
25
+ #
26
+ # This module is free software. You may use, modify, and/or redistribute this
27
+ # software under the same terms as Ruby.
28
+ #
29
+ # This program is distributed in the hope that it will be useful, but WITHOUT
30
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
31
+ # FOR A PARTICULAR PURPOSE.
32
+ #
33
+ # Originally ported from OrderHash 2.0, Copyright (c) 2005 jan molic
34
+ #
35
+ # LOG:
36
+ #
37
+ # - 2007.10.31 trans
38
+ # Fixed initialize so the constructor blocks correctly effected dictionary
39
+ # rather then just the internal hash.
40
+
41
+ # = Dictionary
42
+ #
43
+ # The Dictionary class is a Hash that preserves order.
44
+ # So it has some array-like extensions also. By defualt
45
+ # a Dictionary object preserves insertion order, but any
46
+ # order can be specified including alphabetical key order.
47
+ #
48
+ # == Usage
49
+ #
50
+ # Just require this file and use Dictionary instead of Hash.
51
+ #
52
+ # # You can do simply
53
+ # hsh = Dictionary.new
54
+ # hsh['z'] = 1
55
+ # hsh['a'] = 2
56
+ # hsh['c'] = 3
57
+ # p hsh.keys #=> ['z','a','c']
58
+ #
59
+ # # or using Dictionary[] method
60
+ # hsh = Dictionary['z', 1, 'a', 2, 'c', 3]
61
+ # p hsh.keys #=> ['z','a','c']
62
+ #
63
+ # # but this doesn't preserve order
64
+ # hsh = Dictionary['z'=>1, 'a'=>2, 'c'=>3]
65
+ # p hsh.keys #=> ['a','c','z']
66
+ #
67
+ # # Dictionary has useful extensions: push, pop and unshift
68
+ # p hsh.push('to_end', 15) #=> true, key added
69
+ # p hsh.push('to_end', 30) #=> false, already - nothing happen
70
+ # p hsh.unshift('to_begin', 50) #=> true, key added
71
+ # p hsh.unshift('to_begin', 60) #=> false, already - nothing happen
72
+ # p hsh.keys #=> ["to_begin", "a", "c", "z", "to_end"]
73
+ # p hsh.pop #=> ["to_end", 15], if nothing remains, return nil
74
+ # p hsh.keys #=> ["to_begin", "a", "c", "z"]
75
+ # p hsh.shift #=> ["to_begin", 30], if nothing remains, return nil
76
+ #
77
+ # == Usage Notes
78
+ #
79
+ # * You can use #order_by to set internal sort order.
80
+ # * #<< takes a two element [k,v] array and inserts.
81
+ # * Use ::auto which creates Dictionay sub-entries as needed.
82
+ # * And ::alpha which creates a new Dictionary sorted by key.
83
+ class OrderedHash
84
+
85
+ include Enumerable
86
+
87
+ class << self
88
+ #--
89
+ # TODO is this needed? Doesn't the super class do this?
90
+ #++
91
+ def [](*args)
92
+ hsh = new
93
+ if Hash === args[0]
94
+ hsh.replace(args[0])
95
+ elsif (args.size % 2) != 0
96
+ raise ArgumentError, "odd number of elements for Hash"
97
+ else
98
+ while !args.empty?
99
+ hsh[args.shift] = args.shift
100
+ end
101
+ end
102
+ hsh
103
+ end
104
+
105
+ # Like #new but the block sets the order.
106
+ #
107
+ def new_by(*args, &blk)
108
+ new(*args).order_by(&blk)
109
+ end
110
+
111
+ # Alternate to #new which creates a dictionary sorted by key.
112
+ #
113
+ # d = Dictionary.alpha
114
+ # d["z"] = 1
115
+ # d["y"] = 2
116
+ # d["x"] = 3
117
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
118
+ #
119
+ # This is equivalent to:
120
+ #
121
+ # Dictionary.new.order_by { |key,value| key }
122
+ def alpha(*args, &block)
123
+ new(*args, &block).order_by_key
124
+ end
125
+
126
+ # Alternate to #new which auto-creates sub-dictionaries as needed.
127
+ #
128
+ # d = Dictionary.auto
129
+ # d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
130
+ #
131
+ def auto(*args)
132
+ #AutoDictionary.new(*args)
133
+ leet = lambda { |hsh, key| hsh[key] = new(&leet) }
134
+ new(*args, &leet)
135
+ end
136
+ end
137
+
138
+ # New Dictiionary.
139
+ def initialize(*args, &blk)
140
+ @order = []
141
+ @order_by = nil
142
+ if blk
143
+ dict = self # This ensure autmatic key entry effect the
144
+ oblk = lambda{ |hsh, key| blk[dict,key] } # dictionary rather then just the interal hash.
145
+ @hash = Hash.new(*args, &oblk)
146
+ else
147
+ @hash = Hash.new(*args)
148
+ end
149
+ end
150
+
151
+ def order
152
+ reorder if @order_by
153
+ @order
154
+ end
155
+
156
+ # Keep dictionary sorted by a specific sort order.
157
+ def order_by( &block )
158
+ @order_by = block
159
+ order
160
+ self
161
+ end
162
+
163
+ # Keep dictionary sorted by key.
164
+ #
165
+ # d = Dictionary.new.order_by_key
166
+ # d["z"] = 1
167
+ # d["y"] = 2
168
+ # d["x"] = 3
169
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
170
+ #
171
+ # This is equivalent to:
172
+ #
173
+ # Dictionary.new.order_by { |key,value| key }
174
+ #
175
+ # The initializer Dictionary#alpha also provides this.
176
+ def order_by_key
177
+ @order_by = lambda { |k,v| k }
178
+ order
179
+ self
180
+ end
181
+
182
+ # Keep dictionary sorted by value.
183
+ #
184
+ # d = Dictionary.new.order_by_value
185
+ # d["z"] = 1
186
+ # d["y"] = 2
187
+ # d["x"] = 3
188
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
189
+ #
190
+ # This is equivalent to:
191
+ #
192
+ # Dictionary.new.order_by { |key,value| value }
193
+ def order_by_value
194
+ @order_by = lambda { |k,v| v }
195
+ order
196
+ self
197
+ end
198
+
199
+ #
200
+ def reorder
201
+ if @order_by
202
+ assoc = @order.collect{ |k| [k,@hash[k]] }.sort_by(&@order_by)
203
+ @order = assoc.collect{ |k,v| k }
204
+ end
205
+ @order
206
+ end
207
+
208
+ def ==(hsh2)
209
+ if hsh2.is_a?( Dictionary )
210
+ @order == hsh2.order &&
211
+ @hash == hsh2.instance_variable_get("@hash")
212
+ else
213
+ false
214
+ end
215
+ end
216
+
217
+ def [] k
218
+ @hash[ k ]
219
+ end
220
+
221
+ def fetch(k, *a, &b)
222
+ @hash.fetch(k, *a, &b)
223
+ end
224
+
225
+ # Store operator.
226
+ #
227
+ # h[key] = value
228
+ #
229
+ # Or with additional index.
230
+ #
231
+ # h[key,index] = value
232
+ def []=(k, i=nil, v=nil)
233
+ if v
234
+ insert(i,k,v)
235
+ else
236
+ store(k,i)
237
+ end
238
+ end
239
+
240
+ def insert( i,k,v )
241
+ @order.insert( i,k )
242
+ @hash.store( k,v )
243
+ end
244
+
245
+ def store( a,b )
246
+ @order.push( a ) unless @hash.has_key?( a )
247
+ @hash.store( a,b )
248
+ end
249
+
250
+ def clear
251
+ @order = []
252
+ @hash.clear
253
+ end
254
+
255
+ def delete( key )
256
+ @order.delete( key )
257
+ @hash.delete( key )
258
+ end
259
+
260
+ def each_key
261
+ order.each { |k| yield( k ) }
262
+ self
263
+ end
264
+
265
+ def each_value
266
+ order.each { |k| yield( @hash[k] ) }
267
+ self
268
+ end
269
+
270
+ def each
271
+ order.each { |k| yield( k,@hash[k] ) }
272
+ self
273
+ end
274
+ alias each_pair each
275
+
276
+ def delete_if
277
+ order.clone.each { |k| delete k if yield(k,@hash[k]) }
278
+ self
279
+ end
280
+
281
+ def values
282
+ ary = []
283
+ order.each { |k| ary.push @hash[k] }
284
+ ary
285
+ end
286
+
287
+ def keys
288
+ order
289
+ end
290
+
291
+ def invert
292
+ hsh2 = self.class.new
293
+ order.each { |k| hsh2[@hash[k]] = k }
294
+ hsh2
295
+ end
296
+
297
+ def reject( &block )
298
+ self.dup.delete_if(&block)
299
+ end
300
+
301
+ def reject!( &block )
302
+ hsh2 = reject(&block)
303
+ self == hsh2 ? nil : hsh2
304
+ end
305
+
306
+ def replace( hsh2 )
307
+ @order = hsh2.order
308
+ @hash = hsh2.hash
309
+ end
310
+
311
+ def shift
312
+ key = order.first
313
+ key ? [key,delete(key)] : super
314
+ end
315
+
316
+ def unshift( k,v )
317
+ unless @hash.include?( k )
318
+ @order.unshift( k )
319
+ @hash.store( k,v )
320
+ true
321
+ else
322
+ false
323
+ end
324
+ end
325
+
326
+ def <<(kv)
327
+ push( *kv )
328
+ end
329
+
330
+ def push( k,v )
331
+ unless @hash.include?( k )
332
+ @order.push( k )
333
+ @hash.store( k,v )
334
+ true
335
+ else
336
+ false
337
+ end
338
+ end
339
+
340
+ def pop
341
+ key = order.last
342
+ key ? [key,delete(key)] : nil
343
+ end
344
+
345
+ def inspect
346
+ ary = []
347
+ each {|k,v| ary << k.inspect + "=>" + v.inspect}
348
+ '{' + ary.join(", ") + '}'
349
+ end
350
+
351
+ def dup
352
+ a = []
353
+ each{ |k,v| a << k; a << v }
354
+ self.class[*a]
355
+ end
356
+
357
+ def update( hsh2 )
358
+ hsh2.each { |k,v| self[k] = v }
359
+ reorder
360
+ self
361
+ end
362
+ alias :merge! update
363
+
364
+ def merge( hsh2 )
365
+ self.dup.update(hsh2)
366
+ end
367
+
368
+ def select
369
+ ary = []
370
+ each { |k,v| ary << [k,v] if yield k,v }
371
+ ary
372
+ end
373
+
374
+ def reverse!
375
+ @order.reverse!
376
+ self
377
+ end
378
+
379
+ def reverse
380
+ dup.reverse!
381
+ end
382
+
383
+ def first
384
+ @hash[order.first]
385
+ end
386
+
387
+ def last
388
+ @hash[order.last]
389
+ end
390
+
391
+ def length
392
+ @order.length
393
+ end
394
+ alias :size :length
395
+
396
+ def empty?
397
+ @hash.empty?
398
+ end
399
+
400
+ def has_key?(key)
401
+ @hash.has_key?(key)
402
+ end
403
+
404
+ def key?(key)
405
+ @hash.key?(key)
406
+ end
407
+
408
+ def to_a
409
+ ary = []
410
+ each { |k,v| ary << [k,v] }
411
+ ary
412
+ end
413
+
414
+ def to_json
415
+ buf = "["
416
+ map do |k,v|
417
+ buf << k.to_json
418
+ buf << ", "
419
+ buf << v.to_json
420
+ end.join(", ")
421
+ buf << "]"
422
+ buf
423
+ end
424
+
425
+ def to_s
426
+ self.to_a.to_s
427
+ end
428
+
429
+ def to_hash
430
+ @hash.dup
431
+ end
432
+
433
+ def to_h
434
+ @hash.dup
435
+ end
436
+
437
+ end # class OrderedHash
438
+ end # module Aequitas