diff --git a/CHANGELOG.md b/CHANGELOG.md index 105e7d6..ed3c8e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - handle "\n" strings - [#157](https://github.com/ufirstgroup/ymlr/issues/157),[#159](https://github.com/ufirstgroup/ymlr/pull/159) +- handle empty list - [#163](https://github.com/ufirstgroup/ymlr/pull/163) diff --git a/lib/ymlr/encode.ex b/lib/ymlr/encode.ex index 702d1dd..1062283 100644 --- a/lib/ymlr/encode.ex +++ b/lib/ymlr/encode.ex @@ -107,6 +107,8 @@ defmodule Ymlr.Encode do end @spec list(data :: list(), indent_level :: integer, opts :: Encoder.opts()) :: iodata() + def list([], _, _), do: "[]" + def list(data, indent_level, opts) do indentation = indent(indent_level) @@ -202,7 +204,7 @@ defmodule Ymlr.Encode do indentation = indent(level) block = - data |> String.trim_trailing("\n") |> String.replace("\n", IO.iodata_to_binary(indentation)) + data |> String.replace("\n", IO.iodata_to_binary(indentation)) [block_chomping_indicator(data) | [indentation | block]] end diff --git a/test/ymlr/encode_test.exs b/test/ymlr/encode_test.exs index 8ab8774..d77bc4e 100644 --- a/test/ymlr/encode_test.exs +++ b/test/ymlr/encode_test.exs @@ -4,96 +4,111 @@ defmodule Ymlr.EncodeTest do alias Ymlr.Encode, as: MUT + defmacro assert_encode(input) do + quote do + assert unquote(input) |> MUT.to_s!() |> YamlElixir.read_from_string!() == unquote(input) + end + end + + defmacro assert_encode(input, exptected_output, opts \\ []) do + quote do + assert unquote(input) |> MUT.to_s!() |> YamlElixir.read_from_string!() == unquote(input) + assert MUT.to_s!(unquote(input), unquote(opts)) == unquote(exptected_output) + end + end + describe "to_s!/1" do test "atoms" do assert MUT.to_s!(:a) == "a" end test "empty string" do - assert MUT.to_s!("") == "''" + assert_encode("", "''") end test "simple string" do - assert MUT.to_s!("hello world") == "hello world" + assert_encode("hello world", "hello world") end # see http://blogs.perl.org/users/tinita/2018/03/strings-in-yaml---to-quote-or-not-to-quote.html test "quoted strings - avoid type confusion" do - assert MUT.to_s!("yes") == ~S('yes') - assert MUT.to_s!("no") == ~S('no') - assert MUT.to_s!("true") == ~S('true') - assert MUT.to_s!("false") == ~S('false') - assert MUT.to_s!("True") == ~S('True') - assert MUT.to_s!("False") == ~S('False') - assert MUT.to_s!("1") == ~S('1') - assert MUT.to_s!("1.2") == ~S('1.2') - assert MUT.to_s!("-1") == ~S('-1') - assert MUT.to_s!("1e3") == ~S('1e3') - assert MUT.to_s!("0b1010") == ~S('0b1010') - assert MUT.to_s!("0o777") == ~S('0o777') - assert MUT.to_s!("0x1F") == ~S('0x1F') - assert MUT.to_s!("null") == ~S('null') + assert_encode("yes", ~S('yes')) + assert_encode("no", ~S('no')) + assert_encode("true", ~S('true')) + assert_encode("false", ~S('false')) + assert_encode("True", ~S('True')) + assert_encode("False", ~S('False')) + assert_encode("1", ~S('1')) + assert_encode("1.2", ~S('1.2')) + assert_encode("-1", ~S('-1')) + assert_encode("1e3", ~S('1e3')) + assert_encode("0b1010", ~S('0b1010')) + assert_encode("0o777", ~S('0o777')) + assert_encode("0x1F", ~S('0x1F')) + assert_encode("null", ~S('null')) end test "quoted strings - avoid mapping confusion" do - assert MUT.to_s!("hello: world") == ~S('hello: world') + assert_encode("hello: world", ~S('hello: world')) end test "quoted strings - avoid comment confusion" do - assert MUT.to_s!("hello #world") == ~S('hello #world') + assert_encode("hello #world", ~S('hello #world')) end test "quoted strings - starts with special char" do - assert MUT.to_s!("!tag") == ~S('!tag') - assert MUT.to_s!("&anchor") == ~S('&anchor') - assert MUT.to_s!("*alias") == ~S('*alias') - assert MUT.to_s!("- block sequence enty") == ~S('- block sequence enty') - assert MUT.to_s!(": block mapping entry") == ~S(': block mapping entry') - assert MUT.to_s!("? explicit mapping key") == ~S('? explicit mapping key') - assert MUT.to_s!("{flow_mapping") == ~S('{flow_mapping') - assert MUT.to_s!("}flow_mapping") == ~S('}flow_mapping') - assert MUT.to_s!(":{ block flow") == ~S(':{ block flow') - assert MUT.to_s!("[sequence_mapping") == ~S('[sequence_mapping') - assert MUT.to_s!("]sequence_mapping") == ~S(']sequence_mapping') + assert_encode("!tag", ~S('!tag')) + assert_encode("&anchor", ~S('&anchor')) + assert_encode("*alias", ~S('*alias')) + assert_encode("- block sequence enty", ~S('- block sequence enty')) + assert_encode(": block mapping entry", ~S(': block mapping entry')) + assert_encode("? explicit mapping key", ~S('? explicit mapping key')) + assert_encode("{flow_mapping", ~S('{flow_mapping')) + assert_encode("}flow_mapping", ~S('}flow_mapping')) + assert_encode(":{ block flow", ~S(':{ block flow')) + assert_encode("[sequence_mapping", ~S('[sequence_mapping')) + assert_encode("]sequence_mapping", ~S(']sequence_mapping')) assert MUT.to_s!(",flow_collection_entry_separator") == ~S(',flow_collection_entry_separator') - assert MUT.to_s!("#comment") == ~S('#comment') - assert MUT.to_s!("|block_scalar") == ~S('|block_scalar') - assert MUT.to_s!(">block_scalar") == ~S('>block_scalar') - assert MUT.to_s!(~S("double_quote)) == ~S('"double_quote') - assert MUT.to_s!(~S('single_quote)) == ~S("'single_quote") + assert_encode("#comment", ~S('#comment')) + assert_encode("|block_scalar", ~S('|block_scalar')) + assert_encode(">block_scalar", ~S('>block_scalar')) + assert_encode(~S("double_quote), ~S('"double_quote')) + assert_encode(~S('single_quote), ~S("'single_quote")) # see https://yaml.org/spec/1.2.2/#rule-c-reserved - assert MUT.to_s!("@reserved") == ~S('@reserved') - assert MUT.to_s!("`reserved") == ~S('`reserved') + assert_encode("@reserved", ~S('@reserved')) + assert_encode("`reserved", ~S('`reserved')) end test "quoted strings - ends with colon" do - assert MUT.to_s!("some:entry:") == ~S('some:entry:') + assert_encode("some:entry:", ~S('some:entry:')) end test "quoted strings - escape seq forces double quotes (tab char)" do - assert MUT.to_s!("a\tb") == ~s("a\tb") - assert MUT.to_s!("!a\tb") == ~s("!a\tb") + assert_encode("a\tb", ~s("a\tb")) + assert_encode("!a\tb", ~s("!a\tb")) + # Not for explicit backslash: + assert_encode(~S(!a\tb), ~S('!a\tb')) end test "quoted strings - listy and mappy things" do # ... (prefer single quotes) - assert MUT.to_s!("[]") == ~S('[]') - assert MUT.to_s!(~S(["hello"])) == ~S('["hello"]') - assert MUT.to_s!(~S(["he|\o"])) == ~S('["he|\o"]') - assert MUT.to_s!("{}") == ~S('{}') - assert MUT.to_s!("[{}]") == ~S('[{}]') + assert_encode("[]", ~S('[]')) + assert_encode(~S(["hello"]), ~S('["hello"]')) + assert_encode(~S(["he|\o"]), ~S('["he|\o"]')) + assert_encode("{}", ~S('{}')) + assert_encode("[{}]", ~S('[{}]')) # ... (use double quotes if string contains single quotes) - assert MUT.to_s!(~S(["I don't know!\nRea|\y?"])) == ~S("[\"I don't know!\\nRea|\\y?\"]") + assert_encode(~S(["I don't know!\nRea|\y?"]), ~S("[\"I don't know!\\nRea|\\y?\"]")) end test "quoted strings - in map key" do - assert MUT.to_s!(%{"!key" => "value"}) == ~S('!key': value) - assert MUT.to_s!(%{"@key" => "value"}) == ~S('@key': value) - assert MUT.to_s!(%{"true" => "value"}) == ~S('true': value) + assert_encode(%{"!key" => "value"}, ~S('!key': value)) + assert_encode(%{"@key" => "value"}, ~S('@key': value)) + assert_encode(%{"true" => "value"}, ~S('true': value)) end test "bitstrings" do @@ -105,30 +120,30 @@ defmodule Ymlr.EncodeTest do @tag skip: "not sure about those => to be reviewed" # https://yaml.org/spec/1.2.2/#example-escaped-characters test "quoted strings - example-escaped-characters from 1.2.2 spec" do - assert MUT.to_s!("Fun with \\") == ~S("Fun with \\") - assert MUT.to_s!("\" \u0007 \b \u001b \f") == ~S("\" \a \b \e \f") - # assert MUT.to_s!("\n \r \t \u000b \u0000") == ~S("\n \r \t \v \0") + assert_encode("Fun with \\", ~S("Fun with \\")) + assert_encode("\" \u0007 \b \u001b \f", ~S("\" \a \b \e \f")) + # assert_encode("\n \r \t \u000b \u0000", ~S("\n \r \t \v \0")) # or we use | when string contains newlines => rewrite the example to: - assert MUT.to_s!("\r \t \u000b \u0000") == ~S("\r \t \v \0") - assert MUT.to_s!("\u0020 \u00a0 \u0085 \u2028 \u2029") == ~S("\ \_ \N \L \P") + assert_encode("\r \t \u000b \u0000", ~S("\r \t \v \0")) + assert_encode("\u0020 \u00a0 \u0085 \u2028 \u2029", ~S("\ \_ \N \L \P")) end @tag skip: "not sure about those => review the spec" test "quoted strings - in map key (requires escape char)" do - assert MUT.to_s!(%{"a\tb" => "value"}) == ~s("a\tb": value) - assert MUT.to_s!(%{"a\rb" => "value"}) == ~s("a\rb": value) + assert_encode(%{"a\tb" => "value"}, ~s("a\tb": value)) + assert_encode(%{"a\rb" => "value"}, ~s("a\rb": value)) end test "newline in map key" do - assert MUT.to_s!(%{"a\nb" => "value"}) == ~S("a\nb": value) + assert_encode(%{"a\nb" => "value"}, ~S("a\nb": value)) end test "integers" do - assert MUT.to_s!(1) == "1" + assert_encode(1, "1") end test "floats" do - assert MUT.to_s!(1.2) == "1.2" + assert_encode(1.2, "1.2") end test "decimals" do @@ -136,38 +151,40 @@ defmodule Ymlr.EncodeTest do end test "hex and oversize float" do - assert MUT.to_s!("7e0981ff4c0daa3a47db5542ad5c167176145ef65f597a7f94ba2f5b41d35718") == - "7e0981ff4c0daa3a47db5542ad5c167176145ef65f597a7f94ba2f5b41d35718" + assert_encode( + "7e0981ff4c0daa3a47db5542ad5c167176145ef65f597a7f94ba2f5b41d35718", + "7e0981ff4c0daa3a47db5542ad5c167176145ef65f597a7f94ba2f5b41d35718" + ) - assert MUT.to_s!("1.7976931348623157e+309") == "1.7976931348623157e+309" + assert_encode("1.7976931348623157e+309", "1.7976931348623157e+309") end test "lists" do - assert MUT.to_s!([]) == "" - assert MUT.to_s!([1]) == "- 1" - assert MUT.to_s!([""]) == ~s(- "") - assert MUT.to_s!([1, nil, 2]) == "- 1\n-\n- 2" + assert_encode([], "[]") + assert_encode([1], "- 1") + assert_encode([""], ~s(- "")) + assert_encode([1, nil, 2], "- 1\n-\n- 2") end test "empty map" do - assert MUT.to_s!(%{}) == "{}" + assert_encode(%{}, "{}") end test "maps" do assert MUT.to_s!(%{a: 1}) == "a: 1" assert MUT.to_s!(%{a: 1, b: 2}) == "a: 1\nb: 2" - assert MUT.to_s!(%{"a" => 1, "b" => 2}) == "a: 1\nb: 2" - assert MUT.to_s!(%{"a b" => 1, "c d" => 2}) == "a b: 1\nc d: 2" - assert MUT.to_s!(%{1 => 1, 2 => 2}) == "1: 1\n2: 2" + assert_encode(%{"a" => 1, "b" => 2}, "a: 1\nb: 2") + assert_encode(%{"a b" => 1, "c d" => 2}, "a b: 1\nc d: 2") + assert_encode(%{1 => 1, 2 => 2}, "1: 1\n2: 2") assert MUT.to_s!(%{a: nil}) == "a:" end test "maps with atoms: true" do assert MUT.to_s!(%{a: 1}, atoms: true) == ":a: 1" assert MUT.to_s!(%{a: 1, b: 2}, atoms: true) == ":a: 1\n:b: 2" - assert MUT.to_s!(%{"a" => 1, "b" => 2}, atoms: true) == "a: 1\nb: 2" - assert MUT.to_s!(%{"a b" => 1, "c d" => 2}, atoms: true) == "a b: 1\nc d: 2" - assert MUT.to_s!(%{1 => 1, 2 => 2}, atoms: true) == "1: 1\n2: 2" + assert_encode(%{"a" => 1, "b" => 2}, "a: 1\nb: 2", atoms: true) + assert_encode(%{"a b" => 1, "c d" => 2}, "a b: 1\nc d: 2", atoms: true) + assert_encode(%{1 => 1, 2 => 2}, "1: 1\n2: 2", atoms: true) assert MUT.to_s!(%{a: nil}, atoms: true) == ":a:" end @@ -221,7 +238,7 @@ defmodule Ymlr.EncodeTest do end test "nested: list / list" do - assert MUT.to_s!([[1, 2], [3, 4]]) == "- - 1\n - 2\n- - 3\n - 4" + assert_encode([[1, 2], [3, 4]], "- - 1\n - 2\n- - 3\n - 4") end test "nested: list / map" do @@ -266,7 +283,8 @@ defmodule Ymlr.EncodeTest do test "nested: map / map" do result = - MUT.to_s!(%{a: %{b: 1, c: %{d: 2}}, e: %{f: 3, g: 4}}) |> YamlElixir.read_from_string!() + MUT.to_s!(%{a: %{b: 1, c: %{d: 2}}, e: %{f: 3, g: 4}}) + |> YamlElixir.read_from_string!() assert 2 == result["a"]["c"]["d"] assert 1 == result["a"]["b"] @@ -276,8 +294,8 @@ defmodule Ymlr.EncodeTest do # see https://yaml-multiline.info/ test "multiline strings" do - assert MUT.to_s!("hello\nworld") == "|-\nhello\nworld" - assert MUT.to_s!("hello\nworld\n") == "|\nhello\nworld" + assert_encode("hello\nworld", "|-\nhello\nworld") + assert_encode("hello\nworld\n", "|\nhello\nworld\n") end test "newline only string - encoding" do @@ -327,17 +345,13 @@ defmodule Ymlr.EncodeTest do } """ - # not working yet => TODO better handling of terminal newlines - # assert YamlElixir.read_from_string!(encoded) == given + assert YamlElixir.read_from_string!(encoded) == given # assert encoded == expected - assert encoded == String.trim(expected) + assert encoded == expected <> " " end test "nested: list / multiline string" do - given = ["a\nb\n", "c"] - encoded = MUT.to_s!(given) - - assert encoded == "- |\n a\n b\n- c" + assert_encode(["a\nb\n", "c"], "- |\n a\n b\n \n- c") end test "nested: map / multiline string" do