ribbon 0.2.4 → 0.3.0

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.
data/lib/ribbon.rb CHANGED
@@ -10,10 +10,35 @@ require 'ribbon/wrapper'
10
10
  # general-purpose hash, since the <tt>[key]</tt> and <tt>[key] = value</tt>
11
11
  # methods are defined.
12
12
  #
13
- # In order to make room for as many method names as possible, Ribbon inherits
14
- # from BasicObject and doesn't implement any methods. Ribbons are designed to be
15
- # used together with Ribbon::Wrapper, which provides the methods useful for
16
- # computation.
13
+ # Ribbons support cascading references seamlessly. If you access a property that
14
+ # hasn't been set, a new ribbon is created and returned, allowing you to
15
+ # continue your calls:
16
+ #
17
+ # r = Ribbon.new
18
+ # r.a.b.c = 10
19
+ #
20
+ # Appending a <tt>!</tt> to the end of the property sets the value and returns
21
+ # the receiver:
22
+ #
23
+ # r.x!(10).y!(20).z!(30) # Equivalent to: r.x = 10; r.y = 20; r.z = 30
24
+ # => {x: 10, y: 20, z: 30}
25
+ #
26
+ # Appending a <tt>?</tt> to the end of the property allows you to peek at the
27
+ # contents of the property without creating a new ribbon if it is missing:
28
+ #
29
+ # r.p?
30
+ # => nil
31
+ #
32
+ # Seamless reference cascade using arbitrary keys are also supported via the
33
+ # <tt>[key]</tt> and <tt>[key] = value</tt> operators, which allow you to
34
+ # directly manipulate the internal hash:
35
+ #
36
+ # r[:j][:k][:l]
37
+ #
38
+ # Keep in mind that the <tt>[key]</tt> operator will always create new ribbons
39
+ # for missing properties, which is something that may not be desirable; consider
40
+ # wrapping the ribbon with a Ribbon::Wrapper in order to have better access to
41
+ # the underlying hash.
17
42
  class Ribbon < BasicObject
18
43
 
19
44
  # The internal Hash.
@@ -21,7 +46,7 @@ class Ribbon < BasicObject
21
46
  @hash ||= {}
22
47
  end
23
48
 
24
- # Initializes the new Ribbon, merging the internal hash with the given one and
49
+ # Initializes the new ribbon, merging the internal hash with the given one and
25
50
  # converting all internal objects. See Ribbon::convert_all! for details.
26
51
  def initialize(hash = {}, &block)
27
52
  __hash__.merge! hash, &block
@@ -44,23 +69,25 @@ class Ribbon < BasicObject
44
69
 
45
70
  # Handles the following cases:
46
71
  #
47
- # ribbon.method = value => ribbon[method] = value
48
- # ribbon.method! value => ribbon[method] = value
49
- # ribbon.method? => ribbon[method] ? true : false
50
72
  # ribbon.method => ribbon[method]
73
+ # ribbon.method = value => ribbon[method] = value
74
+ # ribbon.method! value => ribbon[method] = value; self
75
+ # ribbon.method? => ribbon.__hash__[method]
51
76
  def method_missing(method, *args, &block)
52
- m = method.to_s.strip.chop.strip.to_sym
77
+ m = method.to_s.strip.gsub(/[=?!]$/, '').strip.to_sym
53
78
  case method.to_s[-1]
54
- when '=', '!'
79
+ when '='
55
80
  self[m] = args.first
81
+ when '!'
82
+ self[m] = args.first; self
56
83
  when '?'
57
- __hash__[m] ? true : false
84
+ self.__hash__[m]
58
85
  else
59
86
  self[method]
60
87
  end
61
88
  end
62
89
 
63
- # If <tt>object</tt> is a Hash, converts it to a Ribbon. If it is an Array,
90
+ # If <tt>object</tt> is a hash, converts it to a ribbon. If it is an array,
64
91
  # converts any hashes inside.
65
92
  def self.convert(object)
66
93
  case object
@@ -70,7 +97,7 @@ class Ribbon < BasicObject
70
97
  end
71
98
  end
72
99
 
73
- # Converts all values in the given Ribbon.
100
+ # Converts all values in the given ribbon.
74
101
  def self.convert_all!(ribbon)
75
102
  ribbon.__hash__.each do |key, value|
76
103
  ribbon[key] = case value
@@ -81,22 +108,51 @@ class Ribbon < BasicObject
81
108
  ribbon
82
109
  end
83
110
 
84
- # Returns +true+ if the given +object+ is a Ribbon.
111
+ # Computes a simple key: value string for easy visualization of this ribbon.
112
+ #
113
+ # In +opts+ can be specified several options that customize how the string
114
+ # is generated. Among those options:
115
+ #
116
+ # [:separator] Used to separate a key/value pair. Default is <tt>': '</tt>.
117
+ # [:key] Symbol that will be sent to the key in order to obtain its
118
+ # string representation. Defaults to <tt>:to_s</tt>.
119
+ # [:value] Symbol that will be sent to the value in order to obtain its
120
+ # string representation. Defaults to <tt>:inspect</tt>.
121
+ def to_s(opts = {})
122
+ to_s_recursive opts
123
+ end
124
+
125
+ # Same as #to_s.
126
+ alias inspect to_s
127
+
128
+ # Merges the hash of +new+ with the hash of +old+, creating a new ribbon in
129
+ # the process.
130
+ def self.merge(old, new, &block)
131
+ new extract_hash_from(old).merge(extract_hash_from(ribbon), &block)
132
+ end
133
+
134
+ # Merges the hash of +new+ with the hash of +old+, modifying +old+'s hash in
135
+ # the process.
136
+ def self.merge!(old, new, &block)
137
+ extract_hash_from(old).merge! extract_hash_from(ribbon), &block
138
+ end
139
+
140
+ # Returns +true+ if the given +object+ is a ribbon.
85
141
  def self.instance?(object)
86
142
  self === object
87
143
  end
88
144
 
89
- # Returns +true+ if the given Ribbon is wrapped.
145
+ # Returns +true+ if the given ribbon is wrapped.
90
146
  def self.wrapped?(ribbon)
91
147
  Wrapper === ribbon
92
148
  end
93
149
 
94
- # Wraps a Ribbon instance in a Ribbon::Wrapper.
150
+ # Wraps a ribbon instance in a Ribbon::Wrapper.
95
151
  def self.wrap(ribbon)
96
152
  Wrapper.new ribbon
97
153
  end
98
154
 
99
- # Unwraps the +ribbon+ if it is wrapped and returns its hash. Returns nil in
155
+ # Unwraps the +ribbon+ if it is wrapped and returns its hash. Returns +nil+ in
100
156
  # any other case.
101
157
  def self.extract_hash_from(ribbon)
102
158
  ribbon = ribbon.ribbon if ::Ribbon.wrapped? ribbon
@@ -105,11 +161,30 @@ class Ribbon < BasicObject
105
161
 
106
162
  class << self
107
163
 
108
- # Wraps a Ribbon instance in a Ribbon::Wrapper.
164
+ # Wraps a ribbon instance in a Ribbon::Wrapper.
109
165
  #
110
166
  # Ribbon[ribbon].keys
111
167
  alias [] wrap
112
168
 
113
169
  end
114
170
 
171
+ private
172
+
173
+ # Computes a string value recursively for the given ribbon and all ribbons
174
+ # inside it. This implementation avoids creating additional ribbon or
175
+ # Ribbon::Wrapper objects.
176
+ def to_s_recursive(opts, ribbon = self)
177
+ ksym = opts.fetch(:key, :to_s).to_sym
178
+ vsym = opts.fetch(:value, :inspect).to_sym
179
+ separator = opts.fetch(:separator, ': ').to_s
180
+ values = ribbon.__hash__.map do |k, v|
181
+ k = k.ribbon if ::Ribbon.wrapped? k
182
+ v = v.ribbon if ::Ribbon.wrapped? v
183
+ k = if ::Ribbon.instance? k then to_s_recursive opts, k else k.send ksym end
184
+ v = if ::Ribbon.instance? v then to_s_recursive opts, v else v.send vsym end
185
+ "#{k}#{separator}#{v}"
186
+ end.join ', '
187
+ "{#{values}}"
188
+ end
189
+
115
190
  end
@@ -11,12 +11,12 @@ class Ribbon < BasicObject
11
11
  # Minor version.
12
12
  #
13
13
  # Increments denote backward-compatible changes and additions.
14
- MINOR = 2
14
+ MINOR = 3
15
15
 
16
16
  # Patch version.
17
17
  #
18
18
  # Increments denote changes in implementation.
19
- PATCH = 4
19
+ PATCH = 0
20
20
 
21
21
  # Build version.
22
22
  #
@@ -5,18 +5,37 @@ class Ribbon < BasicObject
5
5
  # Wraps around a Ribbon in order to provide general-purpose methods.
6
6
  #
7
7
  # Ribbons are designed to use methods as hash keys. In order to maximize
8
- # possibilities, many useful methods were left out of the Ribbon class and
8
+ # possibilities, many useful methods were left out of the ribbon class and
9
9
  # implemented in this wrapper class instead.
10
10
  #
11
- # One usually wraps a Ribbon on the fly in order to work with it:
11
+ # This class enables you to use ribbons like an ordinary hash. Any undefined
12
+ # methods called on a wrapped ribbon will be sent to its hash, or to the
13
+ # ribbon itself if the hash doesn't respond to the method.
12
14
  #
13
15
  # r = Ribbon.new
14
- # Ribbon[r].each { |k, v| p [k,v] }
16
+ # w = Ribbon::Wrapper.new r
15
17
  #
16
- # If a method the wrapper doesn't respond to is called, it will simply be
17
- # forwarded to the wrapped Ribbon:
18
+ # w.a.b.c
19
+ # w[:a][:b][:c]
20
+ #
21
+ # Wrapped ribbons talk directly to their ribbon's hash:
22
+ #
23
+ # w[:k]
24
+ # => nil
25
+ #
26
+ # However, keep in mind that the wrapped hash may contain other ribbons,
27
+ # which may not be wrapped:
28
+ #
29
+ # w.a.b.c[:d]
30
+ # => {}
31
+ #
32
+ # You can automatically wrap and unwrap all ribbons inside the wrapped one:
33
+ #
34
+ # w.wrap_all!
35
+ # w.unwrap_all!
36
+ #
37
+ # The wrapped ribbon receives all undefined methods that hashes won't take:
18
38
  #
19
- # w = Ribbon[r]
20
39
  # w.x = 10
21
40
  # w.ribbon.x
22
41
  # => 10
@@ -52,7 +71,7 @@ class Ribbon < BasicObject
52
71
  self.ribbon = ribbon
53
72
  end
54
73
 
55
- # Returns the hash of the wrapped Ribbon.
74
+ # Returns the hash of the wrapped ribbon.
56
75
  def hash
57
76
  ribbon.__hash__
58
77
  end
@@ -64,28 +83,22 @@ class Ribbon < BasicObject
64
83
  else ribbon end.__send__ method, *args, &block
65
84
  end
66
85
 
67
- # Merges the hash of this wrapped Ribbon with the given +ribbon+, which can
68
- # be a Ribbon::Wrapper, a Ribbon or a hash.
69
- #
70
- # This method returns a new hash.
71
- def merge(ribbon, &block)
72
- hash.merge Ribbon.extract_hash_from(ribbon), &block
86
+ # Wraps all ribbons contained by this wrapper's ribbon.
87
+ def wrap_all!
88
+ wrap_all_recursive!
73
89
  end
74
90
 
75
- # Merges the hash of this wrapped Ribbon with the given +ribbon+, which can
76
- # be a Ribbon::Wrapper, a Ribbon or a hash.
77
- #
78
- # This method modifies the hash of this wrapped Ribbon.
79
- def merge!(ribbon, &block)
80
- hash.merge! Ribbon.extract_hash_from(ribbon), &block
91
+ # Unwraps all ribbons contained by this wrapper's ribbon.
92
+ def unwrap_all!
93
+ unwrap_all_recursive!
81
94
  end
82
95
 
83
- # Converts the wrapped Ribbon and all Ribbons inside into hashes.
96
+ # Converts the wrapped Ribbon and all ribbons inside into hashes.
84
97
  def to_hash
85
98
  to_hash_recursive
86
99
  end
87
100
 
88
- # Converts the wrapped Ribbon to a hash and serializes it with YAML. To get
101
+ # Converts the wrapped ribbon to a hash and serializes it with YAML. To get
89
102
  # a Ribbon back from the serialized hash, you can simply load the hash and
90
103
  # pass it to the Ribbon constructor:
91
104
  #
@@ -94,46 +107,16 @@ class Ribbon < BasicObject
94
107
  to_hash.to_yaml
95
108
  end
96
109
 
97
- # Computes a simple key: value string for easy visualization of the wrapped
98
- # Ribbon.
99
- #
100
- # In +opts+ can be specified several options that customize how the string
101
- # is generated. Among those options:
102
- #
103
- # [:separator] Used to separate a key/value pair. Default is <tt>': '</tt>.
104
- # [:key] Symbol that will be sent to the key in order to obtain its
105
- # string representation. Defaults to <tt>:to_s</tt>.
106
- # [:value] Symbol that will be sent to the value in order to obtain its
107
- # string representation. Defaults to <tt>:inspect</tt>.
108
- def to_s(opts = {})
109
- to_s_recursive opts, ribbon
110
+ # Delegates to Ribbon#to_s.
111
+ def to_s
112
+ ribbon.to_s
110
113
  end
111
114
 
112
- # Same as #to_s.
113
- alias :inspect :to_s
114
-
115
115
  private
116
116
 
117
- # Computes a string value recursively for the given Ribbon and all Ribbons
118
- # inside it. This implementation avoids creating additional Ribbon or
119
- # Ribbon::Wrapper objects.
120
- def to_s_recursive(opts, ribbon)
121
- ksym = opts.fetch(:key, :to_s).to_sym
122
- vsym = opts.fetch(:value, :inspect).to_sym
123
- separator = opts.fetch(:separator, ': ').to_s
124
- values = ribbon.__hash__.map do |k, v|
125
- k = k.ribbon if Ribbon.wrapped? k
126
- v = v.ribbon if Ribbon.wrapped? v
127
- k = if Ribbon.instance? k then to_s_recursive opts, k else k.send ksym end
128
- v = if Ribbon.instance? v then to_s_recursive opts, v else v.send vsym end
129
- "#{k}#{separator}#{v}"
130
- end.join ', '
131
- "{#{values}}"
132
- end
133
-
134
- # Converts the wrapped Ribbon and all Ribbons inside into hashes using
135
- # recursion. This implementation avoids the creation of additional Ribbon or
136
- # Ribbon::Wrapper objects.
117
+ # Converts the wrapped ribbon and all ribbons inside into hashes using
118
+ # recursion. This implementation avoids the creation of additional ribbon or
119
+ # wrapper objects.
137
120
  def to_hash_recursive(ribbon = self.ribbon)
138
121
  {}.tap do |hash|
139
122
  ribbon.__hash__.each do |key, value|
@@ -146,5 +129,29 @@ class Ribbon < BasicObject
146
129
  end
147
130
  end
148
131
 
132
+ # Recursively wraps all ribbons inside. This implementation avoids the
133
+ # creation of additional ribbon or wrapper objects.
134
+ def wrap_all_recursive!(wrapper = self)
135
+ wrapper.hash.each do |key, value|
136
+ wrapper.hash[key] = case value
137
+ when ::Ribbon then wrap_all_recursive! ::Ribbon::Wrapper[value]
138
+ else value
139
+ end
140
+ end
141
+ wrapper
142
+ end
143
+
144
+ # Recursively unwraps all ribbons inside. This implementation avoids the
145
+ # creation of additional ribbon or wrapper objects.
146
+ def unwrap_all_recursive!(ribbon = self)
147
+ ribbon.__hash__.each do |key, value|
148
+ ribbon[key] = case value
149
+ when ::Ribbon::Wrapper then unwrap_all_recursive! value.ribbon
150
+ else value
151
+ end
152
+ end
153
+ ribbon
154
+ end
155
+
149
156
  end
150
157
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ribbon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2011-12-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rookie
16
- requirement: &8171640 !ruby/object:Gem::Requirement
16
+ requirement: &15659380 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *8171640
24
+ version_requirements: *15659380
25
25
  description: Ruby Object Notation. Inspired by JSON and OpenStruct.
26
26
  email: matheus.a.m.moreira@gmail.com
27
27
  executables: []
@@ -54,7 +54,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
54
  version: '0'
55
55
  segments:
56
56
  - 0
57
- hash: -1602149804169223736
57
+ hash: 2946347091620539582
58
58
  required_rubygems_version: !ruby/object:Gem::Requirement
59
59
  none: false
60
60
  requirements:
@@ -63,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
63
  version: '0'
64
64
  segments:
65
65
  - 0
66
- hash: -1602149804169223736
66
+ hash: 2946347091620539582
67
67
  requirements: []
68
68
  rubyforge_project:
69
69
  rubygems_version: 1.8.10