Skip to content

Commit

Permalink
Make allowing repeated anchors optional
Browse files Browse the repository at this point in the history
Add option reuse_anchors as requested by @ingydotnet
  • Loading branch information
perlpunk committed Sep 23, 2021
1 parent e32eb5b commit ece4e90
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 25 deletions.
22 changes: 11 additions & 11 deletions lib/yaml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,22 @@ def load_warning(method):
warnings.warn(message, YAMLLoadWarning, stacklevel=3)

#------------------------------------------------------------------------------
def scan(stream, Loader=Loader):
def scan(stream, Loader=Loader, reuse_anchors=False):
"""
Scan a YAML stream and produce scanning tokens.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
while loader.check_token():
yield loader.get_token()
finally:
loader.dispose()

def parse(stream, Loader=Loader):
def parse(stream, Loader=Loader, reuse_anchors=False):
"""
Parse a YAML stream and produce parsing events.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
while loader.check_event():
yield loader.get_event()
Expand All @@ -82,25 +82,25 @@ def compose(stream, Loader=Loader):
Parse the first YAML document in a stream
and produce the corresponding representation tree.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
return loader.get_single_node()
finally:
loader.dispose()

def compose_all(stream, Loader=Loader):
def compose_all(stream, Loader=Loader, reuse_anchors=False):
"""
Parse all YAML documents in a stream
and produce corresponding representation trees.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
while loader.check_node():
yield loader.get_node()
finally:
loader.dispose()

def load(stream, Loader=None):
def load(stream, Loader=None, reuse_anchors=False):
"""
Parse the first YAML document in a stream
and produce the corresponding Python object.
Expand All @@ -109,13 +109,13 @@ def load(stream, Loader=None):
load_warning('load')
Loader = FullLoader

loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
return loader.get_single_data()
finally:
loader.dispose()

def load_all(stream, Loader=None):
def load_all(stream, Loader=None, reuse_anchors=False):
"""
Parse all YAML documents in a stream
and produce corresponding Python objects.
Expand All @@ -124,7 +124,7 @@ def load_all(stream, Loader=None):
load_warning('load_all')
Loader = FullLoader

loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
while loader.check_data():
yield loader.get_data()
Expand Down
8 changes: 7 additions & 1 deletion lib/yaml/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class ComposerError(MarkedYAMLError):

class Composer:

def __init__(self):
def __init__(self, reuse_anchors=False):
self.anchors = {}
self.reuse_anchors=reuse_anchors

def check_node(self):
# Drop the STREAM-START event.
Expand Down Expand Up @@ -70,6 +71,11 @@ def compose_node(self, parent, index):
return self.anchors[anchor]
event = self.peek_event()
anchor = event.anchor
if anchor is not None:
if anchor in self.anchors and not self.reuse_anchors:
raise ComposerError("found duplicate anchor %r; first occurrence"
% anchor, self.anchors[anchor].start_mark,
"second occurrence", event.start_mark)
self.descend_resolver(parent, index)
if self.check_event(ScalarEvent):
node = self.compose_scalar_node(anchor)
Expand Down
20 changes: 10 additions & 10 deletions lib/yaml/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,41 @@

class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
BaseConstructor.__init__(self)
BaseResolver.__init__(self)

class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
FullConstructor.__init__(self)
Resolver.__init__(self)

class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
SafeConstructor.__init__(self)
Resolver.__init__(self)

class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
Constructor.__init__(self)
Resolver.__init__(self)

Expand All @@ -54,10 +54,10 @@ def __init__(self, stream):
# to ensure backwards compatibility.
class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
Constructor.__init__(self)
Resolver.__init__(self)
4 changes: 2 additions & 2 deletions tests/lib/canonical.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,12 @@ def peek_event(self):
class CanonicalLoader(CanonicalScanner, CanonicalParser,
yaml.composer.Composer, yaml.constructor.Constructor, yaml.resolver.Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
if hasattr(stream, 'read'):
stream = stream.read()
CanonicalScanner.__init__(self, stream)
CanonicalParser.__init__(self)
yaml.composer.Composer.__init__(self)
yaml.composer.Composer.__init__(self, reuse_anchors=reuse_anchors)
yaml.constructor.Constructor.__init__(self)
yaml.resolver.Resolver.__init__(self)

Expand Down
2 changes: 1 addition & 1 deletion tests/lib/test_constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def test_constructor_types(data_filename, code_filename, verbose=False):
native1 = None
native2 = None
try:
native1 = list(yaml.load_all(open(data_filename, 'rb'), Loader=MyLoader))
native1 = list(yaml.load_all(open(data_filename, 'rb'), Loader=MyLoader, reuse_anchors=True))
if len(native1) == 1:
native1 = native1[0]
native2 = _load_code(open(code_filename, 'rb').read())
Expand Down

0 comments on commit ece4e90

Please sign in to comment.