aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralandonovan <adonovan@google.com>2017-11-28 22:08:16 -0500
committerGitHub <noreply@github.com>2017-11-28 22:08:16 -0500
commit71da662e0b778bcc80721ed7b372d6d45417b300 (patch)
tree0d505a50b5f430126f6f7b5deea5295e96956793
parent1b9d0e7119bc2aca0cd6be14ea0b8b184dc12bd2 (diff)
downloadstarlark-go-71da662e0b778bcc80721ed7b372d6d45417b300.tar.gz
string.format: handle "}}" and "{-0}" correctly (#46)
Fixes #18
-rw-r--r--library.go32
-rw-r--r--testdata/string.sky12
2 files changed, 38 insertions, 6 deletions
diff --git a/library.go b/library.go
index 1da0547..3503dc3 100644
--- a/library.go
+++ b/library.go
@@ -1571,13 +1571,29 @@ func string_format(fnname string, recv_ Value, args Tuple, kwargs []Tuple) (Valu
var buf bytes.Buffer
index := 0
for {
- // TODO(adonovan): replace doubled "}}" with "}" and reject single '}'.
+ literal := format
i := strings.IndexByte(format, '{')
+ if i >= 0 {
+ literal = format[:i]
+ }
+
+ // Replace "}}" with "}" in non-field portion, rejecting a lone '}'.
+ for {
+ j := strings.IndexByte(literal, '}')
+ if j < 0 {
+ buf.WriteString(literal)
+ break
+ }
+ if len(literal) == j+1 || literal[j+1] != '}' {
+ return nil, fmt.Errorf("single '}' in format")
+ }
+ buf.WriteString(literal[:j+1])
+ literal = literal[j+2:]
+ }
+
if i < 0 {
- buf.WriteString(format)
- break
+ break // end of format string
}
- buf.WriteString(format[:i])
if i+1 < len(format) && format[i+1] == '{' {
// "{{" means a literal '{'
@@ -1632,7 +1648,7 @@ func string_format(fnname string, recv_ Value, args Tuple, kwargs []Tuple) (Valu
}
arg = args[index]
index++
- } else if num, err := strconv.Atoi(name); err == nil {
+ } else if num, err := strconv.Atoi(name); err == nil && !strings.HasPrefix(name, "-") {
// positional argument
if auto {
return nil, fmt.Errorf("cannot switch from automatic field numbering to manual field specification")
@@ -1652,13 +1668,17 @@ func string_format(fnname string, recv_ Value, args Tuple, kwargs []Tuple) (Valu
}
}
if arg == nil {
- // Skylark does not support Python's x.y or a[i] syntaxes.
+ // Skylark does not support Python's x.y or a[i] syntaxes,
+ // or nested use of {...}.
if strings.Contains(name, ".") {
return nil, fmt.Errorf("attribute syntax x.y is not supported in replacement fields: %s", name)
}
if strings.Contains(name, "[") {
return nil, fmt.Errorf("element syntax a[i] is not supported in replacement fields: %s", name)
}
+ if strings.Contains(name, "{") {
+ return nil, fmt.Errorf("nested replacement fields not supported")
+ }
return nil, fmt.Errorf("keyword %s not found", name)
}
}
diff --git a/testdata/string.sky b/testdata/string.sky
index 1df8ee9..f92dadf 100644
--- a/testdata/string.sky
+++ b/testdata/string.sky
@@ -162,8 +162,13 @@ assert.fails(lambda: "%c" % -1, "requires a valid Unicode code point")
assert.eq("a{}b".format(123), "a123b")
assert.eq("a{}b{}c{}d{}".format(1, 2, 3, 4), "a1b2c3d4")
assert.eq("a{{b".format(), "a{b")
+assert.eq("a}}b".format(), "a}b")
+assert.eq("a{{b}}c".format(), "a{b}c")
assert.eq("a{x}b{y}c{}".format(1, x=2, y=3), "a2b3c1")
assert.fails(lambda: "a{z}b".format(x=1), "keyword z not found")
+assert.fails(lambda: "{-1}".format(1), "keyword -1 not found")
+assert.fails(lambda: "{-0}".format(1), "keyword -0 not found")
+assert.fails(lambda: '{0,1} and {1}'.format(1, 2), "keyword 0,1 not found")
assert.fails(lambda: "a{123}b".format(), "tuple index out of range")
assert.fails(lambda: "a{}b{}c".format(1), "tuple index out of range")
assert.eq("a{010}b".format(0,1,2,3,4,5,6,7,8,9,10), "a10b") # index is decimal
@@ -173,6 +178,13 @@ assert.eq("a{!r}c".format("b"), r'a"b"c')
assert.eq("a{x!r}c".format(x='b'), r'a"b"c')
assert.fails(lambda: "{x!}".format(x=1), "unknown conversion")
assert.fails(lambda: "{x!:}".format(x=1), "unknown conversion")
+assert.fails(lambda: '{a.b}'.format(1), "syntax x.y is not supported")
+assert.fails(lambda: '{a[0]}'.format(1), "syntax a\[i\] is not supported")
+assert.fails(lambda: '{ {} }'.format(1), "nested replacement fields not supported")
+assert.fails(lambda: '{{}'.format(1), "single '}' in format")
+assert.fails(lambda: '{}}'.format(1), "single '}' in format")
+assert.fails(lambda: '}}{'.format(1), "unmatched '{' in format")
+assert.fails(lambda: '}{{'.format(1), "single '}' in format")
# str.split, str.rsplit
assert.eq("a.b.c.d".split("."), ["a", "b", "c", "d"])