You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2019/01/26 17:29:05 UTC
[freemarker] branch 2.3-gae updated: The "boolean_format"
configuration setting now can be set to "c". Then ${aBoolean} will behave
as ${aBoolean?c}. This should be only used if you are generating output for
non-human (computer) consumption only. If your output has pieces for human
audience too,
it's still recommended to use ${aBoolean?c} where true/false is needed,
and either not set the "boolean_format" at all,
or set it to something that's appropriate for everyday uses (like "yes,
no").
This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git
The following commit(s) were added to refs/heads/2.3-gae by this push:
new becf252 The "boolean_format" configuration setting now can be set to "c". Then ${aBoolean} will behave as ${aBoolean?c}. This should be only used if you are generating output for non-human (computer) consumption only. If your output has pieces for human audience too, it's still recommended to use ${aBoolean?c} where true/false is needed, and either not set the "boolean_format" at all, or set it to something that's appropriate for everyday uses (like "yes,no").
becf252 is described below
commit becf25290b422eb5306a5dbba4bb87261c35253e
Author: ddekany <dd...@apache.org>
AuthorDate: Sat Jan 26 17:37:29 2019 +0100
The "boolean_format" configuration setting now can be set to "c". Then ${aBoolean} will behave as ${aBoolean?c}. This should be only used if you are generating output for non-human (computer) consumption only. If your output has pieces for human audience too, it's still recommended to use ${aBoolean?c} where true/false is needed, and either not set the "boolean_format" at all, or set it to something that's appropriate for everyday uses (like "yes,no").
---
src/main/java/freemarker/core/Configurable.java | 66 ++++++++++++-------
src/main/java/freemarker/core/MiscUtil.java | 2 +-
src/manual/en_US/book.xml | 77 ++++++++++++++++------
.../freemarker/template/ConfigurationTest.java | 18 +++++
.../templatesuite/templates/boolean-formatting.ftl | 9 ++-
.../templatesuite/templates/sequence-builtins.ftl | 39 +++++------
.../templates/string-builtins-regexps.ftl | 55 ++++++++--------
.../templatesuite/templates/string-builtins2.ftl | 23 +++----
8 files changed, 183 insertions(+), 106 deletions(-)
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 70f2dfa..27e5285 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -81,7 +81,8 @@ import freemarker.template.utility.StringUtil;
*/
public class Configurable {
static final String C_TRUE_FALSE = "true,false";
-
+ static final String C_FORMAT_STRING = "c";
+
private static final String NULL = "null";
private static final String DEFAULT = "default";
private static final String DEFAULT_2_3_0 = "default_2_3_0";
@@ -963,39 +964,46 @@ public class Configurable {
}
/**
- * The string value for the boolean {@code true} and {@code false} values, intended for human audience (not for a
- * computer language), separated with comma. For example, {@code "yes,no"}. Note that white-space is significant,
- * so {@code "yes, no"} is WRONG (unless you want that leading space before "no").
+ * The string value for the boolean {@code true} and {@code false} values, usually intended for human consumption
+ * (not for a computer language), separated with comma. For example, {@code "yes,no"}. Note that white-space is
+ * significant, so {@code "yes, no"} is WRONG (unless you want that leading space before "no"). Because the proper
+ * way of formatting booleans depends on the context too much, it's probably the best to leave this setting on its
+ * default, which will enforce explicit formatting, like <code>${aBoolean?string('on', 'off')}</code>.
*
* <p>For backward compatibility the default is {@code "true,false"}, but using that value is denied for automatic
- * boolean-to-string conversion (like <code>${myBoolean}</code> will fail with it), only {@code myBool?string} will
- * allow it, which is deprecated since FreeMarker 2.3.20.
+ * boolean-to-string conversion, like <code>${myBoolean}</code> will fail with it. If you generate the piece of
+ * output for "computer audience" as opposed to "human audience", then you should write
+ * <code>${myBoolean?c}</code>, which will print {@code true} or {@code false}. If you really want to always
+ * format for computer audience, then it's might be reasonable to set this setting to {@code c}.
*
* <p>Note that automatic boolean-to-string conversion only exists since FreeMarker 2.3.20. Earlier this setting
* only influenced the result of {@code myBool?string}.
*/
public void setBooleanFormat(String booleanFormat) {
NullArgumentException.check("booleanFormat", booleanFormat);
-
- int commaIdx = booleanFormat.indexOf(',');
- if (commaIdx == -1) {
- throw new IllegalArgumentException(
- "Setting value must be a string that contains two comma-separated values for true and false, " +
- "respectively.");
- }
-
- this.booleanFormat = booleanFormat;
- properties.setProperty(BOOLEAN_FORMAT_KEY, booleanFormat);
-
+
if (booleanFormat.equals(C_TRUE_FALSE)) {
// C_TRUE_FALSE is the default for BC, but it's not a good default for human audience formatting, so we
// pretend that it wasn't set.
trueStringValue = null;
falseStringValue = null;
+ } else if (booleanFormat.equals(C_FORMAT_STRING)) {
+ trueStringValue = MiscUtil.C_TRUE;
+ falseStringValue = MiscUtil.C_FALSE;
} else {
- trueStringValue = booleanFormat.substring(0, commaIdx);
+ int commaIdx = booleanFormat.indexOf(',');
+ if (commaIdx == -1) {
+ throw new IllegalArgumentException(
+ "Setting value must be a string that contains two comma-separated values for true and false, " +
+ "or it must be \"" + C_FORMAT_STRING + "\", but it was " +
+ StringUtil.jQuote(booleanFormat) + ".");
+ }
+ trueStringValue = booleanFormat.substring(0, commaIdx);
falseStringValue = booleanFormat.substring(commaIdx + 1);
}
+
+ this.booleanFormat = booleanFormat;
+ properties.setProperty(BOOLEAN_FORMAT_KEY, booleanFormat);
}
/**
@@ -1045,15 +1053,23 @@ public class Configurable {
"Can't convert boolean to string automatically, because the \"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
new _DelayedJQuote(getBooleanFormat()),
(getBooleanFormat().equals(C_TRUE_FALSE)
- ? ", which is the legacy default computer-language format, and hence isn't accepted."
+ ? ", which is the legacy deprecated default, and we treat it as if no format was set. "
+ + "This is the default configuration; you should provide the format explicitly for each "
+ + "place where you print a boolean."
: ".")
).tips(
- "If you just want \"true\"/\"false\" result as you are generting computer-language output, "
- + "use \"?c\", like ${myBool?c}.",
- "You can write myBool?string('yes', 'no') and like to specify boolean formatting in place.",
- new Object[] {
- "If you need the same two values on most places, the programmers should set the \"",
- BOOLEAN_FORMAT_KEY ,"\" setting to something like \"yes,no\"." }
+ "Write something like myBool?string('yes', 'no') to specify boolean formatting in place.",
+ new Object[]{
+ "If you want \"true\"/\"false\" result as you are generating computer-language output "
+ + "(not for direct human consumption), then use \"?c\", like ${myBool?c}. (If you "
+ + "always generate computer-language output, then it's might be reasonable to set "
+ + "the \"", BOOLEAN_FORMAT_KEY, "\" setting to \"c\" instead.)",
+ },
+ new Object[] {
+ "If you need the same two values on most places, the programmers can set the \"",
+ BOOLEAN_FORMAT_KEY ,"\" setting to something like \"yes,no\". However, then it will be easy to "
+ + "unwillingly format booleans like that."
+ }
);
}
diff --git a/src/main/java/freemarker/core/MiscUtil.java b/src/main/java/freemarker/core/MiscUtil.java
index ccd0dd9..770e394 100644
--- a/src/main/java/freemarker/core/MiscUtil.java
+++ b/src/main/java/freemarker/core/MiscUtil.java
@@ -35,7 +35,7 @@ class MiscUtil {
static final String C_FALSE = "false";
static final String C_TRUE = "true";
-
+
/**
* Returns the map entries in source code order of the Expression values.
*/
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 161b52c..d82d089 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -15979,7 +15979,8 @@ Extended decimal format: 10<emphasis>_</emphasis>00<emphasis>3</emphasis></progr
</tr>
<tr>
- <td><literal>multiplier</literal></td>
+ <td><literal>multiplier</literal> (was
+ <literal>multipier</literal> before 2.3.29)</td>
<td>The number will be shown after multiplied with this
integer number.</td>
@@ -16721,10 +16722,18 @@ Tue, Apr 8, '03
<quote>computer language</quote> as opposed to for human audience.
The result will be <literal>"true"</literal> or
<literal>"false"</literal>, regardless of the
- <literal>boolean_format</literal> setting. When generating
- JavaScript and such, this should be used, as otherwise changing the
- <literal>boolean_format</literal> can break the generated
- computer-language output.</para>
+ <literal>boolean_format</literal> configuration setting, as that
+ setting is meant to specify the format for human consumption. When
+ generating boolean literals for JavaScript and such, this should be
+ used.</para>
+
+ <para>If you only generate output that's computer language and isn't
+ read by end-users, you may want to set the
+ <literal>boolean_format</literal> configuration setting to
+ <literal>c</literal> (since FreeMarker 2.3.29), in which case
+ <literal>${<replaceable>aBoolean</replaceable>}</literal> will have
+ the same output as
+ <literal>${<replaceable>aBoolean</replaceable>?c}</literal>.</para>
<para>Note that this built-in <link linkend="ref_builtin_c">also
works on strings</link>.</para>
@@ -26752,14 +26761,14 @@ End book</programlisting>
</question>
<answer>
- <para>Unlike numbers, booleans has no commonly accepted format,
- not even a common format within the same page. Like when you show
- on a HTML page if a product is washable, you will hardly want to
- show for the visitor "Washable: true", but rather "Washable: yes".
- So we force the template author (by <literal>${washable}</literal>
- causing error) to find out with his human knowledge how the
- boolean value should be shown at the given place. The common way
- of formatting a boolean is like <literal>${washable?string("yes",
+ <para><literal>${<replaceable>...</replaceable>}</literal> meant
+ to format values for human consumption, and unlike numbers,
+ booleans has no commonly accepted format
+ (<literal>true</literal>/<literal>false</literal> is common in
+ computer languages, but you rarely use it outside that). The
+ proper format depends quite much on the context, therefore,
+ usually the template author should decide the proper format for
+ each case, like <literal>${washable?string("yes",
"no")}</literal>, <literal>${caching?string("Enabled",
"Disabled")}</literal>, <literal>${heating?string("on",
"off")}</literal>, etc.</para>
@@ -26771,14 +26780,24 @@ End book</programlisting>
<listitem>
<para>When printing boolean to generate computer language
output, and hence you want
- <literal>true</literal>/<literal>false</literal>, use
- <literal>${<replaceable>someBoolean</replaceable>?c}</literal>.
- (This requires at least FreeMarker 2.3.20. Before that, the
- common practice was writing
- <literal>${<replaceable>someBoolean</replaceable>?string}</literal>,
- however that's dangerous because its output depends on the
- current boolean format setting, whose default is
- <literal>"true"</literal>/<literal>"false"</literal>.)</para>
+ <literal>true</literal>/<literal>false</literal>. In such case
+ use <literal>${<replaceable>someBoolean</replaceable><link
+ linkend="ref_builtin_c_boolean">?c</link>}</literal> (requires
+ FreeMarker 2.3.20). If you never generate for human
+ consumption, only for computer language output, you might want
+ to set <literal>boolean_format</literal> to
+ <literal>c</literal> (available since FreeMarker 2.3.29), and
+ then <literal>${<replaceable>aBoolean</replaceable>}</literal>
+ will behave as
+ <literal>${<replaceable>aBoolean</replaceable>?c}</literal>.</para>
+
+ <para>Before 2.3.20, if you really can't upgrade FreeMarker,
+ try if
+ <literal>${<replaceable>someBoolean</replaceable>?string}</literal>.
+ By default that prints
+ <literal>true</literal>/<literal>false</literal>. Then somehow
+ try to ensure that <literal>boolean_format</literal> won't be
+ changed later by someone, breaking your output.</para>
</listitem>
<listitem>
@@ -27928,6 +27947,22 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
</listitem>
<listitem>
+ <para>The <literal>boolean_format</literal> configuration
+ setting now can be set to <literal>"c"</literal>. Then
+ <literal>${<replaceable>aBoolean</replaceable>}</literal> will
+ behave as
+ <literal>${<replaceable>aBoolean</replaceable>?c}</literal>.
+ This should be only used if you are generating output for
+ non-human (computer) consumption only. If your output has pieces
+ for human audience too, it's still recommended to use
+ <literal>${<replaceable>aBoolean</replaceable>?c}</literal>
+ where <literal>true</literal>/<literal>false</literal> output is
+ needed, and either not set the <literal>boolean_format</literal>
+ at all, or set it to something that's appropriate for everyday
+ uses (like <literal>"yes,no"</literal>).</para>
+ </listitem>
+
+ <listitem>
<para>Added
<literal>TemplateModelUtils.wrapAsHashUnion(ObjectWrapper,
List<?>)</literal> and
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index cd71ca1..3cf8c8b 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -1417,6 +1417,24 @@ public class ConfigurationTest extends TestCase {
}
@Test
+ public void testSetBooleanFormat() throws Exception {
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
+
+ cfg.setBooleanFormat("yes,no");
+ assertOutputEquals("yes no", new Template(null, "${true} ${false}", cfg));
+
+ cfg.setBooleanFormat("c");
+ assertOutputEquals("true false", new Template(null, "${true} ${false}", cfg));
+
+ try {
+ cfg.setBooleanFormat("yes no");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("comma"));
+ }
+ }
+
+ @Test
public void testSetTabSize() throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/boolean-formatting.ftl b/src/test/resources/freemarker/test/templatesuite/templates/boolean-formatting.ftl
index 9d6c742..e77d3bf 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/boolean-formatting.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/boolean-formatting.ftl
@@ -16,7 +16,7 @@
specific language governing permissions and limitations
under the License.
-->
-<#assign suppress>
+<@noOutput>
<@assertFails message="true,false">${true}</@>
<@assertFails message="true,false">${false}</@>
<@assertFails message="true,false">${"" + true}</@>
@@ -31,6 +31,11 @@
<@assertEquals expected="f" actual=false?string('t', 'f') />
<#setting boolean_format = 'true,false'>
<@assertFails message="true,false">${true}</@>
+<#setting boolean_format = 'c'>
+<@assertEquals expected="true" actual=true?string />
+<@assertEquals expected="false" actual=false?string />
+<@assertEquals expected="true" actual="${true}" />
+<@assertEquals expected="false" actual="${false}" />
<#setting boolean_format = 'ja,nein'>
<@assertEquals expected="ja" actual="" + true />
@@ -58,7 +63,7 @@
<@assertEquals actual=123?upper_case expected="123" />
<@assertEquals actual=true?upper_case expected="Y" />
-</#assign>
+</...@noOutput>
<#escape x as x?upper_case>
<#assign x = true>${x} ${true} ${true?string}
<#assign x = false>${x} ${false} ${false?string}
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/sequence-builtins.ftl b/src/test/resources/freemarker/test/templatesuite/templates/sequence-builtins.ftl
index e21bc2d..7b76501 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/sequence-builtins.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/sequence-builtins.ftl
@@ -19,6 +19,7 @@
<@noOutput>
<#setting locale="en_US">
<#setting number_format="0.#########">
+<#setting boolean_format="c">
<#assign ls = []?sort>
<#list ls as i>
@@ -67,7 +68,7 @@ Boolean order:
false,
true]>
<#list x?sort as i>
-- ${i?string}
+- ${i}
</#list>
@@ -135,25 +136,25 @@ Contains:
<#macro test></#macro>
<#assign x = [1, "2", true, [1,2,3], {"a":1}, test, '1992-02-21'?date('yyyy-MM-dd')]>
True:
-${x?seq_contains(1.0)?string}
-${x?seq_contains("2")?string}
-${x?seq_contains(true)?string}
-${x?seq_contains('1992-02-21'?date('yyyy-MM-dd'))?string}
-${abcSet?seq_contains("a")?string}
-${abcSet?seq_contains("b")?string}
-${abcSet?seq_contains("c")?string}
+${x?seq_contains(1.0)}
+${x?seq_contains("2")}
+${x?seq_contains(true)}
+${x?seq_contains('1992-02-21'?date('yyyy-MM-dd'))}
+${abcSet?seq_contains("a")}
+${abcSet?seq_contains("b")}
+${abcSet?seq_contains("c")}
False:
-${x?seq_contains("1")?string}
-${x?seq_contains(2)?string}
-${x?seq_contains(false)?string}
-${x?seq_contains('1992-02-22'?date('yyyy-MM-dd'))?string}
-${abcSet?seq_contains("A")?string}
-${abcSet?seq_contains(1)?string}
-${abcSet?seq_contains(true)?string}
+${x?seq_contains("1")}
+${x?seq_contains(2)}
+${x?seq_contains(false)}
+${x?seq_contains('1992-02-22'?date('yyyy-MM-dd'))}
+${abcSet?seq_contains("A")}
+${abcSet?seq_contains(1)}
+${abcSet?seq_contains(true)}
<#assign x = []>
-False: ${x?seq_contains(1)?string}
+False: ${x?seq_contains(1)}
Index_of:
---------
@@ -285,12 +286,12 @@ seq_last_index_of "c":
Sequence builtins ignoring nulls
--------------------------------
-true = ${listWithNull?seq_contains('c')?string}
+true = ${listWithNull?seq_contains('c')}
2 = ${listWithNull?seq_index_of('c')}
0 = ${listWithNull?seq_last_index_of('a')}
These should throw exception, but for BC they don't:
-false = ${listWithNull?seq_contains(noSuchVar)?string}
+false = ${listWithNull?seq_contains(noSuchVar)}
-1 = ${listWithNull?seq_index_of(noSuchVar)}
-1 = ${listWithNull?seq_last_index_of(noSuchVar)}
@@ -301,7 +302,7 @@ Sequence built-ins failing on date-type mismatch
<@assertEquals actual=x?seq_index_of('foo') expected=1 />
<@assertEquals actual=x?seq_index_of('1992-02-21'?date('yyyy-MM-dd')) expected=0 />
<@assertFails message="dates of different types">
- 0 = ${x?seq_index_of('1992-02-21 00:00:00'?datetime('yyyy-MM-dd HH:mm:ss'))}
+ ${x?seq_index_of('1992-02-21 00:00:00'?datetime('yyyy-MM-dd HH:mm:ss'))}
</@>
Chunk
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/string-builtins-regexps.ftl b/src/test/resources/freemarker/test/templatesuite/templates/string-builtins-regexps.ftl
index 81c0b8f..ad27270 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/string-builtins-regexps.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/string-builtins-regexps.ftl
@@ -16,31 +16,32 @@
specific language governing permissions and limitations
under the License.
-->
-${"test"?matches('test')?string} == true
-${"test"?matches('test', '')?string} == true
-
-${"TEST"?matches('test')?string} == false
-${"TEST"?matches('test', 'i')?string} == true
-
-${"test\nfoo"?matches('.*^foo')?string} == false
-${"test\nfoo"?matches(r'.*\n^foo', 'm')?string} == true
-
-${"test\nfoo"?matches('test.foo')?string} == false
-${"test\nfoo"?matches('test.foo', 's')?string} == true
-
-${"test\nFoo"?matches('.*foo', 's')?string} == false
-${"test\nFoo"?matches('.*foo', 'i')?string} == false
-${"test\nFoo"?matches('.*foo', 'im')?string} == false
-${"test\nFoo"?matches('.*foo', 'si')?string} == true
-${"test\nFoo"?matches('.*foo', 'is')?string} == true
-${"test\nFoo"?matches('.*foo', 'mis')?string} == true
-
-${"test\nFoo"?matches('.*\n^foo', 'm')?string} == false
-${"test\nFoo"?matches('.*\n^foo', 'i')?string} == false
-${"test\nFoo"?matches('.*\n^foo', 'im')?string} == true
-${"test\nFoo"?matches('.*\n^foo', 'mi')?string} == true
-${"test\nFoo"?matches('.*^foo', 'ism')?string} == true
-${"test\nFoo"?matches('.*^foo', 'smi')?string} == true
+<#setting boolean_format="c">
+${"test"?matches('test')} == true
+${"test"?matches('test', '')} == true
+
+${"TEST"?matches('test')} == false
+${"TEST"?matches('test', 'i')} == true
+
+${"test\nfoo"?matches('.*^foo')} == false
+${"test\nfoo"?matches(r'.*\n^foo', 'm')} == true
+
+${"test\nfoo"?matches('test.foo')} == false
+${"test\nfoo"?matches('test.foo', 's')} == true
+
+${"test\nFoo"?matches('.*foo', 's')} == false
+${"test\nFoo"?matches('.*foo', 'i')} == false
+${"test\nFoo"?matches('.*foo', 'im')} == false
+${"test\nFoo"?matches('.*foo', 'si')} == true
+${"test\nFoo"?matches('.*foo', 'is')} == true
+${"test\nFoo"?matches('.*foo', 'mis')} == true
+
+${"test\nFoo"?matches('.*\n^foo', 'm')} == false
+${"test\nFoo"?matches('.*\n^foo', 'i')} == false
+${"test\nFoo"?matches('.*\n^foo', 'im')} == true
+${"test\nFoo"?matches('.*\n^foo', 'mi')} == true
+${"test\nFoo"?matches('.*^foo', 'ism')} == true
+${"test\nFoo"?matches('.*^foo', 'smi')} == true
<#setting boolean_format="True,False">
<@assert test=false?matches('[eslaF]+') />
<@assert test='False'?matches('[eslaF]+') />
@@ -89,12 +90,12 @@ Lower c-word with follower in the same line:
</#list>
<#attempt>
- Ignored but logged in 2.3: ${s?matches('broken', 'I')?string} == False
+ Ignored but logged in 2.3: ${s?matches('broken', 'I')} == False
<#recover>
Fails in 2.4
</#attempt>
<#attempt>
- Ignored but logged in 2.3: ${s?matches('broken', 'f')?string} == False
+ Ignored but logged in 2.3: ${s?matches('broken', 'f')} == False
<#recover>
Fails in 2.4
</#attempt>
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/string-builtins2.ftl b/src/test/resources/freemarker/test/templatesuite/templates/string-builtins2.ftl
index c9294fa..6e0f018 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/string-builtins2.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/string-builtins2.ftl
@@ -16,6 +16,7 @@
specific language governing permissions and limitations
under the License.
-->
+<#setting boolean_format="c">
--
<#assign s = "abbcdbb">
${s?index_of("bb")} = 1
@@ -26,19 +27,19 @@ ${s?last_index_of("bb")} = 5
${s?last_index_of("bb", 4)} = 1
${s?last_index_of("")} = ${s?length}
--
-${s?starts_with("abb")?string} = true
-${s?starts_with("bb")?string} = false
-${s?starts_with("")?string} = true
+${s?starts_with("abb")} = true
+${s?starts_with("bb")} = false
+${s?starts_with("")} = true
--
-${s?ends_with("dbb")?string} = true
-${s?ends_with("cbb")?string} = false
-${s?ends_with("")?string} = true
+${s?ends_with("dbb")} = true
+${s?ends_with("cbb")} = false
+${s?ends_with("")} = true
--
-${s?contains("abb")?string} = true
-${s?contains("bcd")?string} = true
-${s?contains("dbb")?string} = true
-${s?contains("bbx")?string} = false
-${s?contains("")?string} = true
+${s?contains("abb")} = true
+${s?contains("bcd")} = true
+${s?contains("dbb")} = true
+${s?contains("bbx")} = false
+${s?contains("")} = true
--
[${s?chop_linebreak}] = [abbcdbb]
[${"qwe\n"?chop_linebreak}] = [qwe]