psych 1.2.2 → 1.3.0

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