Skip to content

Commit c79ed44

Browse files
authored
Merge pull request #358 from sorryeh/unify_psych_interface
Unify Psych's API
2 parents 1cc3894 + 4d4439d commit c79ed44

File tree

3 files changed

+165
-47
lines changed

3 files changed

+165
-47
lines changed

lib/psych.rb

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,9 @@
230230
module Psych
231231
# The version of libyaml Psych is using
232232
LIBYAML_VERSION = Psych.libyaml_version.join '.'
233-
234-
FALLBACK = Struct.new :to_ruby # :nodoc:
233+
# Deprecation guard
234+
NOT_GIVEN = Object.new
235+
private_constant :NOT_GIVEN
235236

236237
###
237238
# Load +yaml+ in to a Ruby data structure. If multiple documents are
@@ -248,7 +249,7 @@ module Psych
248249
# Psych.load("---\n - a\n - b") # => ['a', 'b']
249250
#
250251
# begin
251-
# Psych.load("--- `", "file.txt")
252+
# Psych.load("--- `", filename: "file.txt")
252253
# rescue Psych::SyntaxError => ex
253254
# ex.file # => 'file.txt'
254255
# ex.message # => "(file.txt): found character that cannot start any token"
@@ -260,8 +261,15 @@ module Psych
260261
# Psych.load("---\n foo: bar") # => {"foo"=>"bar"}
261262
# Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
262263
#
263-
def self.load yaml, filename = nil, fallback: false, symbolize_names: false
264-
result = parse(yaml, filename, fallback: FALLBACK.new(fallback))
264+
# Raises a TypeError when `yaml` parameter is NilClass
265+
#
266+
def self.load yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false, symbolize_names: false
267+
if legacy_filename != NOT_GIVEN
268+
filename = legacy_filename
269+
end
270+
271+
result = parse(yaml, filename: filename)
272+
return fallback unless result
265273
result = result.to_ruby if result
266274
symbolize_names!(result) if symbolize_names
267275
result
@@ -280,27 +288,27 @@ def self.load yaml, filename = nil, fallback: false, symbolize_names: false
280288
# * Hash
281289
#
282290
# Recursive data structures are not allowed by default. Arbitrary classes
283-
# can be allowed by adding those classes to the +whitelist+. They are
291+
# can be allowed by adding those classes to the +whitelist_classes+ keyword argument. They are
284292
# additive. For example, to allow Date deserialization:
285293
#
286-
# Psych.safe_load(yaml, [Date])
294+
# Psych.safe_load(yaml, whitelist_classes: [Date])
287295
#
288296
# Now the Date class can be loaded in addition to the classes listed above.
289297
#
290-
# Aliases can be explicitly allowed by changing the +aliases+ parameter.
298+
# Aliases can be explicitly allowed by changing the +aliases+ keyword argument.
291299
# For example:
292300
#
293301
# x = []
294302
# x << x
295303
# yaml = Psych.dump x
296304
# Psych.safe_load yaml # => raises an exception
297-
# Psych.safe_load yaml, [], [], true # => loads the aliases
305+
# Psych.safe_load yaml, aliases: true # => loads the aliases
298306
#
299307
# A Psych::DisallowedClass exception will be raised if the yaml contains a
300308
# class that isn't in the whitelist.
301309
#
302310
# A Psych::BadAlias exception will be raised if the yaml contains aliases
303-
# but the +aliases+ parameter is set to false.
311+
# but the +aliases+ keyword argument is set to false.
304312
#
305313
# +filename+ will be used in the exception message if any exception is raised
306314
# while parsing.
@@ -311,18 +319,34 @@ def self.load yaml, filename = nil, fallback: false, symbolize_names: false
311319
# Psych.safe_load("---\n foo: bar") # => {"foo"=>"bar"}
312320
# Psych.safe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
313321
#
314-
def self.safe_load yaml, whitelist_classes = [], whitelist_symbols = [], aliases = false, filename = nil, symbolize_names: false
315-
result = parse(yaml, filename)
316-
return unless result
322+
def self.safe_load yaml, legacy_whitelist_classes = NOT_GIVEN, legacy_whitelist_symbols = NOT_GIVEN, legacy_aliases = NOT_GIVEN, legacy_filename = NOT_GIVEN, whitelist_classes: [], whitelist_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false
323+
if legacy_whitelist_classes != NOT_GIVEN
324+
whitelist_classes = legacy_whitelist_classes
325+
end
326+
327+
if legacy_whitelist_symbols != NOT_GIVEN
328+
whitelist_symbols = legacy_whitelist_symbols
329+
end
330+
331+
if legacy_aliases != NOT_GIVEN
332+
aliases = legacy_aliases
333+
end
334+
335+
if legacy_filename != NOT_GIVEN
336+
filename = legacy_filename
337+
end
338+
339+
result = parse(yaml, filename: filename)
340+
return fallback unless result
317341

318342
class_loader = ClassLoader::Restricted.new(whitelist_classes.map(&:to_s),
319343
whitelist_symbols.map(&:to_s))
320344
scanner = ScalarScanner.new class_loader
321-
if aliases
322-
visitor = Visitors::ToRuby.new scanner, class_loader
323-
else
324-
visitor = Visitors::NoAliasRuby.new scanner, class_loader
325-
end
345+
visitor = if aliases
346+
Visitors::ToRuby.new scanner, class_loader
347+
else
348+
Visitors::NoAliasRuby.new scanner, class_loader
349+
end
326350
result = visitor.accept result
327351
symbolize_names!(result) if symbolize_names
328352
result
@@ -340,28 +364,38 @@ def self.safe_load yaml, whitelist_classes = [], whitelist_symbols = [], aliases
340364
# Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Document:0x00>
341365
#
342366
# begin
343-
# Psych.parse("--- `", "file.txt")
367+
# Psych.parse("--- `", filename: "file.txt")
344368
# rescue Psych::SyntaxError => ex
345369
# ex.file # => 'file.txt'
346370
# ex.message # => "(file.txt): found character that cannot start any token"
347371
# end
348372
#
349373
# See Psych::Nodes for more information about YAML AST.
350-
def self.parse yaml, filename = nil, fallback: false
351-
parse_stream(yaml, filename) do |node|
374+
def self.parse yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: NOT_GIVEN
375+
if legacy_filename != NOT_GIVEN
376+
filename = legacy_filename
377+
end
378+
379+
parse_stream(yaml, filename: filename) do |node|
352380
return node
353381
end
354-
fallback
382+
383+
if fallback != NOT_GIVEN
384+
fallback
385+
else
386+
false
387+
end
355388
end
356389

357390
###
358391
# Parse a file at +filename+. Returns the Psych::Nodes::Document.
359392
#
360393
# Raises a Psych::SyntaxError when a YAML syntax error is detected.
361-
def self.parse_file filename
362-
File.open filename, 'r:bom|utf-8' do |f|
363-
parse f, filename
394+
def self.parse_file filename, fallback: false
395+
result = File.open filename, 'r:bom|utf-8' do |f|
396+
parse f, filename: filename
364397
end
398+
result || fallback
365399
end
366400

367401
###
@@ -390,14 +424,20 @@ def self.parser
390424
# end
391425
#
392426
# begin
393-
# Psych.parse_stream("--- `", "file.txt")
427+
# Psych.parse_stream("--- `", filename: "file.txt")
394428
# rescue Psych::SyntaxError => ex
395429
# ex.file # => 'file.txt'
396430
# ex.message # => "(file.txt): found character that cannot start any token"
397431
# end
398432
#
433+
# Raises a TypeError when NilClass is passed.
434+
#
399435
# See Psych::Nodes for more information about YAML AST.
400-
def self.parse_stream yaml, filename = nil, &block
436+
def self.parse_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, &block
437+
if legacy_filename != NOT_GIVEN
438+
filename = legacy_filename
439+
end
440+
401441
if block_given?
402442
parser = Psych::Parser.new(Handlers::DocumentStream.new(&block))
403443
parser.parse yaml, filename
@@ -498,14 +538,21 @@ def self.to_json object
498538
# end
499539
# list # => ['foo', 'bar']
500540
#
501-
def self.load_stream yaml, filename = nil
502-
if block_given?
503-
parse_stream(yaml, filename) do |node|
504-
yield node.to_ruby
505-
end
506-
else
507-
parse_stream(yaml, filename).children.map { |child| child.to_ruby }
541+
def self.load_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: []
542+
if legacy_filename != NOT_GIVEN
543+
filename = legacy_filename
508544
end
545+
546+
result = if block_given?
547+
parse_stream(yaml, filename: filename) do |node|
548+
yield node.to_ruby
549+
end
550+
else
551+
parse_stream(yaml, filename: filename).children.map(&:to_ruby)
552+
end
553+
554+
return fallback if result.is_a?(Array) && result.empty?
555+
result
509556
end
510557

511558
###
@@ -514,7 +561,7 @@ def self.load_stream yaml, filename = nil
514561
# the specified +fallback+ return value, which defaults to +false+.
515562
def self.load_file filename, fallback: false
516563
File.open(filename, 'r:bom|utf-8') { |f|
517-
self.load f, filename, fallback: fallback
564+
self.load f, filename: filename, fallback: fallback
518565
}
519566
end
520567

test/psych/test_exception.rb

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ def test_load_takes_file
3030
assert_nil ex.file
3131

3232
ex = assert_raises(Psych::SyntaxError) do
33-
Psych.load '--- `', 'meow'
33+
Psych.load '--- `', filename: 'meow'
3434
end
3535
assert_equal 'meow', ex.file
36+
37+
# deprecated interface
38+
ex = assert_raises(Psych::SyntaxError) do
39+
Psych.load '--- `', 'deprecated'
40+
end
41+
assert_equal 'deprecated', ex.file
3642
end
3743

3844
def test_psych_parse_stream_takes_file
@@ -43,7 +49,7 @@ def test_psych_parse_stream_takes_file
4349
assert_match '(<unknown>)', ex.message
4450

4551
ex = assert_raises(Psych::SyntaxError) do
46-
Psych.parse_stream '--- `', 'omg!'
52+
Psych.parse_stream '--- `', filename: 'omg!'
4753
end
4854
assert_equal 'omg!', ex.file
4955
assert_match 'omg!', ex.message
@@ -57,9 +63,15 @@ def test_load_stream_takes_file
5763
assert_match '(<unknown>)', ex.message
5864

5965
ex = assert_raises(Psych::SyntaxError) do
60-
Psych.load_stream '--- `', 'omg!'
66+
Psych.load_stream '--- `', filename: 'omg!'
6167
end
6268
assert_equal 'omg!', ex.file
69+
70+
# deprecated interface
71+
ex = assert_raises(Psych::SyntaxError) do
72+
Psych.load_stream '--- `', 'deprecated'
73+
end
74+
assert_equal 'deprecated', ex.file
6375
end
6476

6577
def test_parse_file_exception
@@ -94,9 +106,15 @@ def test_psych_parse_takes_file
94106
assert_nil ex.file
95107

96108
ex = assert_raises(Psych::SyntaxError) do
97-
Psych.parse '--- `', 'omg!'
109+
Psych.parse '--- `', filename: 'omg!'
98110
end
99111
assert_match 'omg!', ex.message
112+
113+
# deprecated interface
114+
ex = assert_raises(Psych::SyntaxError) do
115+
Psych.parse '--- `', 'deprecated'
116+
end
117+
assert_match 'deprecated', ex.message
100118
end
101119

102120
def test_attributes

0 commit comments

Comments
 (0)