representative 0.2.3 → 0.2.4

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/README.markdown CHANGED
@@ -1,16 +1,16 @@
1
1
  Representative
2
2
  ==============
3
3
 
4
- "Representative" makes it easier to create XML representations of your Ruby objects.
5
- It works best when you want the XML to roughly follow the object structure,
6
- but still have complete control of the result.
4
+ "Representative" makes it easier to create XML or JSON representations of your Ruby objects.
7
5
 
8
- Example
9
- -------
6
+ It works best when you want the output to roughly follow the object structure, but still want complete control of the result.
7
+
8
+ Generating XML
9
+ --------------
10
10
 
11
11
  Given a Ruby data-structure:
12
12
 
13
- books = [
13
+ @books = [
14
14
  OpenStruct.new(
15
15
  :title => "Sailing for old dogs",
16
16
  :authors => ["Jim Watson"],
@@ -34,13 +34,13 @@ Given a Ruby data-structure:
34
34
  )
35
35
  ]
36
36
 
37
- Representative::Xml can be used to generate XML, in a declarative style:
37
+ Representative::Xml can be used to generate XML:
38
38
 
39
39
  xml = Builder::XmlMarkup.new(:indent => 2)
40
40
 
41
41
  Representative::Xml.new(xml) do |r|
42
42
 
43
- r.list_of :books, books do
43
+ r.list_of :books, @books do
44
44
  r.element :title
45
45
  r.list_of :authors
46
46
  r.element :published do
@@ -53,7 +53,7 @@ Representative::Xml can be used to generate XML, in a declarative style:
53
53
 
54
54
  puts xml.target!
55
55
 
56
- The resulting XML looks like this:
56
+ which produces:
57
57
 
58
58
  <books type="array">
59
59
  <book>
@@ -88,12 +88,65 @@ The resulting XML looks like this:
88
88
 
89
89
  Notice that:
90
90
 
91
- - The structure of the XML mirrors the structure described by the nested Ruby blocks.
91
+ - The structure of the output mirrors the structure described by the nested Ruby blocks.
92
92
  - Representative walks the object-graph for you.
93
93
  - Using `list_of` for a collection attribute generates an "array" element, which plays nicely
94
94
  with most Ruby XML-to-hash converters.
95
95
  - Where a named object-attribute is nil, you get an empty element.
96
96
 
97
+ Generating JSON
98
+ ---------------
99
+
100
+ Representative::Json can be used to generate JSON, using exactly the same DSL:
101
+
102
+ json = Representative::Json.new do |r|
103
+
104
+ r.list_of :books, @books do
105
+ r.element :title
106
+ r.list_of :authors
107
+ r.element :published do
108
+ r.element :by
109
+ r.element :year
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ puts json.to_s
116
+
117
+ producing:
118
+
119
+ [
120
+ {
121
+ "title": "Sailing for old dogs",
122
+ "authors": [
123
+ "Jim Watson"
124
+ ],
125
+ "published": {
126
+ "by": "Credulous Print",
127
+ "year": 1994
128
+ }
129
+ },
130
+ {
131
+ "title": "On the horizon",
132
+ "authors": [
133
+ "Zoe Primpton",
134
+ "Stan Ford"
135
+ ],
136
+ "published": {
137
+ "by": "McGraw-Hill",
138
+ "year": 2005
139
+ }
140
+ },
141
+ {
142
+ "title": "The Little Blue Book of VHS Programming",
143
+ "authors": [
144
+ "Henry Nelson"
145
+ ],
146
+ "published": null
147
+ }
148
+ ]
149
+
97
150
  Installation
98
151
  ------------
99
152
 
data/Rakefile CHANGED
@@ -17,6 +17,8 @@ require "spec/rake/spectask"
17
17
  Spec::Rake::SpecTask.new(:spec) do |spec|
18
18
  spec.libs << 'lib' << 'spec'
19
19
  spec.spec_files = FileList['spec/**/*_spec.rb']
20
+ spec.spec_opts << "-Du"
21
+ spec.spec_opts << "--color"
20
22
  end
21
23
 
22
24
  task :default => :spec
@@ -0,0 +1,44 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require "rubygems"
4
+ require "representative/json"
5
+ require "ostruct"
6
+
7
+ @books = [
8
+ OpenStruct.new(
9
+ :title => "Sailing for old dogs",
10
+ :authors => ["Jim Watson"],
11
+ :published => OpenStruct.new(
12
+ :by => "Credulous Print",
13
+ :year => 1994
14
+ )
15
+ ),
16
+ OpenStruct.new(
17
+ :title => "On the horizon",
18
+ :authors => ["Zoe Primpton", "Stan Ford"],
19
+ :published => OpenStruct.new(
20
+ :by => "McGraw-Hill",
21
+ :year => 2005
22
+ )
23
+ ),
24
+ OpenStruct.new(
25
+ :title => "The Little Blue Book of VHS Programming",
26
+ :authors => ["Henry Nelson"],
27
+ :rating => "****"
28
+ )
29
+ ]
30
+
31
+ json = Representative::Json.new do |r|
32
+
33
+ r.list_of :books, @books do
34
+ r.element :title
35
+ r.list_of :authors
36
+ r.element :published do
37
+ r.element :by
38
+ r.element :year
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ puts json.to_s
data/examples/xml_demo.rb CHANGED
@@ -4,7 +4,7 @@ require "rubygems"
4
4
  require "representative/xml"
5
5
  require "ostruct"
6
6
 
7
- books = [
7
+ @books = [
8
8
  OpenStruct.new(
9
9
  :title => "Sailing for old dogs",
10
10
  :authors => ["Jim Watson"],
@@ -29,8 +29,10 @@ books = [
29
29
  ]
30
30
 
31
31
  xml = Builder::XmlMarkup.new(:indent => 2)
32
+
32
33
  Representative::Xml.new(xml) do |r|
33
- r.list_of :books, books do
34
+
35
+ r.list_of :books, @books do
34
36
  r.element :title
35
37
  r.list_of :authors
36
38
  r.element :published do
@@ -38,6 +40,7 @@ Representative::Xml.new(xml) do |r|
38
40
  r.element :year
39
41
  end
40
42
  end
43
+
41
44
  end
42
45
 
43
46
  puts xml.target!
@@ -0,0 +1,62 @@
1
+ require "representative/object_inspector"
2
+
3
+ module Representative
4
+
5
+ class Base
6
+
7
+ def initialize(subject = nil, options = {})
8
+ @subjects = [subject]
9
+ @inspector = options[:inspector] || ObjectInspector.new
10
+ end
11
+
12
+ # Return the current "subject" of representation.
13
+ #
14
+ # This object will provide element values where they haven't been
15
+ # explicitly provided.
16
+ #
17
+ def current_subject
18
+ @subjects.last
19
+ end
20
+
21
+ alias :subject :current_subject
22
+
23
+ # Evaluate a block with a specified object as #subject.
24
+ #
25
+ def representing(new_subject, &block)
26
+ with_subject(resolve_value(new_subject), &block)
27
+ end
28
+
29
+ protected
30
+
31
+ def with_subject(subject)
32
+ @subjects.push(subject)
33
+ begin
34
+ yield subject
35
+ ensure
36
+ @subjects.pop
37
+ end
38
+ end
39
+
40
+ def resolve_value(value_generator)
41
+ if value_generator == :self
42
+ current_subject
43
+ elsif value_generator.respond_to?(:to_proc)
44
+ value_generator.to_proc.call(current_subject) unless current_subject.nil?
45
+ else
46
+ value_generator
47
+ end
48
+ end
49
+
50
+ def resolve_attributes(attributes)
51
+ if attributes
52
+ attributes.inject({}) do |resolved, (name, value_generator)|
53
+ resolved_value = resolve_value(value_generator)
54
+ resolved[name.to_s.dasherize] = resolved_value unless resolved_value.nil?
55
+ resolved
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,122 @@
1
+ require "active_support/core_ext/array"
2
+ require "representative/base"
3
+ require "json"
4
+
5
+ module Representative
6
+
7
+ class Json < Base
8
+
9
+ def initialize(subject = nil, options = {})
10
+ super(subject, options)
11
+ @buffer = ""
12
+ @indent_level = 0
13
+ now_at :beginning_of_buffer
14
+ yield self if block_given?
15
+ end
16
+
17
+ def element(name, *args, &block)
18
+
19
+ metadata = @inspector.get_metadata(current_subject, name)
20
+ attributes = args.extract_options!.merge(metadata)
21
+
22
+ subject_of_element = if args.empty?
23
+ lambda do |subject|
24
+ @inspector.get_value(current_subject, name)
25
+ end
26
+ else
27
+ args.shift
28
+ end
29
+
30
+ raise ArgumentError, "too many arguments" unless args.empty?
31
+
32
+ label(name)
33
+ value(subject_of_element, &block)
34
+
35
+ end
36
+
37
+ def list_of(name, *args, &block)
38
+ list_subject = args.empty? ? name : args.shift
39
+ items = resolve_value(list_subject)
40
+ label(name)
41
+ inside "[", "]" do
42
+ items.each do |item|
43
+ new_item
44
+ value(item, &block)
45
+ end
46
+ end
47
+ end
48
+
49
+ def value(subject)
50
+ representing(subject) do
51
+ if block_given? && !current_subject.nil?
52
+ inside "{", "}" do
53
+ yield current_subject
54
+ end
55
+ else
56
+ emit(current_subject.to_json)
57
+ end
58
+ end
59
+ now_at :end_of_item
60
+ end
61
+
62
+ def comment(text)
63
+ new_item
64
+ emit("// #{text}")
65
+ now_at :end_of_comment
66
+ end
67
+
68
+ def to_json
69
+ @buffer + "\n"
70
+ end
71
+
72
+ def to_s
73
+ to_json
74
+ end
75
+
76
+ private
77
+
78
+ def emit(s)
79
+ @buffer << s
80
+ end
81
+
82
+ def indentation
83
+ (" " * @indent_level)
84
+ end
85
+
86
+ def label(name)
87
+ return false if @indent_level == 0
88
+ new_item
89
+ emit("#{name.to_s.to_json}: ")
90
+ end
91
+
92
+ def new_item
93
+ emit(",") if at? :end_of_item
94
+ emit("\n") unless at? :beginning_of_buffer
95
+ emit(indentation)
96
+ @pending_comma = ","
97
+ end
98
+
99
+ def inside(opening_char, closing_char)
100
+ emit(opening_char)
101
+ @indent_level += 1
102
+ now_at :beginning_of_block
103
+ yield
104
+ @indent_level -= 1
105
+ emit("\n#{indentation}") unless at? :beginning_of_block
106
+ emit(closing_char)
107
+ now_at :end_of_item
108
+ end
109
+
110
+ def now_at(state)
111
+ @state = state
112
+ end
113
+
114
+ def at?(state)
115
+ @state == state
116
+ end
117
+
118
+ end
119
+
120
+ JSON = Json
121
+
122
+ end
@@ -1,3 +1,3 @@
1
1
  module Representative
2
- VERSION = "0.2.3".freeze
2
+ VERSION = "0.2.4".freeze
3
3
  end
@@ -1,14 +1,14 @@
1
1
  require "active_support/core_ext/array"
2
2
  require "active_support/core_ext/string"
3
3
  require "builder"
4
+ require "representative/base"
4
5
  require "representative/empty"
5
- require "representative/object_inspector"
6
6
 
7
7
  module Representative
8
8
 
9
9
  # Easily generate XML while traversing an object-graph.
10
10
  #
11
- class Xml
11
+ class Xml < Base
12
12
 
13
13
  # Create an XML-generating Representative. The first argument should be an instance of
14
14
  # Builder::XmlMarkup (or something that implements it's interface). The second argument
@@ -16,28 +16,10 @@ module Representative
16
16
  #
17
17
  def initialize(xml_builder, subject = nil, options = {})
18
18
  @xml = xml_builder
19
- @subjects = [subject]
20
- @inspector = options[:inspector] || ObjectInspector.new
19
+ super(subject, options)
21
20
  yield self if block_given?
22
21
  end
23
22
 
24
- # Return the current "subject" of representation.
25
- #
26
- # This object will provide element values where they haven't been
27
- # explicitly provided.
28
- #
29
- def current_subject
30
- @subjects.last
31
- end
32
-
33
- alias :subject :current_subject
34
-
35
- # Evaluate a block with a specified object as #subject.
36
- #
37
- def representing(new_subject, &block)
38
- with_subject(resolve_value(new_subject), &block)
39
- end
40
-
41
23
  # Generate an element.
42
24
  #
43
25
  # With two arguments, it generates an element with the specified text content.
@@ -156,37 +138,6 @@ module Representative
156
138
  @xml.comment!(text)
157
139
  end
158
140
 
159
- private
160
-
161
- def with_subject(subject)
162
- @subjects.push(subject)
163
- begin
164
- yield subject
165
- ensure
166
- @subjects.pop
167
- end
168
- end
169
-
170
- def resolve_value(value_generator)
171
- if value_generator == :self
172
- current_subject
173
- elsif value_generator.respond_to?(:to_proc)
174
- value_generator.to_proc.call(current_subject) unless current_subject.nil?
175
- else
176
- value_generator
177
- end
178
- end
179
-
180
- def resolve_attributes(attributes)
181
- if attributes
182
- attributes.inject({}) do |resolved, (name, value_generator)|
183
- resolved_value = resolve_value(value_generator)
184
- resolved[name.to_s.dasherize] = resolved_value unless resolved_value.nil?
185
- resolved
186
- end
187
- end
188
- end
189
-
190
141
  end
191
142
 
192
143
  end
@@ -0,0 +1,224 @@
1
+ require 'spec_helper'
2
+
3
+ require "representative/json"
4
+
5
+ describe Representative::Json do
6
+
7
+ def r
8
+ @representative ||= Representative::Json.new(@subject)
9
+ end
10
+
11
+ def resulting_json
12
+ r.to_json
13
+ end
14
+
15
+ describe "at the top level" do
16
+
17
+ describe "#element" do
18
+
19
+ describe "with an explicit String value" do
20
+
21
+ it "outputs the value as JSON" do
22
+ r.element :name, "Fred"
23
+ resulting_json.should == %{"Fred"\n}
24
+ end
25
+
26
+ end
27
+
28
+ describe "with an explicit integer value" do
29
+
30
+ it "outputs the value as JSON" do
31
+ r.element :age, 36
32
+ resulting_json.should == "36\n"
33
+ end
34
+
35
+ end
36
+
37
+ describe "with a nil value" do
38
+
39
+ it "generates null" do
40
+ r.element :flavour, nil
41
+ resulting_json.should == "null\n"
42
+ end
43
+
44
+ describe "and a block" do
45
+
46
+ it "generates null" do
47
+ r.element :book, nil do
48
+ r.element :author
49
+ end
50
+ resulting_json.should == "null\n"
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ describe "without an explicit value" do
58
+
59
+ it "extracts the value from the current subject" do
60
+ @author = OpenStruct.new(:name => "Fred", :age => 36)
61
+ r.representing(@author) do
62
+ r.element :name
63
+ end
64
+ resulting_json.should == %{"Fred"\n}
65
+ end
66
+
67
+ end
68
+
69
+ describe "with a block" do
70
+
71
+ it "outputs an object" do
72
+ r.element :something, Object.new do
73
+ end
74
+ resulting_json.should == "{}\n"
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ describe "#list_of" do
82
+
83
+ describe "with an explicit array value" do
84
+
85
+ it "outputs the array as JSON" do
86
+ r.list_of :names, %w(Hewey Dewey Louie)
87
+ resulting_json.should == undent(<<-JSON)
88
+ [
89
+ "Hewey",
90
+ "Dewey",
91
+ "Louie"
92
+ ]
93
+ JSON
94
+ end
95
+
96
+ end
97
+
98
+ describe "without an explicit value" do
99
+
100
+ it "extracts the value from the current subject" do
101
+ @donald = OpenStruct.new(:nephews => %w(Hewey Dewey Louie))
102
+ r.element(:duck, @donald) do
103
+ r.list_of :nephews
104
+ end
105
+ resulting_json.should == undent(<<-JSON)
106
+ {
107
+ "nephews": [
108
+ "Hewey",
109
+ "Dewey",
110
+ "Louie"
111
+ ]
112
+ }
113
+ JSON
114
+ end
115
+
116
+ end
117
+
118
+ describe "with a block" do
119
+
120
+ it "generates an object for each array element" do
121
+ @authors = [
122
+ OpenStruct.new(:name => "Hewey", :age => 3),
123
+ OpenStruct.new(:name => "Dewey", :age => 4),
124
+ OpenStruct.new(:name => "Louie", :age => 5)
125
+ ]
126
+ r.list_of :authors, @authors do
127
+ r.element :name
128
+ r.element :age
129
+ end
130
+ resulting_json.should == undent(<<-JSON)
131
+ [
132
+ {
133
+ "name": "Hewey",
134
+ "age": 3
135
+ },
136
+ {
137
+ "name": "Dewey",
138
+ "age": 4
139
+ },
140
+ {
141
+ "name": "Louie",
142
+ "age": 5
143
+ }
144
+ ]
145
+ JSON
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
152
+ describe "#comment" do
153
+
154
+ it "inserts a comment" do
155
+ r.comment "now pay attention"
156
+ resulting_json.should == undent(<<-JSON)
157
+ // now pay attention
158
+ JSON
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+
165
+ describe "within an element block" do
166
+
167
+ describe "#element" do
168
+
169
+ it "generates labelled values" do
170
+ r.element :author, Object.new do
171
+ r.element :name, "Fred"
172
+ r.element :age, 36
173
+ end
174
+ resulting_json.should == undent(<<-JSON)
175
+ {
176
+ "name": "Fred",
177
+ "age": 36
178
+ }
179
+ JSON
180
+ end
181
+
182
+ describe "without an explicit value" do
183
+
184
+ it "extracts the value from the current subject" do
185
+ @author = OpenStruct.new(:name => "Fred", :age => 36)
186
+ r.element :author, @author do
187
+ r.element :name
188
+ r.element :age
189
+ end
190
+ resulting_json.should == undent(<<-JSON)
191
+ {
192
+ "name": "Fred",
193
+ "age": 36
194
+ }
195
+ JSON
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+
202
+ describe "#comment" do
203
+
204
+ it "inserts a comment" do
205
+ @author = OpenStruct.new(:name => "Fred", :age => 36)
206
+ r.element :author, @author do
207
+ r.element :name
208
+ r.comment "age is irrelevant"
209
+ r.element :age
210
+ end
211
+ resulting_json.should == undent(<<-JSON)
212
+ {
213
+ "name": "Fred",
214
+ // age is irrelevant
215
+ "age": 36
216
+ }
217
+ JSON
218
+ end
219
+
220
+ end
221
+
222
+ end
223
+
224
+ end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  require "representative/xml"
4
4
 
data/spec/spec_helper.rb CHANGED
@@ -8,3 +8,12 @@ require "rubygems"
8
8
  Spec::Runner.configure do |config|
9
9
 
10
10
  end
11
+
12
+ def undent(raw)
13
+ if raw =~ /\A( +)/
14
+ indent = $1
15
+ raw.gsub(/^#{indent}/, '').gsub(/ +$/, '')
16
+ else
17
+ raw
18
+ end
19
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 3
9
- version: 0.2.3
8
+ - 4
9
+ version: 0.2.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Mike Williams
@@ -40,11 +40,14 @@ extensions: []
40
40
  extra_rdoc_files: []
41
41
 
42
42
  files:
43
+ - lib/representative/base.rb
43
44
  - lib/representative/empty.rb
45
+ - lib/representative/json.rb
44
46
  - lib/representative/object_inspector.rb
45
47
  - lib/representative/version.rb
46
48
  - lib/representative/xml.rb
47
49
  - lib/representative.rb
50
+ - examples/json_demo.rb
48
51
  - examples/xml_demo.rb
49
52
  - README.markdown
50
53
  - LICENSE
@@ -79,6 +82,7 @@ signing_key:
79
82
  specification_version: 3
80
83
  summary: Builds XML representations of your Ruby objects
81
84
  test_files:
85
+ - spec/representative/json_spec.rb
82
86
  - spec/representative/xml_spec.rb
83
87
  - spec/spec_helper.rb
84
88
  - Rakefile