yaml-write-stream 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c3b944beb4d212490ae2f8900f090869bbbf1783
4
+ data.tar.gz: 3d69474c75ca853562dd964956b778220335fbf4
5
+ SHA512:
6
+ metadata.gz: f95dcf646a2930f5459fd021eb0751bca932315269977934ef5e444cc59396091a6d68fa57c527fe86e921dad687661ef2690b99514f691c071e678cdae44c6e
7
+ data.tar.gz: 075277ab39f967c205400d8a12ca808e81a67df3b375bdd5d63ce45cdc8762e1f8ddb0fa52df57290aa823b4bab35ad809c5869892738a6ad39a17561e882bc2
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'pry', '~> 0.9.0'
7
+ gem 'pry-nav'
8
+ gem 'rake'
9
+ end
10
+
11
+ group :test do
12
+ gem 'rspec'
13
+ gem 'rr'
14
+ end
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ == 1.0.0
2
+
3
+ * Birthday!
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ yaml-write-stream
2
+ =================
3
+
4
+ [![Build Status](https://travis-ci.org/camertron/yaml-write-stream.svg?branch=master)](http://travis-ci.org/camertron/yaml-write-stream)
5
+
6
+ An easy, streaming way to generate YAML.
7
+
8
+ ## Installation
9
+
10
+ `gem install yaml-write-stream`
11
+
12
+ ## Usage
13
+
14
+ ```ruby
15
+ require 'yaml-write-stream'
16
+ ```
17
+
18
+ ### Examples for the Impatient
19
+
20
+ There are two types of YAML write stream: one that uses blocks and `yield` to delimit arrays (sequences) and objects (maps), and one that's purely stateful. Here are two examples that produce the same output:
21
+
22
+ Yielding:
23
+
24
+ ```ruby
25
+ stream = StringIO.new
26
+ YamlWriteStream.from_stream(stream) do |writer|
27
+ writer.write_map do |map_writer|
28
+ map_writer.write_key_value('foo', 'bar')
29
+ map_writer.write_sequence('baz') do |seq_writer|
30
+ seq_writer.write_element('goo')
31
+ end
32
+ end
33
+ end
34
+ ```
35
+
36
+ Stateful:
37
+
38
+ ```ruby
39
+ stream = StringIO.new
40
+ writer = YamlWriteStream.from_stream(stream)
41
+ writer.write_map
42
+ writer.write_key_value('foo', 'bar')
43
+ writer.write_sequence('baz')
44
+ writer.write_element('goo')
45
+ writer.close # automatically adds closing punctuation for all nested types
46
+ ```
47
+
48
+ Output:
49
+
50
+ ```ruby
51
+ stream.string # => foo: bar\nbaz:\n- goo\n
52
+ ```
53
+
54
+ ### Yielding Writers
55
+
56
+ As far as yielding writers go, the example above contains everything you need. The stream will be automatically closed when the outermost block terminates.
57
+
58
+ ### Stateful Writers
59
+
60
+ Stateful writers have a number of additional methods:
61
+
62
+ ```ruby
63
+ stream = StringIO.new
64
+ writer = YamlWriteStream.from_stream(stream)
65
+ writer.write_map
66
+
67
+ writer.in_map? # => true, currently writing a map
68
+ writer.in_sequence? # => false, not currently writing a sequence
69
+ writer.eos? # => false, the stream is open and the outermost map hasn't been closed yet
70
+
71
+ writer.close_map # explicitly close the current map
72
+ writer.eos? # => true, the outermost map has been closed
73
+
74
+ writer.write_sequence # => raises YamlWriteStream::EndOfStreamError
75
+ writer.close_sequence # => raises YamlWriteStream::NotInArrayError
76
+
77
+ writer.closed? # => false, the stream is still open
78
+ writer.close # close the stream
79
+ writer.closed? # => true, the stream has been closed
80
+ ```
81
+
82
+ ### Writing to a File
83
+
84
+ YamlWriteStream also supports streaming to a file via the `open` method:
85
+
86
+ Yielding:
87
+
88
+ ```ruby
89
+ YamlWriteStream.open('path/to/file.yml') do |writer|
90
+ writer.write_map do |map_writer|
91
+ ...
92
+ end
93
+ end
94
+ ```
95
+
96
+ Stateful:
97
+
98
+ ```ruby
99
+ writer = YamlWriteStream.open('path/to/file.yml')
100
+ writer.write_map
101
+ ...
102
+ writer.close
103
+ ```
104
+
105
+ ## Requirements
106
+
107
+ Only Ruby 1.9 or greater is supported (requires the Psych emitter).
108
+
109
+ ## Running Tests
110
+
111
+ `bundle exec rake` should do the trick. Alternatively you can run `bundle exec rspec`, which does the same thing.
112
+
113
+ ## Authors
114
+
115
+ * Cameron C. Dutro: http://github.com/camertron
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
4
+
5
+ require 'bundler'
6
+ require 'rspec/core/rake_task'
7
+ require 'rubygems/package_task'
8
+
9
+ require './lib/yaml-write-stream'
10
+
11
+ Bundler::GemHelper.install_tasks
12
+
13
+ task :default => :spec
14
+
15
+ desc 'Run specs'
16
+ RSpec::Core::RakeTask.new do |t|
17
+ t.pattern = './spec/**/*_spec.rb'
18
+ end
@@ -0,0 +1,183 @@
1
+ # encoding: UTF-8
2
+
3
+ class YamlWriteStream
4
+ class NotInMapError < StandardError; end
5
+ class NotInSequenceError < StandardError; end
6
+ class EndOfStreamError < StandardError; end
7
+
8
+ class StatefulWriter
9
+ attr_reader :emitter, :stream, :stack, :closed, :first
10
+ alias :closed? :closed
11
+
12
+ def initialize(emitter, stream)
13
+ @emitter = emitter
14
+ @stream = stream
15
+ @stack = []
16
+ @closed = false
17
+ after_initialize
18
+ @first = true
19
+ end
20
+
21
+ def after_initialize
22
+ @first = true
23
+ end
24
+
25
+ def close
26
+ # psych gets confused if you open a file and don't at least
27
+ # pretend to write something
28
+ write_scalar('') if first
29
+
30
+ until stack.empty?
31
+ if in_map?
32
+ close_map
33
+ else
34
+ close_sequence
35
+ end
36
+ end
37
+
38
+ emitter.end_document(true)
39
+ emitter.end_stream
40
+ stream.close
41
+ @closed = true
42
+ nil
43
+ end
44
+
45
+ def write_map(*args)
46
+ check_eos
47
+ @first = false
48
+ current.write_map(*args) if current
49
+ stack.push(StatefulMappingWriter.new(emitter, stream))
50
+ end
51
+
52
+ def write_sequence(*args)
53
+ check_eos
54
+ @first = false
55
+ current.write_sequence(*args) if current
56
+ stack.push(StatefulSequenceWriter.new(emitter, stream))
57
+ end
58
+
59
+ def write_key_value(*args)
60
+ check_eos
61
+ @first = false
62
+ current.write_key_value(*args)
63
+ end
64
+
65
+ def write_element(*args)
66
+ check_eos
67
+ @first = false
68
+ current.write_element(*args)
69
+ end
70
+
71
+ def eos?
72
+ closed? || (!first && stack.size == 0)
73
+ end
74
+
75
+ def in_map?
76
+ current ? current.is_map? : false
77
+ end
78
+
79
+ def in_sequence?
80
+ current ? current.is_sequence? : false
81
+ end
82
+
83
+ def close_map
84
+ if in_map?
85
+ stack.pop.close
86
+ else
87
+ raise NotInMapError, 'not currently writing a map.'
88
+ end
89
+ end
90
+
91
+ def close_sequence
92
+ if in_sequence?
93
+ stack.pop.close
94
+ else
95
+ raise NotInSequenceError, 'not currently writing an sequence.'
96
+ end
97
+ end
98
+
99
+ protected
100
+
101
+ def check_eos
102
+ if eos?
103
+ raise EndOfStreamError, 'end of stream.'
104
+ end
105
+ end
106
+
107
+ def current
108
+ stack.last
109
+ end
110
+
111
+ def write_scalar(value)
112
+ # value, anchor, tag, plain, quoted, style
113
+ emitter.scalar(
114
+ value, nil, nil, true, false, Psych::Nodes::Scalar::ANY
115
+ )
116
+ end
117
+ end
118
+
119
+ class StatefulMappingWriter < StatefulWriter
120
+ def after_initialize
121
+ # anchor, tag, implicit, style
122
+ emitter.start_mapping(
123
+ nil, nil, true, Psych::Nodes::Sequence::BLOCK
124
+ )
125
+ end
126
+
127
+ def write_map(key)
128
+ write_scalar(key)
129
+ end
130
+
131
+ def write_sequence(key)
132
+ write_scalar(key)
133
+ end
134
+
135
+ def write_key_value(key, value)
136
+ write_scalar(key)
137
+ write_scalar(value)
138
+ end
139
+
140
+ def close
141
+ emitter.end_mapping
142
+ end
143
+
144
+ def is_map?
145
+ true
146
+ end
147
+
148
+ def is_sequence?
149
+ false
150
+ end
151
+ end
152
+
153
+ class StatefulSequenceWriter < StatefulWriter
154
+ def after_initialize
155
+ # anchor, tag, implicit, style
156
+ emitter.start_sequence(
157
+ nil, nil, true, Psych::Nodes::Sequence::BLOCK
158
+ )
159
+ end
160
+
161
+ def write_element(element)
162
+ write_scalar(element)
163
+ end
164
+
165
+ def write_map
166
+ end
167
+
168
+ def write_sequence
169
+ end
170
+
171
+ def close
172
+ emitter.end_sequence
173
+ end
174
+
175
+ def is_map?
176
+ false
177
+ end
178
+
179
+ def is_sequence?
180
+ true
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+
3
+ class YamlWriteStream
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,81 @@
1
+ # encoding: UTF-8
2
+
3
+ class YamlWriteStream
4
+ class YieldingWriter
5
+ attr_reader :emitter, :stream, :first
6
+
7
+ def initialize(emitter, stream)
8
+ @emitter = emitter
9
+ @stream = stream
10
+ @first = true
11
+ end
12
+
13
+ def close
14
+ # psych gets confused if you open a file and don't at least
15
+ # pretend to write something
16
+ write_scalar('') if first
17
+ emitter.end_document(true)
18
+ emitter.end_stream
19
+ stream.close
20
+ end
21
+
22
+ def write_sequence
23
+ @first = false
24
+
25
+ # anchor, tag, implicit, style
26
+ emitter.start_sequence(
27
+ nil, nil, true, Psych::Nodes::Sequence::ANY
28
+ )
29
+
30
+ yield YieldingSequenceWriter.new(emitter, stream)
31
+ emitter.end_sequence
32
+ end
33
+
34
+ def write_map
35
+ @first = false
36
+
37
+ # anchor, tag, implicit, style
38
+ emitter.start_mapping(
39
+ nil, nil, true, Psych::Nodes::Sequence::ANY
40
+ )
41
+
42
+ yield YieldingMappingWriter.new(emitter, stream)
43
+ emitter.end_mapping
44
+ end
45
+
46
+ protected
47
+
48
+ def write_scalar(value)
49
+ @first = false
50
+
51
+ # value, anchor, tag, plain, quoted, style
52
+ emitter.scalar(
53
+ value, nil, nil, true, false, Psych::Nodes::Scalar::ANY
54
+ )
55
+ end
56
+ end
57
+
58
+ class YieldingMappingWriter < YieldingWriter
59
+ def write_map(key)
60
+ write_scalar(key)
61
+ super()
62
+ end
63
+
64
+ def write_sequence(key)
65
+ write_scalar(key)
66
+ super()
67
+ end
68
+
69
+ def write_key_value(key, value)
70
+ @first = false
71
+ write_scalar(key)
72
+ write_scalar(value)
73
+ end
74
+ end
75
+
76
+ class YieldingSequenceWriter < YieldingWriter
77
+ def write_element(element)
78
+ write_scalar(element)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'psych'
4
+ require 'yaml-write-stream/yielding'
5
+ require 'yaml-write-stream/stateful'
6
+
7
+ class YamlWriteStream
8
+ class << self
9
+ def open(path, encoding = Psych::Parser::UTF8, &block)
10
+ handle = ::File.open(path, 'w')
11
+ from_stream(handle, encoding, &block)
12
+ end
13
+
14
+ def from_stream(stream, encoding = Psych::Parser::UTF8)
15
+ emitter = Psych::Emitter.new(stream)
16
+ emitter.start_stream(convert_encoding(encoding))
17
+
18
+ # version, tag_directives, implicit
19
+ emitter.start_document([], [], true)
20
+
21
+ if block_given?
22
+ yield writer = YieldingWriter.new(emitter, stream)
23
+ writer.close
24
+ nil
25
+ else
26
+ StatefulWriter.new(emitter, stream)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def convert_encoding(encoding)
33
+ case encoding
34
+ when Encoding
35
+ case encoding
36
+ when Encoding::UTF_8
37
+ Psych::Parser::UTF8
38
+ when Encoding::UTF_16BE
39
+ Psych::Parser::UTF16BE
40
+ when Encoding::UTF_16LE
41
+ Psych::Parser::UTF16LE
42
+ else
43
+ raise ArgumentError, "'#{encoding}' encoding is not supported by Psych."
44
+ end
45
+ when Fixnum
46
+ encoding
47
+ when String
48
+ convert_encoding(Encoding.find(encoding))
49
+ else
50
+ raise ArgumentError, "encoding of type #{encoding.class} is not supported, please provide an Encoding or a Fixnum."
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+
3
+ shared_examples 'a yaml stream' do
4
+ it 'handles a simple array' do
5
+ check_roundtrip(['abc'])
6
+ end
7
+
8
+ it 'handles a simple object' do
9
+ check_roundtrip({ 'foo' => 'bar' })
10
+ end
11
+
12
+ it 'handles one level of array nesting' do
13
+ check_roundtrip([['def'],'abc'])
14
+ check_roundtrip(['abc',['def']])
15
+ end
16
+
17
+ it 'handles one level of object nesting' do
18
+ check_roundtrip({ 'foo' => { 'bar' => 'baz' } })
19
+ end
20
+
21
+ it 'handles one level of mixed nesting' do
22
+ check_roundtrip({ 'foo' => ['bar', 'baz'] })
23
+ check_roundtrip([{ 'foo' => 'bar' }])
24
+ end
25
+
26
+ it 'handles multiple levels of mixed nesting' do
27
+ check_roundtrip({'foo' => ['bar', { 'baz' => 'moo', 'gaz' => ['doo'] }, 'kal'], 'jim' => ['jill', ['john']] })
28
+ check_roundtrip(['foo', { 'bar' => 'baz', 'moo' => ['gaz', ['jim', ['jill']], 'jam'] }])
29
+ end
30
+ end
@@ -0,0 +1,173 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rspec'
4
+ require 'yaml-write-stream'
5
+ require 'shared_examples'
6
+ require 'pry-nav'
7
+
8
+ RSpec.configure do |config|
9
+ config.mock_with :rr
10
+ end
11
+
12
+ class RoundtripChecker
13
+ class << self
14
+ include RSpec::Matchers
15
+
16
+ def check_roundtrip(obj)
17
+ stream = StringIO.new
18
+ writer = create_writer(stream)
19
+ serialize(obj, writer)
20
+ writer.close
21
+ new_obj = Psych.load(stream.string)
22
+ compare(obj, new_obj)
23
+ end
24
+
25
+ protected
26
+
27
+ def create_emitter(stream)
28
+ Psych::Emitter.new(stream).tap do |emitter|
29
+ emitter.start_stream(Psych::Parser::UTF8)
30
+ emitter.start_document([], [], true)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def compare(old_obj, new_obj)
37
+ expect(old_obj.class).to equal(new_obj.class)
38
+
39
+ case old_obj
40
+ when Hash
41
+ expect(old_obj.keys).to eq(new_obj.keys)
42
+
43
+ old_obj.each_pair do |key, old_val|
44
+ compare(old_val, new_obj[key])
45
+ end
46
+ when Array
47
+ old_obj.each_with_index do |old_element, idx|
48
+ compare(old_element, new_obj[idx])
49
+ end
50
+ else
51
+ expect(old_obj).to eq(new_obj)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ class YieldingRoundtripChecker < RoundtripChecker
58
+ class << self
59
+ def create_writer(stream)
60
+ YamlWriteStream::YieldingWriter.new(
61
+ create_emitter(stream), stream
62
+ )
63
+ end
64
+
65
+ protected
66
+
67
+ def serialize(obj, writer)
68
+ case obj
69
+ when Hash
70
+ writer.write_map do |map_writer|
71
+ serialize_map(obj, map_writer)
72
+ end
73
+ when Array
74
+ writer.write_sequence do |sequence_writer|
75
+ serialize_sequence(obj, sequence_writer)
76
+ end
77
+ end
78
+ end
79
+
80
+ def serialize_map(obj, writer)
81
+ obj.each_pair do |key, val|
82
+ case val
83
+ when Hash
84
+ writer.write_map(key) do |map_writer|
85
+ serialize_map(val, map_writer)
86
+ end
87
+ when Array
88
+ writer.write_sequence(key) do |sequence_writer|
89
+ serialize_sequence(val, sequence_writer)
90
+ end
91
+ else
92
+ writer.write_key_value(key, val)
93
+ end
94
+ end
95
+ end
96
+
97
+ def serialize_sequence(obj, writer)
98
+ obj.each do |element|
99
+ case element
100
+ when Hash
101
+ writer.write_map do |map_writer|
102
+ serialize_map(element, map_writer)
103
+ end
104
+ when Array
105
+ writer.write_sequence do |sequence_writer|
106
+ serialize_sequence(element, sequence_writer)
107
+ end
108
+ else
109
+ writer.write_element(element)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ class StatefulRoundtripChecker < RoundtripChecker
117
+ class << self
118
+ def create_writer(stream)
119
+ YamlWriteStream::StatefulWriter.new(
120
+ create_emitter(stream), stream
121
+ )
122
+ end
123
+
124
+ protected
125
+
126
+ def serialize(obj, writer)
127
+ case obj
128
+ when Hash
129
+ writer.write_map
130
+ serialize_map(obj, writer)
131
+ writer.close_map
132
+ when Array
133
+ writer.write_sequence
134
+ serialize_sequence(obj, writer)
135
+ writer.close_sequence
136
+ end
137
+ end
138
+
139
+ def serialize_map(obj, writer)
140
+ obj.each_pair do |key, val|
141
+ case val
142
+ when Hash
143
+ writer.write_map(key)
144
+ serialize_map(val, writer)
145
+ writer.close_map
146
+ when Array
147
+ writer.write_sequence(key)
148
+ serialize_sequence(val, writer)
149
+ writer.close_sequence
150
+ else
151
+ writer.write_key_value(key, val)
152
+ end
153
+ end
154
+ end
155
+
156
+ def serialize_sequence(obj, writer)
157
+ obj.each do |element|
158
+ case element
159
+ when Hash
160
+ writer.write_map
161
+ serialize_map(element, writer)
162
+ writer.close_map
163
+ when Array
164
+ writer.write_sequence
165
+ serialize_sequence(element, writer)
166
+ writer.close_sequence
167
+ else
168
+ writer.write_element(element)
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,142 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe YamlWriteStream::YieldingWriter do
6
+ let(:stream) do
7
+ StringIO.new.tap do |io|
8
+ io.set_encoding(Encoding::UTF_8)
9
+ end
10
+ end
11
+
12
+ let(:stream_writer) do
13
+ StatefulRoundtripChecker.create_writer(stream)
14
+ end
15
+
16
+ def check_roundtrip(obj)
17
+ StatefulRoundtripChecker.check_roundtrip(obj)
18
+ end
19
+
20
+ def utf8(str)
21
+ str.encode(Encoding::UTF_8)
22
+ end
23
+
24
+ it_behaves_like 'a yaml stream'
25
+
26
+ describe '#close' do
27
+ it 'unwinds the stack, adds appropriate closing punctuation for each unclosed item, and closes the stream' do
28
+ stream_writer.write_sequence
29
+ stream_writer.write_element('abc')
30
+ stream_writer.write_map
31
+ stream_writer.write_key_value('def', 'ghi')
32
+ stream_writer.close
33
+
34
+ expect(stream.string).to eq(utf8("- abc\n- def: ghi\n"))
35
+ expect(stream_writer).to be_closed
36
+ expect(stream).to be_closed
37
+ end
38
+ end
39
+
40
+ describe '#closed?' do
41
+ it 'returns false if the stream is still open' do
42
+ expect(stream_writer).to_not be_closed
43
+ end
44
+
45
+ it 'returns true if the stream is closed' do
46
+ stream_writer.close
47
+ expect(stream_writer).to be_closed
48
+ end
49
+ end
50
+
51
+ describe '#in_map?' do
52
+ it 'returns true if the writer is currently writing a map' do
53
+ stream_writer.write_map
54
+ expect(stream_writer).to be_in_map
55
+ end
56
+
57
+ it 'returns false if the writer is not currently writing a map' do
58
+ expect(stream_writer).to_not be_in_map
59
+ stream_writer.write_sequence
60
+ expect(stream_writer).to_not be_in_map
61
+ end
62
+ end
63
+
64
+ describe '#in_sequence?' do
65
+ it 'returns true if the writer is currently writing a sequence' do
66
+ stream_writer.write_sequence
67
+ expect(stream_writer).to be_in_sequence
68
+ end
69
+
70
+ it 'returns false if the writer is not currently writing a sequence' do
71
+ expect(stream_writer).to_not be_in_sequence
72
+ stream_writer.write_map
73
+ expect(stream_writer).to_not be_in_sequence
74
+ end
75
+ end
76
+
77
+ describe '#eos?' do
78
+ it 'returns false if nothing has been written yet' do
79
+ expect(stream_writer).to_not be_eos
80
+ end
81
+
82
+ it 'returns false if the writer is in the middle of writing' do
83
+ stream_writer.write_map
84
+ expect(stream_writer).to_not be_eos
85
+ end
86
+
87
+ it "returns true if the writer has finished it's top-level" do
88
+ stream_writer.write_map
89
+ stream_writer.close_map
90
+ expect(stream_writer).to be_eos
91
+ end
92
+
93
+ it 'returns true if the writer is closed' do
94
+ stream_writer.close
95
+ expect(stream_writer).to be_eos
96
+ end
97
+ end
98
+
99
+ describe '#close_map' do
100
+ it 'raises an error if a map is not currently being written' do
101
+ stream_writer.write_sequence
102
+ expect(lambda { stream_writer.close_map }).to raise_error(YamlWriteStream::NotInMapError)
103
+ end
104
+ end
105
+
106
+ describe '#close_sequence' do
107
+ it 'raises an error if a sequence is not currently being written' do
108
+ stream_writer.write_map
109
+ expect(lambda { stream_writer.close_sequence }).to raise_error(YamlWriteStream::NotInSequenceError)
110
+ end
111
+ end
112
+
113
+ context 'with a closed stream writer' do
114
+ before(:each) do
115
+ stream_writer.close
116
+ end
117
+
118
+ describe '#write_map' do
119
+ it 'raises an error if eos' do
120
+ expect(lambda { stream_writer.write_map }).to raise_error(YamlWriteStream::EndOfStreamError)
121
+ end
122
+ end
123
+
124
+ describe '#write_sequence' do
125
+ it 'raises an error if eos' do
126
+ expect(lambda { stream_writer.write_map }).to raise_error(YamlWriteStream::EndOfStreamError)
127
+ end
128
+ end
129
+
130
+ describe '#write_key_value' do
131
+ it 'raises an error if eos' do
132
+ expect(lambda { stream_writer.write_key_value('abc', 'def') }).to raise_error(YamlWriteStream::EndOfStreamError)
133
+ end
134
+ end
135
+
136
+ describe '#write_element' do
137
+ it 'raises an error if eos' do
138
+ expect(lambda { stream_writer.write_element('foo') }).to raise_error(YamlWriteStream::EndOfStreamError)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,146 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+ require 'tempfile'
5
+
6
+ describe YamlWriteStream do
7
+ let(:yielding_writer) { YamlWriteStream::YieldingWriter }
8
+ let(:stateful_writer) { YamlWriteStream::StatefulWriter }
9
+ let(:stream_writer) { YamlWriteStream }
10
+ let(:tempfile) { Tempfile.new('temp') }
11
+ let(:stream) { StringIO.new }
12
+
13
+ def encodings_match?(outputs)
14
+ flattened = flatten_encodings(outputs)
15
+ flattened.uniq.size == flattened.size
16
+ end
17
+
18
+ def flatten_encodings(outputs)
19
+ outputs.map do |encoding, value|
20
+ bom = case encoding
21
+ when Encoding::UTF_16LE, Encoding::UTF_16BE
22
+ [239, 187, 191]
23
+ else
24
+ []
25
+ end
26
+
27
+ bom + value
28
+ .force_encoding(encoding)
29
+ .encode(Encoding::UTF_8)
30
+ .bytes
31
+ .to_a
32
+ end
33
+ end
34
+
35
+ describe '#from_stream' do
36
+ it 'yields a yielding stream if given a block' do
37
+ stream_writer.from_stream(stream) do |writer|
38
+ expect(writer).to be_a(yielding_writer)
39
+ expect(writer.stream).to equal(stream)
40
+ end
41
+ end
42
+
43
+ it 'returns a stateful writer if not given a block' do
44
+ writer = stream_writer.from_stream(stream)
45
+ expect(writer).to be_a(stateful_writer)
46
+ expect(writer.stream).to equal(stream)
47
+ end
48
+
49
+ [Encoding::UTF_8, Encoding::UTF_16LE, Encoding::UTF_16BE].each do |encoding|
50
+ it "supports specifying a #{encoding.name} encoding" do
51
+ stream_writer.from_stream(stream, encoding) do |writer|
52
+ writer.write_map do |map_writer|
53
+ map_writer.write_key_value('foo', 'bar')
54
+ end
55
+ end
56
+
57
+ expect(
58
+ encodings_match?({
59
+ encoding => stream.string,
60
+ Encoding::UTF_8 => "foo: bar\n"
61
+ })
62
+ ).to eq(true)
63
+ end
64
+ end
65
+
66
+ it "doesn't support other encodings" do
67
+ expect(
68
+ lambda do
69
+ stream_writer.from_stream(stream, Encoding::US_ASCII)
70
+ end
71
+ ).to raise_error(ArgumentError)
72
+ end
73
+
74
+ it 'interprets string encoding names' do
75
+ stream_writer.from_stream(stream, 'UTF-16BE') do |writer|
76
+ writer.write_map do |map_writer|
77
+ map_writer.write_key_value('foo', 'bar')
78
+ end
79
+ end
80
+
81
+ expect(
82
+ encodings_match?({
83
+ Encoding::UTF_16BE => stream.string,
84
+ Encoding::UTF_8 => "foo: bar\n"
85
+ })
86
+ )
87
+ end
88
+
89
+ it 'interprets Psych integer encodings' do
90
+ stream_writer.from_stream(stream, Psych::Parser::UTF16BE) do |writer|
91
+ writer.write_map do |map_writer|
92
+ map_writer.write_key_value('foo', 'bar')
93
+ end
94
+ end
95
+
96
+ expect(
97
+ encodings_match?({
98
+ Encoding::UTF_16BE => stream.string,
99
+ Encoding::UTF_8 => "foo: bar\n"
100
+ })
101
+ )
102
+ end
103
+
104
+ it 'raises an error if an unrecognized type of object is given as encoding' do
105
+ expect(
106
+ lambda do
107
+ stream_writer.from_stream(stream, Object.new)
108
+ end
109
+ ).to raise_error(ArgumentError)
110
+ end
111
+ end
112
+
113
+ describe '#open' do
114
+ it 'opens a file and yields a yielding stream if given a block' do
115
+ mock.proxy(File).open(tempfile, 'w')
116
+ stream_writer.open(tempfile) do |writer|
117
+ expect(writer).to be_a(yielding_writer)
118
+ expect(writer.stream.path).to eq(tempfile.path)
119
+ end
120
+ end
121
+
122
+ it 'opens a file and returns a stateful writer if not given a block' do
123
+ mock.proxy(File).open(tempfile, 'w')
124
+ writer = stream_writer.open(tempfile)
125
+ expect(writer).to be_a(stateful_writer)
126
+ expect(writer.stream.path).to eq(tempfile.path)
127
+ end
128
+
129
+ [Encoding::UTF_8, Encoding::UTF_16LE, Encoding::UTF_16BE].each do |encoding|
130
+ it "supports specifying a #{encoding.name} encoding" do
131
+ stream_writer.open(tempfile, encoding) do |writer|
132
+ writer.write_map do |map_writer|
133
+ map_writer.write_key_value('foo', 'bar')
134
+ end
135
+ end
136
+
137
+ expect(
138
+ encodings_match?({
139
+ encoding => stream.string,
140
+ Encoding::UTF_8 => "foo: bar\n"
141
+ })
142
+ ).to eq(true)
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe YamlWriteStream::YieldingWriter do
6
+ let(:stream) do
7
+ StringIO.new.tap do |io|
8
+ io.set_encoding(Encoding::UTF_8)
9
+ end
10
+ end
11
+
12
+ let(:stream_writer) do
13
+ YieldingRoundtripChecker.create_writer(stream)
14
+ end
15
+
16
+ def check_roundtrip(obj)
17
+ YieldingRoundtripChecker.check_roundtrip(obj)
18
+ end
19
+
20
+ def utf8(str)
21
+ str.encode(Encoding::UTF_8)
22
+ end
23
+
24
+ it_behaves_like 'a yaml stream'
25
+
26
+ describe '#close' do
27
+ it 'closes the underlying stream' do
28
+ stream_writer.close
29
+ expect(stream).to be_closed
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'yaml-write-stream/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "yaml-write-stream"
6
+ s.version = ::YamlWriteStream::VERSION
7
+ s.authors = ["Cameron Dutro"]
8
+ s.email = ["camertron@gmail.com"]
9
+ s.homepage = "http://github.com/camertron"
10
+
11
+ s.description = s.summary = "An easy, streaming way to generate YAML."
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+ s.has_rdoc = true
15
+
16
+ s.require_path = 'lib'
17
+ s.files = Dir["{lib,spec}/**/*", "Gemfile", "History.txt", "README.md", "Rakefile", "yaml-write-stream.gemspec"]
18
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yaml-write-stream
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cameron Dutro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-26 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: An easy, streaming way to generate YAML.
14
+ email:
15
+ - camertron@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/yaml-write-stream/stateful.rb
21
+ - lib/yaml-write-stream/version.rb
22
+ - lib/yaml-write-stream/yielding.rb
23
+ - lib/yaml-write-stream.rb
24
+ - spec/shared_examples.rb
25
+ - spec/spec_helper.rb
26
+ - spec/stateful_spec.rb
27
+ - spec/yaml-write-stream_spec.rb
28
+ - spec/yielding_spec.rb
29
+ - Gemfile
30
+ - History.txt
31
+ - README.md
32
+ - Rakefile
33
+ - yaml-write-stream.gemspec
34
+ homepage: http://github.com/camertron
35
+ licenses: []
36
+ metadata: {}
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 2.0.14
54
+ signing_key:
55
+ specification_version: 4
56
+ summary: An easy, streaming way to generate YAML.
57
+ test_files: []