iiif-presentation 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +23 -0
  7. data/README.md +173 -0
  8. data/Rakefile +12 -0
  9. data/VERSION +1 -0
  10. data/gemfiles/rails3.gemfile +5 -0
  11. data/gemfiles/rails4.gemfile +5 -0
  12. data/iiif-presentation.gemspec +28 -0
  13. data/lib/active_support/ordered_hash.rb +147 -0
  14. data/lib/iiif/hash_behaviours.rb +150 -0
  15. data/lib/iiif/presentation.rb +25 -0
  16. data/lib/iiif/presentation/abstract_resource.rb +75 -0
  17. data/lib/iiif/presentation/annotation.rb +25 -0
  18. data/lib/iiif/presentation/annotation_list.rb +28 -0
  19. data/lib/iiif/presentation/canvas.rb +45 -0
  20. data/lib/iiif/presentation/collection.rb +29 -0
  21. data/lib/iiif/presentation/image_resource.rb +115 -0
  22. data/lib/iiif/presentation/layer.rb +34 -0
  23. data/lib/iiif/presentation/manifest.rb +39 -0
  24. data/lib/iiif/presentation/range.rb +32 -0
  25. data/lib/iiif/presentation/resource.rb +21 -0
  26. data/lib/iiif/presentation/sequence.rb +35 -0
  27. data/lib/iiif/service.rb +418 -0
  28. data/spec/fixtures/manifests/complete_from_spec.json +171 -0
  29. data/spec/fixtures/manifests/minimal.json +40 -0
  30. data/spec/fixtures/manifests/service_only.json +11 -0
  31. data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +159 -0
  32. data/spec/integration/iiif/presentation/image_resource_spec.rb +123 -0
  33. data/spec/integration/iiif/service_spec.rb +211 -0
  34. data/spec/spec_helper.rb +104 -0
  35. data/spec/unit/active_support/ordered_hash_spec.rb +155 -0
  36. data/spec/unit/iiif/hash_behaviours_spec.rb +569 -0
  37. data/spec/unit/iiif/presentation/abstract_resource_spec.rb +133 -0
  38. data/spec/unit/iiif/presentation/annotation_list_spec.rb +7 -0
  39. data/spec/unit/iiif/presentation/annotation_spec.rb +7 -0
  40. data/spec/unit/iiif/presentation/canvas_spec.rb +40 -0
  41. data/spec/unit/iiif/presentation/collection_spec.rb +54 -0
  42. data/spec/unit/iiif/presentation/image_resource_spec.rb +13 -0
  43. data/spec/unit/iiif/presentation/layer_spec.rb +38 -0
  44. data/spec/unit/iiif/presentation/manifest_spec.rb +89 -0
  45. data/spec/unit/iiif/presentation/range_spec.rb +43 -0
  46. data/spec/unit/iiif/presentation/resource_spec.rb +16 -0
  47. data/spec/unit/iiif/presentation/sequence_spec.rb +110 -0
  48. data/spec/unit/iiif/presentation/shared_examples/abstract_resource_only_keys.rb +43 -0
  49. data/spec/unit/iiif/presentation/shared_examples/any_type_keys.rb +33 -0
  50. data/spec/unit/iiif/presentation/shared_examples/array_only_keys.rb +44 -0
  51. data/spec/unit/iiif/presentation/shared_examples/int_only_keys.rb +49 -0
  52. data/spec/unit/iiif/presentation/shared_examples/string_only_keys.rb +29 -0
  53. data/spec/unit/iiif/service_spec.rb +10 -0
  54. metadata +262 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1e600987059a68eef0064df0b7d4a62606af135e
4
+ data.tar.gz: d1a85d821d52a370f9c3b0cd27383437cd242694
5
+ SHA512:
6
+ metadata.gz: 65ab0328b4f5c514e4c6a3860d24aa00d0d9908971d64ff0a36fd000d552c1992711700c30ea690a43b1a436b3c8b459242d1f853a01a71b0b699a4685fd8228
7
+ data.tar.gz: e210d147df7c6ce971c518c5f117c2918aadab3ad9f4e115a3add980453f81251a33acd3ec69dcb576b0f5157c23b14df37f9517e4b82505d8ae9fa09cc32eb0
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ coverage/
2
+ pkg/
3
+ Gemfile.lock
4
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.1.0
5
+ gemfile:
6
+ - gemfiles/rails3.gemfile
7
+ - gemfiles/rails4.gemfile
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2014, Jon Stroop
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # O'Sullivan: A Ruby API for working with IIIF Presentation manifests
2
+
3
+ [![Build Status](https://travis-ci.org/IIIF/osullivan.svg?branch=development)](https://travis-ci.org/IIIF/osullivan)
4
+ [![Coverage Status](https://coveralls.io/repos/IIIF/osullivan/badge.svg?branch=development&service=github)](https://coveralls.io/github/IIIF/osullivan?branch=development)
5
+
6
+
7
+ ## Installation
8
+
9
+ From the source code do `rake install`, or get the latest release [from RubyGems](https://rubygems.org/gems/iiif-presentation).
10
+
11
+ ## Building New Objects
12
+
13
+ There is (or will be) a class for all types in [IIIF Presentation API Spec](http://iiif.io/api/presentation/2.0/).
14
+
15
+
16
+
17
+
18
+ ```ruby
19
+ require 'iiif/presentation'
20
+
21
+ seed = {
22
+ '@id' => 'http://example.com/manifest',
23
+ 'label' => 'My Manifest'
24
+ }
25
+ # Any options you add are added to the object
26
+ manifest = IIIF::Presentation::Manifest.new(seed)
27
+
28
+ canvas = IIIF::Presentation::Canvas.new()
29
+ # All classes act like `ActiveSupport::OrderedHash`es, for the most part.
30
+ # Use `[]=` to set JSON-LD properties...
31
+ canvas['@id'] = 'http://example.com/canvas'
32
+ # ...but there are also accessors and mutators for the properties mentioned in
33
+ # the spec
34
+ canvas.width = 10
35
+ canvas.height = 20
36
+ canvas.label = 'My Canvas'
37
+
38
+ oc = IIIF::Presentation::Resource.new('@id' => 'http://example.com/content')
39
+ canvas.other_content << oc
40
+
41
+ manifest.sequences << canvas
42
+
43
+ puts manifest.to_json(pretty: true)
44
+ ```
45
+
46
+ Methods are generated dynamically, which means `#methods` is your friend:
47
+
48
+ ```ruby
49
+ manifest = IIIF::Presentation::Manifest.new()
50
+ puts manifest.methods(false)
51
+ > label=
52
+ > label
53
+ > description=
54
+ > description
55
+ > thumbnail=
56
+ > thumbnail
57
+ > attribution=
58
+ > attribution
59
+ > viewing_hint=
60
+ > viewingHint=
61
+ > viewing_hint
62
+ > viewingHint
63
+ [...]
64
+ ```
65
+
66
+ Note that multi-word properties are implemented as snake_case (because this is
67
+ Ruby), but is serialized as camelCase. There are camelCase aliases for these.
68
+
69
+ ```ruby
70
+ manifest = IIIF::Presentation::Manifest.new()
71
+ manifest.viewing_hint = 'paged'
72
+ puts manifest.to_json(pretty: true, force: true) # force: true skips validations
73
+
74
+ > {
75
+ > "@context": "http://iiif.io/api/presentation/2/context.json",
76
+ > "@type": "sc:Manifest",
77
+ > "viewingHint": "paged"
78
+ > }
79
+
80
+ ```
81
+
82
+ ## Parsing Existing Objects
83
+
84
+ Use `IIIF::Service#parse`. It will figure out what the object
85
+ should be, based on `@type`, and fall back to `ActiveSupport::OrderedHash` when
86
+ it can't e.g.:
87
+
88
+ ```ruby
89
+ seed = '{
90
+ "@context": "http://iiif.io/api/presentation/2/context.json",
91
+ "@id": "http://example.com/manifest",
92
+ "@type": "sc:Manifest",
93
+ "label": "My Manifest",
94
+ "service": {
95
+ "@context": "http://iiif.io/api/image/2/context.json",
96
+ "@id":"http://www.example.org/images/book1-page1",
97
+ "profile":"http://iiif.io/api/image/2/profiles/level2.json"
98
+ },
99
+ "seeAlso": {
100
+ "@id": "http://www.example.org/library/catalog/book1.marc",
101
+ "format": "application/marc"
102
+ },
103
+ "sequences": [
104
+ {
105
+ "@id":"http://www.example.org/iiif/book1/sequence/normal",
106
+ "@type":"sc:Sequence",
107
+ "label":"Current Page Order",
108
+ "viewingDirection":"left-to-right",
109
+ "viewingHint":"paged",
110
+ "startCanvas": "http://www.example.org/iiif/book1/canvas/p2",
111
+ "canvases": [
112
+ {
113
+ "@id": "http://example.com/canvas",
114
+ "@type": "sc:Canvas",
115
+ "width": 10,
116
+ "height": 20,
117
+ "label": "My Canvas",
118
+ "otherContent": [
119
+ {
120
+ "@id": "http://example.com/content",
121
+ "@type":"sc:AnnotationList",
122
+ "motivation": "sc:painting"
123
+ }
124
+ ]
125
+ }
126
+ ]
127
+ }
128
+ ]
129
+ }'
130
+
131
+ obj = IIIF::Service.parse(seed) # can also be a file path or a Hash
132
+ puts obj.class
133
+ puts obj.see_also.class
134
+
135
+ > IIIF::Presentation::Manifest
136
+ > ActiveSupport::OrderedHash
137
+ ```
138
+
139
+ ## Validation and Exceptions
140
+
141
+ This is work in progress. Right now exceptions are generally raised when you
142
+ try to set something to a type it should never be:
143
+
144
+ ```ruby
145
+ manifest = IIIF::Presentation::Manifest.new
146
+ manifest.sequences = 'quux'
147
+
148
+ > [...] sequences must be an Array. (IIIF::Presentation::IllegalValueError)
149
+ ```
150
+
151
+ and also if any required properties are missing when calling `to_json`
152
+
153
+ ```ruby
154
+ canvas = IIIF::Presentation::Canvas.new('@id' => 'http://example.com/canvas')
155
+ puts canvas.to_json(pretty: true)
156
+
157
+ > A(n) width is required for each IIIF::Presentation::Canvas (IIIF::Presentation::MissingRequiredKeyError)
158
+ ```
159
+
160
+ but you can skip this validation by adding `force: true`:
161
+
162
+ ```ruby
163
+ canvas = IIIF::Presentation::Canvas.new('@id' => 'http://example.com/canvas')
164
+ puts canvas.to_json(pretty: true, force: true)
165
+
166
+ > {
167
+ > "@context": "http://iiif.io/api/presentation/2/context.json",
168
+ > "@id": "http://example.com/canvas",
169
+ > "@type": "sc:Canvas"
170
+ > }
171
+ ```
172
+ This all needs a bit of tidying up, finishing, and refactoring, so expect it to
173
+ change.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :ci do
9
+ Rake::Task['spec'].invoke
10
+ end
11
+
12
+ task default: :ci
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.4
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../"
4
+
5
+ gem 'activesupport', '~> 3.2', '>= 3.2.18'
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../"
4
+
5
+ gem 'activesupport', '~> 4.1'
@@ -0,0 +1,28 @@
1
+ version = File.read(File.expand_path('../VERSION', __FILE__)).strip
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'iiif-presentation'
5
+ spec.version = version
6
+ spec.authors = ['Jon Stroop']
7
+ spec.email = ['jpstroop@gmail.com']
8
+ spec.description = 'API for working with IIIF Presentation manifests.'
9
+ spec.summary = 'API for working with IIIF Presentation manifests.'
10
+ spec.license = 'Simplified BSD'
11
+ spec.homepage = 'https://github.com/iiif/osullivan'
12
+
13
+ spec.files = `git ls-files`.split($/)
14
+ spec.test_files = spec.files.grep(%r{^spec/})
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_development_dependency 'bundler'
18
+ spec.add_development_dependency 'rake'
19
+ spec.add_development_dependency 'rspec'
20
+ spec.add_development_dependency 'coveralls'
21
+ spec.add_development_dependency 'webmock'
22
+ spec.add_development_dependency 'multi_json'
23
+ spec.add_development_dependency 'vcr', '~> 2.9.3'
24
+
25
+ spec.add_dependency 'json'
26
+ spec.add_dependency 'activesupport', '>= 3.2.18'
27
+ spec.add_dependency 'faraday', '~> 0.9.0'
28
+ end
@@ -0,0 +1,147 @@
1
+ require 'active_support/inflector'
2
+ require 'active_support/ordered_hash'
3
+
4
+ module ActiveSupport
5
+ class OrderedHash < ::Hash
6
+
7
+ # Insert a new key and value at the suppplied index.
8
+ #
9
+ # Note that this is slightly different from Array#insert in that new
10
+ # entries must be added one at a time, i.e. insert(n, k, v, k, v...) is
11
+ # not supported.
12
+ #
13
+ # @param [Integer] index
14
+ # @param [Object] key
15
+ # @param [Object] value
16
+ def insert(index, key, value)
17
+ tmp = ActiveSupport::OrderedHash.new
18
+ index = self.length + 1 + index if index < 0
19
+ if index < 0
20
+ m = "Index #{index} is too small for current length (#{length})"
21
+ raise IndexError, m
22
+ end
23
+ if index > 0
24
+ i=0
25
+ self.each do |k,v|
26
+ tmp[k] = v
27
+ self.delete(k)
28
+ i+=1
29
+ break if i == index
30
+ end
31
+ end
32
+ tmp[key] = value
33
+ tmp.merge!(self) # copy the remaining to tmp
34
+ self.clear # start over...
35
+ self.merge!(tmp) # now put them all back
36
+ self
37
+ end
38
+
39
+ # Insert a key and value before an existing key or the first entry for'
40
+ # which the supplied block evaluates to true. The block takes precendence
41
+ # over the supplied key.
42
+ # Options:'
43
+ # * :existing_key (default: nil). If nil or not supplied then a block is required.
44
+ # * :new_key (required)
45
+ # * :value (required)
46
+ # @raise KeyError if the supplied existing key is not found, the new
47
+ # key exists, or the block never evaluates to true.
48
+ def insert_before(hsh, &block)
49
+ existing_key = hsh.fetch(:existing_key, nil)
50
+ new_key = hsh[:new_key]
51
+ value = hsh[:value]
52
+ if block_given?
53
+ self.insert_here(0, new_key, value, &block)
54
+ else
55
+ self.insert_here(0, new_key, value, existing_key)
56
+ end
57
+ end
58
+
59
+ # Insert a key and value after an existing key or the first entry for'
60
+ # which the supplied block evaluates to true. The block takes precendence
61
+ # over the supplied key.
62
+ # Options:'
63
+ # * :existing_key (default: nil). If nil or not supplied then a block is required.
64
+ # * :new_key (required)
65
+ # * :value (required)
66
+ # @raise KeyError if the supplied existing key is not found, the new
67
+ # key exists, or the block never evaluates to true.
68
+ def insert_after(hsh, &block)
69
+ existing_key = hsh.fetch(:existing_key, nil)
70
+ new_key = hsh[:new_key]
71
+ value = hsh[:value]
72
+ if block_given?
73
+ self.insert_here(1, new_key, value, &block)
74
+ else
75
+ self.insert_here(1, new_key, value, existing_key)
76
+ end
77
+ end
78
+
79
+ # Delete any keys that are empty arrays
80
+ def remove_empties
81
+ self.keys.each do |key|
82
+ if (self[key].kind_of?(Array) && self[key].empty?) || self[key].nil?
83
+ self.delete(key)
84
+ end
85
+ end
86
+ end
87
+
88
+ # Covert snake_case keys to camelCase
89
+ def camelize_keys
90
+ self.keys.each_with_index do |key, i|
91
+ if key != key.camelize(:lower)
92
+ self.insert(i, key.camelize(:lower), self[key])
93
+ self.delete(key)
94
+ end
95
+ end
96
+ self
97
+ end
98
+
99
+ # Covert camelCase keys to snake_case
100
+ def snakeize_keys
101
+ self.keys.each_with_index do |key, i|
102
+ if key != key.underscore
103
+ self.insert(i, key.underscore, self[key])
104
+ self.delete(key)
105
+ end
106
+ end
107
+ self
108
+ end
109
+
110
+
111
+ # Prepends an entry to the front of the object.
112
+ # Note that this is slightly different from Array#unshift in that new
113
+ # entries must be added one at a time, i.e. unshift([k,v],[k,v],...) is
114
+ # not currently supported.
115
+ def unshift k,v
116
+ self.insert(0, k, v)
117
+ self
118
+ end
119
+
120
+ protected
121
+ def insert_here(where, new_key, value, existing_key=nil, &block)
122
+ idx = nil
123
+ if block_given?
124
+ self.each_with_index do |(k,v), i|
125
+ if yield(k, v)
126
+ idx = i
127
+ break
128
+ end
129
+ end
130
+ if idx.nil?
131
+ raise KeyError, "Supplied block never evaluates to true"
132
+ end
133
+ else
134
+ unless self.has_key?(existing_key)
135
+ raise KeyError, "Existing key '#{existing_key}' does not exist"
136
+ end
137
+ if self.has_key?(new_key)
138
+ raise KeyError, "Supplied new key '#{new_key}' already exists"
139
+ end
140
+ idx = self.keys.index(existing_key) + where
141
+ end
142
+ self.insert(idx, new_key, value)
143
+ self
144
+ end
145
+
146
+ end
147
+ end
@@ -0,0 +1,150 @@
1
+ require 'forwardable'
2
+
3
+ module IIIF
4
+ module HashBehaviours
5
+ extend Forwardable
6
+
7
+ # TODO:
8
+ # * reject
9
+ # * replace
10
+
11
+ def_delegators :@data, :[], :[]=, :camelize_keys, :delete, :empty?,
12
+ :fetch, :has_key?, :has_value?, :include?, :insert, :insert_after,
13
+ :insert_before, :key, :key?, :keys, :length, :member?, :shift, :size,
14
+ :snakeize_keys, :store, :unshift, :value?, :values
15
+
16
+
17
+ ###
18
+ # Methods that take a block and should return an instance (self or a new'
19
+ # instance) have been overridden to do so, rather than an'
20
+ # ActiveSupport::OrderedHash based on the internal hash
21
+
22
+ SIMPLE_SELF_RETURNERS = %w[delete_if each each_key each_value keep_if]
23
+
24
+ SIMPLE_SELF_RETURNERS.each do |method_name|
25
+ define_method(method_name) do |*arg, &block|
26
+ unless block.nil? # block_given? doesn't seem to work in this context
27
+ @data.send(method_name, *arg, &block)
28
+ return self
29
+ else
30
+ @data.send(method_name)
31
+ end
32
+ end
33
+ end
34
+
35
+ # Clear is the only method that returns self but doesn't accept a block
36
+ def clear
37
+ @data.clear
38
+ return self
39
+ end
40
+
41
+ # Returns a new instance of this class containing the contents of'
42
+ # another_obj. The argument can be any object that implements two
43
+ # methods:
44
+ #
45
+ # obj.each { |k,v| block }
46
+ # obj.has_key?
47
+ #
48
+ # If no block is specified, the value for entries with duplicate keys'
49
+ # will be those of the argument, but at the index of the original; all'
50
+ # other entries will be appended to the end.
51
+ #
52
+ # If a block is specified the value for each duplicate key is determined'
53
+ # by calling the block with the key, its value in hsh and its value in'
54
+ # another_obj.
55
+ def merge another_obj
56
+ new_instance = self.class.new
57
+ # self.clone # Would this be better? What happens to other attributes of the class?
58
+ if block_given?
59
+ self.each do |k,v|
60
+ if another_obj.has_key? k
61
+ new_instance[k] = yield(k, self[k], another_obj[k])
62
+ else
63
+ new_instance[k] = v
64
+ end
65
+ end
66
+ else
67
+ self.each { |k,v| new_instance[k] = v }
68
+ another_obj.each { |k,v| new_instance[k] = v }
69
+ end
70
+ new_instance
71
+ end
72
+
73
+ # Adds the entries from another obj to this one. The argument can be any
74
+ # object that implements two methods:
75
+ #
76
+ # obj.each { |k,v| block }
77
+ # obj.has_key?
78
+ #
79
+ # If no block is specified, the value for entries with duplicate keys'
80
+ # will be those of the argument, but at the index of the original; all'
81
+ # other entries will be appended to the end.
82
+ #
83
+ # If a block is specified the value for each duplicate key is determined'
84
+ # by calling the block with the key, its value in hsh and its value in'
85
+ # another_obj.
86
+ def merge! another_obj
87
+ if block_given?
88
+ self.each do |k,v|
89
+ if another_obj.has_key? k
90
+ self[k] = yield(k, self[k], another_obj[k])
91
+ else
92
+ self[k] = v
93
+ end
94
+ end
95
+ else
96
+ self.each { |k,v| self[k] = v }
97
+ another_obj.each { |k,v| self[k] = v }
98
+ end
99
+ self
100
+ end
101
+ alias update merge!
102
+
103
+ # Deletes entries for which the supplied block evaluates to true.
104
+ # Equivalent to #delete_if, but returns nil if there were no changes
105
+ def reject!
106
+ if block_given?
107
+ return_nil = true
108
+ @data.each do |k, v|
109
+ if yield(k, v)
110
+ @data.delete(k)
111
+ return_nil = false
112
+ end
113
+ end
114
+ return return_nil ? nil : self
115
+ else
116
+ return self.data.reject!
117
+ end
118
+ end
119
+
120
+ # Returns a new instance consisting of entries for which the block returns
121
+ # true. Not that an enumerator is not available for the OrderedHash'
122
+ # implementation
123
+ def select
124
+ new_instance = self.class.new
125
+ if block_given?
126
+ @data.each { |k,v| new_instance.data[k] = v if yield(k,v) }
127
+ end
128
+ return new_instance
129
+ end
130
+
131
+ # Deletes entries for which the supplied block evaluates to false.
132
+ # Equivalent to Hash#keep_if, but returns nil if no changes were made.
133
+ def select!
134
+ if block_given?
135
+ return_nil = true
136
+ @data.each do |k,v|
137
+ unless yield(k,v)
138
+ @data.delete(k)
139
+ return_nil = false
140
+ end
141
+ end
142
+ return nil if return_nil
143
+ end
144
+ self
145
+ end
146
+
147
+ end
148
+
149
+ end
150
+