Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 6 additions & 15 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,10 @@ python:
- "2.7"
- "3.6"

install:
- pip install -r requirements_dev.txt

script:
- bin/py.test -vvs tests

notifications:
irc:
channels:
- "chat.freenode.net#aboutcode"
on_success: change
on_failure: always
use_notice: true
skip_join: true
template:
- "%{repository_slug}#%{build_number} (%{branch} - %{commit} : %{author}): %{message} : %{build_url}"
- pip install pyyaml==3.13
- pip install -r requirements_dev.txt
- py.test -vvs tests
- pip uninstall -y pyyaml
- pip install pyyaml
- py.test -vvs tests
16 changes: 6 additions & 10 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
version: '{build}'

install:
- configure etc/conf/dev

build: off

test_script:
- set
- pip install pyyaml==3.13
- pip install -r requirements_dev.txt
# test also on latest version
- py.test -vvs tests
- pip uninstall -y pyyaml
- pip install pyyaml
- py.test -vvs tests

on_success:
- "python etc/scripts/irc-notify.py aboutcode [{project_name}:{branch}] {short_commit}: \"{message}\" ({author}) {color_green}Succeeded,Details: {build_url},Commit: {commit_url}"

on_failure:
- "python etc/scripts/irc-notify.py aboutcode [{project_name}:{branch}] {short_commit}: \"{message}\" ({author}) {color_red}Failed,Details: {build_url},Commit: {commit_url}"
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

setup(
name='saneyaml',
version='0.3',
version='0.4',
license='Apache-2.0',
description='Dump readable YAML and load safely any YAML preserving '
'ordering and avoiding surprises of type conversions. '
Expand Down Expand Up @@ -50,6 +50,6 @@
'yaml', 'block', 'flow', 'readable',
],
install_requires=[
'PyYAML >= 3.11, <= 3.13',
'PyYAML',
],
)
70 changes: 61 additions & 9 deletions src/saneyaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from collections import OrderedDict
from functools import partial
import re
import sys

import yaml
Expand Down Expand Up @@ -127,7 +128,8 @@ def ordered_loader(self, node, check_dupe=False):
# This avoid unwanted type conversions for unquoted strings and the resulting
# content damaging. This overrides the implicit resolvers. Callers must handle
# type conversion explicitly from unicode to other types in the loaded objects.

# NOTE: we are still using the built-in loader for booleans. It will recognize
# yes/no as a boolean.
BaseSaneLoader.add_constructor('tag:yaml.org,2002:str', BaseSaneLoader.string_loader)
BaseSaneLoader.add_constructor('tag:yaml.org,2002:null', BaseSaneLoader.string_loader)
BaseSaneLoader.add_constructor('tag:yaml.org,2002:boolean', BaseSaneLoader.string_loader)
Expand Down Expand Up @@ -166,6 +168,8 @@ class DupeKeySaneLoader(BaseSaneLoader):
# Dumping
###############################################################################

WIDTH = 90

def dump(obj, indent=2, encoding=None):
"""
Return a safe and sane YAML string representation from `obj`.
Expand All @@ -187,7 +191,7 @@ def dump(obj, indent=2, encoding=None):
# anything above 2 will yield weird vertical indents on lists and maps
indent=indent,
# make this 80ish
width=90,
width=WIDTH,
# posix LF
line_break='\n',
# no --- and ...
Expand All @@ -212,7 +216,7 @@ def __init__(self, stream,
canonical=None, indent=None, width=None,
allow_unicode=None, line_break=None,
encoding=None, explicit_start=None, explicit_end=None,
version=None, tags=None):
version=None, tags=None, sort_keys=False, **kwargs):
IndentingEmitter.__init__(self, stream, canonical=canonical,
indent=indent, width=width,
allow_unicode=allow_unicode, line_break=line_break)
Expand Down Expand Up @@ -245,7 +249,7 @@ def null_dumper(self, value): # NOQA
"""
Always dump nulls as empty string.
"""
return self.represent_scalar('tag:yaml.org,2002:null', '')
return self.represent_scalar('tag:yaml.org,2002:str', '', style=None)

def string_dumper(self, value):
"""
Expand All @@ -254,20 +258,50 @@ def string_dumper(self, value):
"""
tag = 'tag:yaml.org,2002:str'
style = None

if value is None:
return ''

if isinstance(value, bool):
value = 'yes' if value else 'no'
style = ''

if isinstance(value, float):
style = "'"

if isinstance(value, int):
value = unicode(value)
style = ''

if isinstance(value, bytes):
value = value.decode('utf-8')
elif isinstance(value, int):
value = unicode(value)
elif not isinstance(value, unicode):
value = repr(value)

# do not quote integer strings
if value.isdigit() and unicode(int(value)) == value:
style = None
tag = 'tag:yaml.org,2002:int'
if value.isdigit():
if value.lstrip('0') == value:
style = ''
else:
# things such as 012 needs to be quoted
style = "'"

# quote things that could be mistakenly loaded as date
if is_iso_date(value):
style = "'"

# quote things that could be mistakenly loaded as float such as version numbers
if value !='.' and len(value.split('.')) == 2 and all(c in '0123456789.' for c in value):
style = "'"

if '\n' in value:
elif value == 'null':
style = "'"

# if '\n' in value or len(value) > WIDTH:
# literal_style for multilines or long
elif '\n' in value:
# literal_style for multilines
style = '|'

Expand All @@ -282,12 +316,30 @@ def boolean_dumper(self, value):
return self.represent_scalar('tag:yaml.org,2002:bool', value, style=None)


def is_float(s):
"""
Return True if this is a float with trailing zeroes such as `1.20`
"""
try:
float(s)
return s.startswith('0') or s.endswith('0')
except:
return False


# Return True if s is an iso date such as `2019-12-12`
is_iso_date = re.compile(r'19|20[0-9]{2}-[0-1][0-9]-[0-3]?[1-9]').match


SaneDumper.add_representer(int, SaneDumper.string_dumper)
SaneDumper.add_representer(odict, SaneDumper.ordered_dumper)
SaneDumper.add_representer(OrderedDict, SaneDumper.ordered_dumper)
SaneDumper.add_representer(type(None), SaneDumper.null_dumper)
SaneDumper.add_representer(bool, SaneDumper.boolean_dumper)
SaneDumper.add_representer(bool, SaneDumper.string_dumper)
SaneDumper.add_representer(bytes, SaneDumper.string_dumper)
SaneDumper.add_representer(str, SaneDumper.string_dumper)
SaneDumper.add_representer(unicode, SaneDumper.string_dumper)
SaneDumper.add_representer(float, SaneDumper.string_dumper)

SaneDumper.yaml_implicit_resolvers = {}
SaneDumper.yaml_path_resolvers = {}
4 changes: 2 additions & 2 deletions tests/data/yamls/corner-cases.yml.expected.yaml.dump
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
about_resource: 'null'
name: '123.34'
about_resource_path: '012'
? ''
?
: - this
- 'null'
- '2012-03-12'
that: ''
that: