Skip to content

Commit a6e8a1e

Browse files
author
Greg Houle
committed
unifying interface of Psych
1 parent 89bd0d4 commit a6e8a1e

File tree

3 files changed

+118
-56
lines changed

3 files changed

+118
-56
lines changed

lib/psych.rb

Lines changed: 90 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
@@ -247,7 +248,7 @@ module Psych
247248
# Psych.load("---\n - a\n - b") # => ['a', 'b']
248249
#
249250
# begin
250-
# Psych.load("--- `", "file.txt")
251+
# Psych.load("--- `", filename: "file.txt")
251252
# rescue Psych::SyntaxError => ex
252253
# ex.file # => 'file.txt'
253254
# ex.message # => "(file.txt): found character that cannot start any token"
@@ -259,8 +260,16 @@ module Psych
259260
# Psych.load("---\n foo: bar") # => {"foo"=>"bar"}
260261
# Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
261262
#
262-
def self.load yaml, filename = nil, fallback: false, symbolize_names: false
263-
result = parse(yaml, filename, fallback: fallback)
263+
# Raises a TypeError when `yaml` parameter is NilClass
264+
#
265+
def self.load yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false, symbolize_names: false
266+
if legacy_filename != NOT_GIVEN
267+
warn 'warning: Passing filename with the 2nd argument of Psych.load is deprecated. Use keyword argument like Psych.load(yaml, filename: ...) instead.'
268+
filename = legacy_filename
269+
end
270+
271+
result = parse(yaml, filename: filename)
272+
return fallback unless result
264273
result = result.to_ruby if result
265274
symbolize_names!(result) if symbolize_names
266275
result
@@ -279,27 +288,27 @@ def self.load yaml, filename = nil, fallback: false, symbolize_names: false
279288
# * Hash
280289
#
281290
# Recursive data structures are not allowed by default. Arbitrary classes
282-
# 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
283292
# additive. For example, to allow Date deserialization:
284293
#
285-
# Psych.safe_load(yaml, [Date])
294+
# Psych.safe_load(yaml, whitelist_classes: [Date])
286295
#
287296
# Now the Date class can be loaded in addition to the classes listed above.
288297
#
289-
# Aliases can be explicitly allowed by changing the +aliases+ parameter.
298+
# Aliases can be explicitly allowed by changing the +aliases+ keyword argument.
290299
# For example:
291300
#
292301
# x = []
293302
# x << x
294303
# yaml = Psych.dump x
295304
# Psych.safe_load yaml # => raises an exception
296-
# Psych.safe_load yaml, [], [], true # => loads the aliases
305+
# Psych.safe_load yaml, aliases: true # => loads the aliases
297306
#
298307
# A Psych::DisallowedClass exception will be raised if the yaml contains a
299308
# class that isn't in the whitelist.
300309
#
301310
# A Psych::BadAlias exception will be raised if the yaml contains aliases
302-
# but the +aliases+ parameter is set to false.
311+
# but the +aliases+ keyword argument is set to false.
303312
#
304313
# +filename+ will be used in the exception message if any exception is raised
305314
# while parsing.
@@ -310,18 +319,38 @@ def self.load yaml, filename = nil, fallback: false, symbolize_names: false
310319
# Psych.safe_load("---\n foo: bar") # => {"foo"=>"bar"}
311320
# Psych.safe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
312321
#
313-
def self.safe_load yaml, whitelist_classes = [], whitelist_symbols = [], aliases = false, filename = nil, symbolize_names: false
314-
result = parse(yaml, filename)
315-
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: false, symbolize_names: false
323+
if legacy_whitelist_classes != NOT_GIVEN
324+
warn 'warning: Passing whitelist_classes with the 2nd argument of Psych.safe_load is deprecated. Use keyword argument like Psych.safe_load(yaml, whitelist_classes: ...) instead.'
325+
whitelist_classes = legacy_whitelist_classes
326+
end
327+
328+
if legacy_whitelist_symbols != NOT_GIVEN
329+
warn 'warning: Passing whitelist_symbols with the 3rd argument of Psych.safe_load is deprecated. Use keyword argument like Psych.safe_load(yaml, whitelist_symbols: ...) instead.'
330+
whitelist_symbols = legacy_whitelist_symbols
331+
end
332+
333+
if legacy_aliases != NOT_GIVEN
334+
warn 'warning: Passing aliases with the 4th argument of Psych.safe_load is deprecated. Use keyword argument like Psych.safe_load(yaml, aliases: ...) instead.'
335+
aliases = legacy_aliases
336+
end
337+
338+
if legacy_filename != NOT_GIVEN
339+
warn 'warning: Passing filename with the 5th argument of Psych.safe_load is deprecated. Use keyword argument like Psych.safe_load(yaml, filename: ...) instead.'
340+
filename = legacy_filename
341+
end
342+
343+
result = parse(yaml, filename: filename)
344+
return fallback unless result
316345

317346
class_loader = ClassLoader::Restricted.new(whitelist_classes.map(&:to_s),
318347
whitelist_symbols.map(&:to_s))
319348
scanner = ScalarScanner.new class_loader
320-
if aliases
321-
visitor = Visitors::ToRuby.new scanner, class_loader
322-
else
323-
visitor = Visitors::NoAliasRuby.new scanner, class_loader
324-
end
349+
visitor = if aliases
350+
Visitors::ToRuby.new scanner, class_loader
351+
else
352+
Visitors::NoAliasRuby.new scanner, class_loader
353+
end
325354
result = visitor.accept result
326355
symbolize_names!(result) if symbolize_names
327356
result
@@ -339,28 +368,40 @@ def self.safe_load yaml, whitelist_classes = [], whitelist_symbols = [], aliases
339368
# Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Document:0x00>
340369
#
341370
# begin
342-
# Psych.parse("--- `", "file.txt")
371+
# Psych.parse("--- `", filename: "file.txt")
343372
# rescue Psych::SyntaxError => ex
344373
# ex.file # => 'file.txt'
345374
# ex.message # => "(file.txt): found character that cannot start any token"
346375
# end
347376
#
348377
# See Psych::Nodes for more information about YAML AST.
349-
def self.parse yaml, filename = nil, fallback: false
350-
parse_stream(yaml, filename) do |node|
378+
def self.parse yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: NOT_GIVEN
379+
if legacy_filename != NOT_GIVEN
380+
warn 'warning: Passing filename with the 2nd argument of Psych.parse is deprecated. Use keyword argument like Psych.parse(yaml, filename: ...) instead.'
381+
filename = legacy_filename
382+
end
383+
384+
parse_stream(yaml, filename: filename) do |node|
351385
return node
352386
end
353-
fallback
387+
388+
if fallback != NOT_GIVEN
389+
warn 'warning: Passing the `fallback` keyword argument of Psych.parse is deprecated.'
390+
fallback
391+
else
392+
false
393+
end
354394
end
355395

356396
###
357397
# Parse a file at +filename+. Returns the Psych::Nodes::Document.
358398
#
359399
# Raises a Psych::SyntaxError when a YAML syntax error is detected.
360-
def self.parse_file filename
361-
File.open filename, 'r:bom|utf-8' do |f|
362-
parse f, filename
400+
def self.parse_file filename, fallback: false
401+
result = File.open filename, 'r:bom|utf-8' do |f|
402+
parse f, filename: filename
363403
end
404+
result || fallback
364405
end
365406

366407
###
@@ -389,14 +430,21 @@ def self.parser
389430
# end
390431
#
391432
# begin
392-
# Psych.parse_stream("--- `", "file.txt")
433+
# Psych.parse_stream("--- `", filename: "file.txt")
393434
# rescue Psych::SyntaxError => ex
394435
# ex.file # => 'file.txt'
395436
# ex.message # => "(file.txt): found character that cannot start any token"
396437
# end
397438
#
439+
# Raises a TypeError when NilClass is passed.
440+
#
398441
# See Psych::Nodes for more information about YAML AST.
399-
def self.parse_stream yaml, filename = nil, &block
442+
def self.parse_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, &block
443+
if legacy_filename != NOT_GIVEN
444+
warn 'warning: Passing filename with the 2nd argument of Psych.parse_stream is deprecated. Use keyword argument like Psych.parse_stream(yaml, filename: ...) instead.'
445+
filename = legacy_filename
446+
end
447+
400448
if block_given?
401449
parser = Psych::Parser.new(Handlers::DocumentStream.new(&block))
402450
parser.parse yaml, filename
@@ -497,14 +545,21 @@ def self.to_json object
497545
# end
498546
# list # => ['foo', 'bar']
499547
#
500-
def self.load_stream yaml, filename = nil
501-
if block_given?
502-
parse_stream(yaml, filename) do |node|
503-
yield node.to_ruby
504-
end
505-
else
506-
parse_stream(yaml, filename).children.map { |child| child.to_ruby }
548+
def self.load_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false
549+
if legacy_filename != NOT_GIVEN
550+
warn 'warning: Passing filename with the 2nd argument of Psych.load_stream is deprecated. Use keyword argument like Psych.load_stream(yaml, filename: ...) instead.'
551+
filename = legacy_filename
507552
end
553+
554+
result = if block_given?
555+
parse_stream(yaml, filename: filename) do |node|
556+
yield node.to_ruby
557+
end
558+
else
559+
parse_stream(yaml, filename: filename).children.map(&:to_ruby)
560+
end
561+
562+
result || fallback
508563
end
509564

510565
###
@@ -513,7 +568,7 @@ def self.load_stream yaml, filename = nil
513568
# the specified +fallback+ return value, which defaults to +false+.
514569
def self.load_file filename, fallback: false
515570
File.open(filename, 'r:bom|utf-8') { |f|
516-
self.load f, filename, fallback: FALLBACK.new(fallback)
571+
self.load f, filename: filename, fallback: fallback
517572
}
518573
end
519574

test/psych/test_exception.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ 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
3636
end
@@ -43,7 +43,7 @@ def test_psych_parse_stream_takes_file
4343
assert_match '(<unknown>)', ex.message
4444

4545
ex = assert_raises(Psych::SyntaxError) do
46-
Psych.parse_stream '--- `', 'omg!'
46+
Psych.parse_stream '--- `', filename: 'omg!'
4747
end
4848
assert_equal 'omg!', ex.file
4949
assert_match 'omg!', ex.message
@@ -57,7 +57,7 @@ def test_load_stream_takes_file
5757
assert_match '(<unknown>)', ex.message
5858

5959
ex = assert_raises(Psych::SyntaxError) do
60-
Psych.load_stream '--- `', 'omg!'
60+
Psych.load_stream '--- `', filename: 'omg!'
6161
end
6262
assert_equal 'omg!', ex.file
6363
end
@@ -94,7 +94,7 @@ def test_psych_parse_takes_file
9494
assert_nil ex.file
9595

9696
ex = assert_raises(Psych::SyntaxError) do
97-
Psych.parse '--- `', 'omg!'
97+
Psych.parse '--- `', filename: 'omg!'
9898
end
9999
assert_match 'omg!', ex.message
100100
end

test/psych/test_safe_load.rb

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,62 +22,69 @@ def test_no_recursion
2222
def test_explicit_recursion
2323
x = []
2424
x << x
25-
assert_equal(x, Psych.safe_load(Psych.dump(x), [], [], true))
25+
assert_equal(x, Psych.safe_load(Psych.dump(x), whitelist_classes: [], whitelist_symbols: [], aliases: true))
2626
end
2727

2828
def test_symbol_whitelist
2929
yml = Psych.dump :foo
3030
assert_raises(Psych::DisallowedClass) do
3131
Psych.safe_load yml
3232
end
33-
assert_equal(:foo, Psych.safe_load(yml, [Symbol], [:foo]))
33+
assert_equal(
34+
:foo,
35+
Psych.safe_load(
36+
yml,
37+
whitelist_classes: [Symbol],
38+
whitelist_symbols: [:foo]
39+
)
40+
)
3441
end
3542

3643
def test_symbol
3744
assert_raises(Psych::DisallowedClass) do
3845
assert_safe_cycle :foo
3946
end
4047
assert_raises(Psych::DisallowedClass) do
41-
Psych.safe_load '--- !ruby/symbol foo', []
48+
Psych.safe_load '--- !ruby/symbol foo', whitelist_classes: []
4249
end
43-
assert_safe_cycle :foo, [Symbol]
44-
assert_safe_cycle :foo, %w{ Symbol }
45-
assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', [Symbol])
50+
assert_safe_cycle :foo, whitelist_classes: [Symbol]
51+
assert_safe_cycle :foo, whitelist_classes: %w{ Symbol }
52+
assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', whitelist_classes: [Symbol])
4653
end
4754

4855
def test_foo
4956
assert_raises(Psych::DisallowedClass) do
50-
Psych.safe_load '--- !ruby/object:Foo {}', [Foo]
57+
Psych.safe_load '--- !ruby/object:Foo {}', whitelist_classes: [Foo]
5158
end
5259
assert_raises(Psych::DisallowedClass) do
5360
assert_safe_cycle Foo.new
5461
end
55-
assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), [Foo]))
62+
assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), whitelist_classes: [Foo]))
5663
end
5764

5865
X = Struct.new(:x)
5966
def test_struct_depends_on_sym
60-
assert_safe_cycle(X.new, [X, Symbol])
67+
assert_safe_cycle(X.new, whitelist_classes: [X, Symbol])
6168
assert_raises(Psych::DisallowedClass) do
62-
cycle X.new, [X]
69+
cycle X.new, whitelist_classes: [X]
6370
end
6471
end
6572

6673
def test_anon_struct
67-
assert Psych.safe_load(<<-eoyml, [Struct, Symbol])
74+
assert Psych.safe_load(<<-eoyml, whitelist_classes: [Struct, Symbol])
6875
--- !ruby/struct
6976
foo: bar
7077
eoyml
7178

7279
assert_raises(Psych::DisallowedClass) do
73-
Psych.safe_load(<<-eoyml, [Struct])
80+
Psych.safe_load(<<-eoyml, whitelist_classes: [Struct])
7481
--- !ruby/struct
7582
foo: bar
7683
eoyml
7784
end
7885

7986
assert_raises(Psych::DisallowedClass) do
80-
Psych.safe_load(<<-eoyml, [Symbol])
87+
Psych.safe_load(<<-eoyml, whitelist_classes: [Symbol])
8188
--- !ruby/struct
8289
foo: bar
8390
eoyml
@@ -86,12 +93,12 @@ def test_anon_struct
8693

8794
private
8895

89-
def cycle object, whitelist = []
90-
Psych.safe_load(Psych.dump(object), whitelist)
96+
def cycle object, whitelist_classes: []
97+
Psych.safe_load(Psych.dump(object), whitelist_classes: whitelist_classes)
9198
end
9299

93-
def assert_safe_cycle object, whitelist = []
94-
other = cycle object, whitelist
100+
def assert_safe_cycle object, whitelist_classes: []
101+
other = cycle object, whitelist_classes: whitelist_classes
95102
assert_equal object, other
96103
end
97104
end

0 commit comments

Comments
 (0)