psych 1.2.2 → 1.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/psych.rb CHANGED
@@ -10,7 +10,10 @@ require 'psych/set'
10
10
  require 'psych/coder'
11
11
  require 'psych/core_ext'
12
12
  require 'psych/deprecated'
13
- require 'psych/json'
13
+ require 'psych/stream'
14
+ require 'psych/json/tree_builder'
15
+ require 'psych/json/stream'
16
+ require 'psych/handlers/document_stream'
14
17
 
15
18
  ###
16
19
  # = Overview
@@ -90,7 +93,7 @@ require 'psych/json'
90
93
 
91
94
  module Psych
92
95
  # The version is Psych you're using
93
- VERSION = '1.2.2'
96
+ VERSION = '1.3.0'
94
97
 
95
98
  # The version of libyaml Psych is using
96
99
  LIBYAML_VERSION = Psych.libyaml_version.join '.'
@@ -101,39 +104,63 @@ module Psych
101
104
  class BadAlias < Exception
102
105
  end
103
106
 
104
- autoload :Stream, 'psych/stream'
105
-
106
107
  ###
107
108
  # Load +yaml+ in to a Ruby data structure. If multiple documents are
108
109
  # provided, the object contained in the first document will be returned.
110
+ # +filename+ will be used in the exception message if any exception is raised
111
+ # while parsing.
112
+ #
113
+ # Raises a Psych::SyntaxError when a YAML syntax error is detected.
109
114
  #
110
115
  # Example:
111
116
  #
112
- # Psych.load("--- a") # => 'a'
113
- # Psych.load("---\n - a\n - b") # => ['a', 'b']
114
- def self.load yaml
115
- result = parse(yaml)
117
+ # Psych.load("--- a") # => 'a'
118
+ # Psych.load("---\n - a\n - b") # => ['a', 'b']
119
+ #
120
+ # begin
121
+ # Psych.load("--- `", "file.txt")
122
+ # rescue Psych::SyntaxError => ex
123
+ # ex.file # => 'file.txt'
124
+ # ex.message # => "(foo.txt): found character that cannot start any token"
125
+ # end
126
+ def self.load yaml, filename = nil
127
+ result = parse(yaml, filename)
116
128
  result ? result.to_ruby : result
117
129
  end
118
130
 
119
131
  ###
120
132
  # Parse a YAML string in +yaml+. Returns the first object of a YAML AST.
133
+ # +filename+ is used in the exception message if a Psych::SyntaxError is
134
+ # raised.
135
+ #
136
+ # Raises a Psych::SyntaxError when a YAML syntax error is detected.
121
137
  #
122
138
  # Example:
123
139
  #
124
140
  # Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Sequence:0x00>
125
141
  #
142
+ # begin
143
+ # Psych.parse("--- `", "file.txt")
144
+ # rescue Psych::SyntaxError => ex
145
+ # ex.file # => 'file.txt'
146
+ # ex.message # => "(foo.txt): found character that cannot start any token"
147
+ # end
148
+ #
126
149
  # See Psych::Nodes for more information about YAML AST.
127
- def self.parse yaml
128
- children = parse_stream(yaml).children
129
- children.empty? ? false : children.first.children.first
150
+ def self.parse yaml, filename = nil
151
+ parse_stream(yaml, filename) do |node|
152
+ return node
153
+ end
154
+ false
130
155
  end
131
156
 
132
157
  ###
133
158
  # Parse a file at +filename+. Returns the YAML AST.
159
+ #
160
+ # Raises a Psych::SyntaxError when a YAML syntax error is detected.
134
161
  def self.parse_file filename
135
- File.open filename do |f|
136
- parse f
162
+ File.open filename, 'r:bom|utf-8' do |f|
163
+ parse f, filename
137
164
  end
138
165
  end
139
166
 
@@ -146,16 +173,39 @@ module Psych
146
173
  ###
147
174
  # Parse a YAML string in +yaml+. Returns the full AST for the YAML document.
148
175
  # This method can handle multiple YAML documents contained in +yaml+.
176
+ # +filename+ is used in the exception message if a Psych::SyntaxError is
177
+ # raised.
178
+ #
179
+ # If a block is given, a Psych::Nodes::Document node will be yielded to the
180
+ # block as it's being parsed.
181
+ #
182
+ # Raises a Psych::SyntaxError when a YAML syntax error is detected.
149
183
  #
150
184
  # Example:
151
185
  #
152
186
  # Psych.parse_stream("---\n - a\n - b") # => #<Psych::Nodes::Stream:0x00>
153
187
  #
188
+ # Psych.parse_stream("--- a\n--- b") do |node|
189
+ # node # => #<Psych::Nodes::Document:0x00>
190
+ # end
191
+ #
192
+ # begin
193
+ # Psych.parse_stream("--- `", "file.txt")
194
+ # rescue Psych::SyntaxError => ex
195
+ # ex.file # => 'file.txt'
196
+ # ex.message # => "(foo.txt): found character that cannot start any token"
197
+ # end
198
+ #
154
199
  # See Psych::Nodes for more information about YAML AST.
155
- def self.parse_stream yaml
156
- parser = self.parser
157
- parser.parse yaml
158
- parser.handler.root
200
+ def self.parse_stream yaml, filename = nil, &block
201
+ if block_given?
202
+ parser = Psych::Parser.new(Handlers::DocumentStream.new(&block))
203
+ parser.parse yaml, filename
204
+ else
205
+ parser = self.parser
206
+ parser.parse yaml, filename
207
+ parser.handler.root
208
+ end
159
209
  end
160
210
 
161
211
  ###
@@ -217,19 +267,34 @@ module Psych
217
267
 
218
268
  ###
219
269
  # Load multiple documents given in +yaml+. Returns the parsed documents
220
- # as a list. For example:
270
+ # as a list. If a block is given, each document will be converted to ruby
271
+ # and passed to the block during parsing
272
+ #
273
+ # Example:
221
274
  #
222
275
  # Psych.load_stream("--- foo\n...\n--- bar\n...") # => ['foo', 'bar']
223
276
  #
224
- def self.load_stream yaml
225
- parse_stream(yaml).children.map { |child| child.to_ruby }
277
+ # list = []
278
+ # Psych.load_stream("--- foo\n...\n--- bar\n...") do |ruby|
279
+ # list << ruby
280
+ # end
281
+ # list # => ['foo', 'bar']
282
+ #
283
+ def self.load_stream yaml, filename = nil
284
+ if block_given?
285
+ parse_stream(yaml, filename) do |node|
286
+ yield node.to_ruby
287
+ end
288
+ else
289
+ parse_stream(yaml, filename).children.map { |child| child.to_ruby }
290
+ end
226
291
  end
227
292
 
228
293
  ###
229
294
  # Load the document contained in +filename+. Returns the yaml contained in
230
295
  # +filename+ as a ruby object
231
296
  def self.load_file filename
232
- self.load File.open(filename)
297
+ File.open(filename, 'r:bom|utf-8') { |f| self.load f, filename }
233
298
  end
234
299
 
235
300
  # :stopdoc:
@@ -30,6 +30,7 @@ class Module
30
30
  alias :yaml_as :psych_yaml_as
31
31
  end
32
32
 
33
+ if defined?(::IRB)
33
34
  module Kernel
34
35
  def psych_y *objects
35
36
  puts Psych.dump_stream(*objects)
@@ -38,3 +39,4 @@ module Kernel
38
39
  alias y psych_y
39
40
  private :y
40
41
  end
42
+ end
@@ -0,0 +1,22 @@
1
+ require 'psych/tree_builder'
2
+
3
+ module Psych
4
+ module Handlers
5
+ class DocumentStream < Psych::TreeBuilder # :nodoc:
6
+ def initialize &block
7
+ super
8
+ @block = block
9
+ end
10
+
11
+ def start_document version, tag_directives, implicit
12
+ n = Nodes::Document.new version, tag_directives, implicit
13
+ push n
14
+ end
15
+
16
+ def end_document implicit_end = !streaming?
17
+ @last.implicit_end = implicit_end
18
+ @block.call pop
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/psych/parser.rb CHANGED
@@ -36,12 +36,16 @@ module Psych
36
36
  # The handler on which events will be called
37
37
  attr_accessor :handler
38
38
 
39
+ # Set the encoding for this parser to +encoding+
40
+ attr_writer :external_encoding
41
+
39
42
  ###
40
43
  # Creates a new Psych::Parser instance with +handler+. YAML events will
41
44
  # be called on +handler+. See Psych::Parser for more details.
42
45
 
43
46
  def initialize handler = Handler.new
44
47
  @handler = handler
48
+ @external_encoding = ANY
45
49
  end
46
50
  end
47
51
  end
@@ -46,9 +46,13 @@ module Psych
46
46
  end
47
47
  when TIME
48
48
  parse_time string
49
- when /^\d{4}-\d{1,2}-\d{1,2}$/
49
+ when /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/
50
50
  require 'date'
51
- Date.strptime(string, '%Y-%m-%d')
51
+ begin
52
+ Date.strptime(string, '%Y-%m-%d')
53
+ rescue ArgumentError
54
+ string
55
+ end
52
56
  when /^\.inf$/i
53
57
  1 / 0.0
54
58
  when /^-\.inf$/i
@@ -61,7 +65,7 @@ module Psych
61
65
  else
62
66
  string.sub(/^:/, '').to_sym
63
67
  end
64
- when /^[-+]?[1-9][0-9_]*(:[0-5]?[0-9])+$/
68
+ when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/
65
69
  i = 0
66
70
  string.split(':').each_with_index do |n,e|
67
71
  i += (n.to_i * 60 ** (e - 2).abs)
@@ -74,13 +78,19 @@ module Psych
74
78
  end
75
79
  i
76
80
  when FLOAT
77
- return Float(string.gsub(/[,_]/, '')) rescue ArgumentError
81
+ begin
82
+ return Float(string.gsub(/[,_]/, ''))
83
+ rescue ArgumentError
84
+ end
78
85
 
79
86
  @string_cache[string] = true
80
87
  string
81
88
  else
82
89
  if string.count('.') < 2
83
- return Integer(string.gsub(/[,_]/, '')) rescue ArgumentError
90
+ begin
91
+ return Integer(string.gsub(/[,_]/, ''))
92
+ rescue ArgumentError
93
+ end
84
94
  end
85
95
 
86
96
  @string_cache[string] = true
@@ -0,0 +1,19 @@
1
+ module Psych
2
+ class SyntaxError < ::SyntaxError
3
+ attr_reader :file, :line, :column, :offset, :problem, :context
4
+
5
+ def initialize file, line, col, offset, problem, context
6
+ err = [problem, context].compact.join ' '
7
+ filename = file || '<unknown>'
8
+ message = "(%s): %s at line %d column %d" % [filename, err, line, col]
9
+
10
+ @file = file
11
+ @line = line
12
+ @column = col
13
+ @offset = offset
14
+ @problem = problem
15
+ @context = context
16
+ super(message)
17
+ end
18
+ end
19
+ end
@@ -72,7 +72,9 @@ module Psych
72
72
  end
73
73
 
74
74
  def scalar value, anchor, tag, plain, quoted, style
75
- @last.children << Nodes::Scalar.new(value,anchor,tag,plain,quoted,style)
75
+ s = Nodes::Scalar.new(value,anchor,tag,plain,quoted,style)
76
+ @last.children << s
77
+ s
76
78
  end
77
79
 
78
80
  def alias anchor
@@ -31,9 +31,7 @@ module Psych
31
31
  result
32
32
  end
33
33
 
34
- def visit_Psych_Nodes_Scalar o
35
- @st[o.anchor] = o.value if o.anchor
36
-
34
+ def deserialize o
37
35
  if klass = Psych.load_tags[o.tag]
38
36
  instance = klass.allocate
39
37
 
@@ -52,8 +50,16 @@ module Psych
52
50
  case o.tag
53
51
  when '!binary', 'tag:yaml.org,2002:binary'
54
52
  o.value.unpack('m').first
55
- when '!str', 'tag:yaml.org,2002:str'
56
- o.value
53
+ when /^!(?:str|ruby\/string)(?::(.*))?/, 'tag:yaml.org,2002:str'
54
+ klass = resolve_class($1)
55
+ if klass
56
+ klass.allocate.replace o.value
57
+ else
58
+ o.value
59
+ end
60
+ when '!ruby/object:BigDecimal'
61
+ require 'bigdecimal'
62
+ BigDecimal._load o.value
57
63
  when "!ruby/object:DateTime"
58
64
  require 'date'
59
65
  @ss.parse_time(o.value).to_datetime
@@ -92,6 +98,11 @@ module Psych
92
98
  @ss.tokenize o.value
93
99
  end
94
100
  end
101
+ private :deserialize
102
+
103
+ def visit_Psych_Nodes_Scalar o
104
+ register o, deserialize(o)
105
+ end
95
106
 
96
107
  def visit_Psych_Nodes_Sequence o
97
108
  if klass = Psych.load_tags[o.tag]
@@ -108,15 +119,18 @@ module Psych
108
119
 
109
120
  case o.tag
110
121
  when '!omap', 'tag:yaml.org,2002:omap'
111
- map = Psych::Omap.new
112
- @st[o.anchor] = map if o.anchor
122
+ map = register(o, Psych::Omap.new)
113
123
  o.children.each { |a|
114
124
  map[accept(a.children.first)] = accept a.children.last
115
125
  }
116
126
  map
127
+ when /^!(?:seq|ruby\/array):(.*)$/
128
+ klass = resolve_class($1)
129
+ list = register(o, klass.allocate)
130
+ o.children.each { |c| list.push accept c }
131
+ list
117
132
  else
118
- list = []
119
- @st[o.anchor] = list if o.anchor
133
+ list = register(o, [])
120
134
  o.children.each { |c| list.push accept c }
121
135
  list
122
136
  end
@@ -127,16 +141,33 @@ module Psych
127
141
  return revive_hash({}, o) unless o.tag
128
142
 
129
143
  case o.tag
130
- when '!str', 'tag:yaml.org,2002:str'
144
+ when /^!(?:str|ruby\/string)(?::(.*))?/, 'tag:yaml.org,2002:str'
145
+ klass = resolve_class($1)
131
146
  members = Hash[*o.children.map { |c| accept c }]
132
147
  string = members.delete 'str'
148
+
149
+ if klass
150
+ string = klass.allocate
151
+ string.replace string
152
+ end
153
+
133
154
  init_with(string, members.map { |k,v| [k.to_s.sub(/^@/, ''),v] }, o)
155
+ when /^!ruby\/array:(.*)$/
156
+ klass = resolve_class($1)
157
+ list = register(o, klass.allocate)
158
+
159
+ members = Hash[o.children.map { |c| accept c }.each_slice(2).to_a]
160
+ list.replace members['internal']
161
+
162
+ members['ivars'].each do |ivar, v|
163
+ list.instance_variable_set ivar, v
164
+ end
165
+ list
134
166
  when /^!ruby\/struct:?(.*)?$/
135
167
  klass = resolve_class($1)
136
168
 
137
169
  if klass
138
- s = klass.allocate
139
- @st[o.anchor] = s if o.anchor
170
+ s = register(o, klass.allocate)
140
171
 
141
172
  members = {}
142
173
  struct_members = s.members.map { |x| x.to_sym }
@@ -158,7 +189,7 @@ module Psych
158
189
 
159
190
  when '!ruby/range'
160
191
  h = Hash[*o.children.map { |c| accept c }]
161
- Range.new(h['begin'], h['end'], h['excl'])
192
+ register o, Range.new(h['begin'], h['end'], h['excl'])
162
193
 
163
194
  when /^!ruby\/exception:?(.*)?$/
164
195
  h = Hash[*o.children.map { |c| accept c }]
@@ -177,11 +208,11 @@ module Psych
177
208
 
178
209
  when '!ruby/object:Complex'
179
210
  h = Hash[*o.children.map { |c| accept c }]
180
- Complex(h['real'], h['image'])
211
+ register o, Complex(h['real'], h['image'])
181
212
 
182
213
  when '!ruby/object:Rational'
183
214
  h = Hash[*o.children.map { |c| accept c }]
184
- Rational(h['numerator'], h['denominator'])
215
+ register o, Rational(h['numerator'], h['denominator'])
185
216
 
186
217
  when /^!ruby\/object:?(.*)?$/
187
218
  name = $1 || 'Object'
@@ -209,6 +240,11 @@ module Psych
209
240
  end
210
241
 
211
242
  private
243
+ def register node, object
244
+ @st[node.anchor] = object if node.anchor
245
+ object
246
+ end
247
+
212
248
  def revive_hash hash, o
213
249
  @st[o.anchor] = hash if o.anchor
214
250
 
@@ -159,13 +159,13 @@ module Psych
159
159
  end
160
160
 
161
161
  def visit_Regexp o
162
- @emitter.scalar o.inspect, nil, '!ruby/regexp', false, false, Nodes::Scalar::ANY
162
+ register o, @emitter.scalar(o.inspect, nil, '!ruby/regexp', false, false, Nodes::Scalar::ANY)
163
163
  end
164
164
 
165
165
  def visit_DateTime o
166
166
  formatted = format_time o.to_time
167
167
  tag = '!ruby/object:DateTime'
168
- @emitter.scalar formatted, nil, tag, false, false, Nodes::Scalar::ANY
168
+ register o, @emitter.scalar(formatted, nil, tag, false, false, Nodes::Scalar::ANY)
169
169
  end
170
170
 
171
171
  def visit_Time o
@@ -174,7 +174,7 @@ module Psych
174
174
  end
175
175
 
176
176
  def visit_Rational o
177
- @emitter.start_mapping(nil, '!ruby/object:Rational', false, Nodes::Mapping::BLOCK)
177
+ register o, @emitter.start_mapping(nil, '!ruby/object:Rational', false, Nodes::Mapping::BLOCK)
178
178
 
179
179
  [
180
180
  'denominator', o.denominator.to_s,
@@ -187,7 +187,7 @@ module Psych
187
187
  end
188
188
 
189
189
  def visit_Complex o
190
- @emitter.start_mapping(nil, '!ruby/object:Complex', false, Nodes::Mapping::BLOCK)
190
+ register o, @emitter.start_mapping(nil, '!ruby/object:Complex', false, Nodes::Mapping::BLOCK)
191
191
 
192
192
  ['real', o.real.to_s, 'image', o.imag.to_s].each do |m|
193
193
  @emitter.scalar m, nil, nil, true, false, Nodes::Scalar::ANY
@@ -214,6 +214,10 @@ module Psych
214
214
  end
215
215
  end
216
216
 
217
+ def visit_BigDecimal o
218
+ @emitter.scalar o._dump, nil, '!ruby/object:BigDecimal', false, false, Nodes::Scalar::ANY
219
+ end
220
+
217
221
  def binary? string
218
222
  string.encoding == Encoding::ASCII_8BIT ||
219
223
  string.index("\x00") ||
@@ -241,9 +245,15 @@ module Psych
241
245
  ivars = find_ivars o
242
246
 
243
247
  if ivars.empty?
248
+ unless o.class == ::String
249
+ tag = "!ruby/string:#{o.class}"
250
+ end
244
251
  @emitter.scalar str, nil, tag, plain, quote, style
245
252
  else
246
- @emitter.start_mapping nil, '!str', false, Nodes::Mapping::BLOCK
253
+ maptag = '!ruby/string'
254
+ maptag << ":#{o.class}" unless o.class == ::String
255
+
256
+ @emitter.start_mapping nil, maptag, false, Nodes::Mapping::BLOCK
247
257
  @emitter.scalar 'str', nil, nil, true, false, Nodes::Scalar::ANY
248
258
  @emitter.scalar str, nil, tag, plain, quote, style
249
259
 
@@ -255,16 +265,16 @@ module Psych
255
265
 
256
266
  def visit_Module o
257
267
  raise TypeError, "can't dump anonymous module: #{o}" unless o.name
258
- @emitter.scalar o.name, nil, '!ruby/module', false, false, Nodes::Scalar::SINGLE_QUOTED
268
+ register o, @emitter.scalar(o.name, nil, '!ruby/module', false, false, Nodes::Scalar::SINGLE_QUOTED)
259
269
  end
260
270
 
261
271
  def visit_Class o
262
272
  raise TypeError, "can't dump anonymous class: #{o}" unless o.name
263
- @emitter.scalar o.name, nil, '!ruby/class', false, false, Nodes::Scalar::SINGLE_QUOTED
273
+ register o, @emitter.scalar(o.name, nil, '!ruby/class', false, false, Nodes::Scalar::SINGLE_QUOTED)
264
274
  end
265
275
 
266
276
  def visit_Range o
267
- @emitter.start_mapping nil, '!ruby/range', false, Nodes::Mapping::BLOCK
277
+ register o, @emitter.start_mapping(nil, '!ruby/range', false, Nodes::Mapping::BLOCK)
268
278
  ['begin', o.begin, 'end', o.end, 'excl', o.exclude_end?].each do |m|
269
279
  accept m
270
280
  end
@@ -297,9 +307,13 @@ module Psych
297
307
  end
298
308
 
299
309
  def visit_Array o
300
- register o, @emitter.start_sequence(nil, nil, true, Nodes::Sequence::BLOCK)
301
- o.each { |c| accept c }
302
- @emitter.end_sequence
310
+ if o.class == ::Array
311
+ register o, @emitter.start_sequence(nil, nil, true, Nodes::Sequence::BLOCK)
312
+ o.each { |c| accept c }
313
+ @emitter.end_sequence
314
+ else
315
+ visit_array_subclass o
316
+ end
303
317
  end
304
318
 
305
319
  def visit_NilClass o
@@ -311,6 +325,39 @@ module Psych
311
325
  end
312
326
 
313
327
  private
328
+ def visit_array_subclass o
329
+ tag = "!ruby/array:#{o.class}"
330
+ if o.instance_variables.empty?
331
+ node = @emitter.start_sequence(nil, tag, false, Nodes::Sequence::BLOCK)
332
+ register o, node
333
+ o.each { |c| accept c }
334
+ @emitter.end_sequence
335
+ else
336
+ node = @emitter.start_mapping(nil, tag, false, Nodes::Sequence::BLOCK)
337
+ register o, node
338
+
339
+ # Dump the internal list
340
+ accept 'internal'
341
+ @emitter.start_sequence(nil, nil, true, Nodes::Sequence::BLOCK)
342
+ o.each { |c| accept c }
343
+ @emitter.end_sequence
344
+
345
+ # Dump the ivars
346
+ accept 'ivars'
347
+ @emitter.start_mapping(nil, nil, true, Nodes::Sequence::BLOCK)
348
+ o.instance_variables.each do |ivar|
349
+ accept ivar
350
+ accept o.instance_variable_get ivar
351
+ end
352
+ @emitter.end_mapping
353
+
354
+ @emitter.end_mapping
355
+ end
356
+ end
357
+
358
+ def dump_list o
359
+ end
360
+
314
361
  # '%:z' was no defined until 1.9.3
315
362
  if RUBY_VERSION < '1.9.3'
316
363
  def format_time time