Skip to content

Commit

Permalink
Merge pull request #24 from fonttools/escape-newlines-option
Browse files Browse the repository at this point in the history
[writer] add escape_newlines=True option to dump/dumps
  • Loading branch information
anthrotype authored Nov 2, 2023
2 parents c9acd40 + d502f28 commit 4f8a953
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 10 deletions.
9 changes: 8 additions & 1 deletion src/openstep_plist/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def main(args=None):
"-j", "--json", help="use json to serialize", action="store_true", default=False
)
parser.add_argument("-i", "--indent", help="indentation level", type=int, default=2)
parser.add_argument(
"--no-escape-newlines", dest="escape_newlines", action="store_false"
)
args = parser.parse_args(args)

if not args.glyphs:
Expand All @@ -58,7 +61,11 @@ def parse(fp, dict_type=dict):
if args.glyphs:
from glyphsLib.writer import dump
else:
dump = partial(openstep_plist.dump, indent=args.indent)
dump = partial(
openstep_plist.dump,
indent=args.indent,
escape_newlines=args.escape_newlines,
)

with open(args.infile, "r", encoding="utf-8") as fp:
data = parse(fp)
Expand Down
20 changes: 14 additions & 6 deletions src/openstep_plist/writer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,20 @@ cdef class Writer:
cdef unicode indent
cdef int current_indent_level
cdef bint single_line_tuples
cdef bint escape_newlines

def __cinit__(
self,
bint unicode_escape=True,
int float_precision=6,
indent=None,
bint single_line_tuples=False,
bint escape_newlines=True,
):
self.dest = new vector[Py_UCS4]()
self.unicode_escape = unicode_escape
self.float_precision = float_precision
self.escape_newlines = escape_newlines

if indent is not None:
if isinstance(indent, basestring):
Expand Down Expand Up @@ -225,14 +228,17 @@ cdef class Writer:
unsigned long ch
Py_ssize_t base_length = dest.size()
Py_ssize_t new_length = 0
bint escape_newlines = self.escape_newlines

while curr < end:
ch = curr[0]
if ch == c'\t' or ch == c' ':
if ch == c'\t' or ch == c' ' or (ch == c'\n' and not escape_newlines):
new_length += 1
elif (
ch == c'\n' or ch == c'\\' or ch == c'"' or ch == c'\a'
ch == c'\\' or ch == c'"' or ch == c'\a'
or ch == c'\b' or ch == c'\v' or ch == c'\f' or ch == c'\r'
) or (
ch == c'\n' and escape_newlines
):
new_length += 2
else:
Expand All @@ -258,10 +264,10 @@ cdef class Writer:
curr = s
while curr < end:
ch = curr[0]
if ch == c'\t' or ch == c' ':
if ch == c'\t' or ch == c' ' or (ch == c'\n' and not escape_newlines):
ptr[0] = ch
ptr += 1
elif ch == c'\n':
elif ch == c'\n' and escape_newlines:
ptr[0] = c'\\'; ptr[1] = c'n'; ptr += 2
elif ch == c'\a':
ptr[0] = c'\\'; ptr[1] = c'a'; ptr += 2
Expand Down Expand Up @@ -584,24 +590,26 @@ cdef class Writer:


def dumps(obj, bint unicode_escape=True, int float_precision=6, indent=None,
single_line_tuples=False):
bint single_line_tuples=False, bint escape_newlines=True):
w = Writer(
unicode_escape=unicode_escape,
float_precision=float_precision,
indent=indent,
single_line_tuples=single_line_tuples,
escape_newlines=escape_newlines,
)
w.write(obj)
return w.getvalue()


def dump(obj, fp, bint unicode_escape=True, int float_precision=6, indent=None,
single_line_tuples=False):
bint single_line_tuples=False, bint escape_newlines=True):
w = Writer(
unicode_escape=unicode_escape,
float_precision=float_precision,
indent=indent,
single_line_tuples=single_line_tuples,
escape_newlines=escape_newlines,
)
w.write(obj)
w.dump(fp)
31 changes: 28 additions & 3 deletions tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from openstep_plist.writer import Writer, string_needs_quotes
from io import StringIO, BytesIO
from collections import OrderedDict
from textwrap import dedent
import string
import random
import pytest
Expand Down Expand Up @@ -57,6 +58,11 @@ def test_quoted_string(self, string, expected):
w.write(string)
assert w.getvalue() == expected

def test_quoted_string_dont_escape_newlines(self):
w = Writer(escape_newlines=False)
w.write("a\n\n\nbc")
assert w.getvalue() == '"a\n\n\nbc"'

def test_quoted_string_no_unicode_escape(self):
w = Writer(unicode_escape=False)
w.write("\u0410") == 3
Expand Down Expand Up @@ -211,17 +217,36 @@ def test_dumps():
'{a = 1; b = 3; "c d" = (33, 44); '
"e = (<66676869 6C6D6E6F>, <70717273 7475767A>);}"
)
assert openstep_plist.dumps(
{
"features": dedent(
"""\
sub periodcentered by periodcentered.case;
sub bullet by bullet.case;
"""
),
},
escape_newlines=False,
) == (
'{features = "sub periodcentered by periodcentered.case;\n'
'sub bullet by bullet.case;\n'
'";}'
)


def test_dump():
plist = [1, b"2", {3: (4, "5", "\U0001F4A9")}]
plist = [1, b"2", {3: (4, "5\n6", "\U0001F4A9")}]
fp = StringIO()
openstep_plist.dump(plist, fp)
assert fp.getvalue() == '(1, <32>, {"3" = (4, "5", "\\UD83D\\UDCA9");})'
assert fp.getvalue() == '(1, <32>, {"3" = (4, "5\\n6", "\\UD83D\\UDCA9");})'

fp = BytesIO()
openstep_plist.dump(plist, fp, unicode_escape=False)
assert fp.getvalue() == b'(1, <32>, {"3" = (4, "5", "\xf0\x9f\x92\xa9");})'
assert fp.getvalue() == b'(1, <32>, {"3" = (4, "5\\n6", "\xf0\x9f\x92\xa9");})'

fp = BytesIO()
openstep_plist.dump(plist, fp, escape_newlines=False, unicode_escape=False)
assert fp.getvalue() == b'(1, <32>, {"3" = (4, "5\n6", "\xf0\x9f\x92\xa9");})'

with pytest.raises(AttributeError):
openstep_plist.dump(plist, object())
Expand Down

0 comments on commit 4f8a953

Please sign in to comment.