ribbon 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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