From 48ecc2597df0fda4c369b133899795b4f572896a Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Mon, 16 Sep 2024 18:40:03 -0500 Subject: [PATCH 1/4] semi working serialization --- src/tom.gleam | 137 ++++++++++++++++++++++++++++++++++++++------ test/tom_test.gleam | 110 +++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 16 deletions(-) diff --git a/src/tom.gleam b/src/tom.gleam index 31e4a6d..9417f0a 100644 --- a/src/tom.gleam +++ b/src/tom.gleam @@ -2,28 +2,29 @@ //// //// ```gleam //// import tom -//// +//// //// const config = " //// [person] //// name = \"Lucy\" //// is_cool = true //// " -//// +//// //// pub fn main() { //// // Parse a string of TOML //// let assert Ok(parsed) = tom.parse(config) -//// +//// //// // Now you can work with the data directly, or you can use the `get_*` //// // functions to retrieve values. -//// +//// //// tom.get_string(parsed, ["person", "name"]) //// // -> Ok("Lucy") -//// +//// //// let is_cool = tom.get_bool(parsed, ["person", "is_cool"]) //// // -> Ok(True) //// } //// ``` +import gleam/bool import gleam/dict.{type Dict} import gleam/float import gleam/int @@ -109,7 +110,7 @@ pub type GetError { /// Get a value of any type from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = 1") /// get(parsed, ["a", "b", "c"]) @@ -138,7 +139,7 @@ pub fn get( /// Get an int from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = 1") /// get_int(parsed, ["a", "b", "c"]) @@ -160,7 +161,7 @@ pub fn get_int( /// Get a float from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = 1.1") /// get_float(parsed, ["a", "b", "c"]) @@ -182,7 +183,7 @@ pub fn get_float( /// Get a bool from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = true") /// get_bool(parsed, ["a", "b", "c"]) @@ -204,7 +205,7 @@ pub fn get_bool( /// Get a string from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = \"ok\"") /// get_string(parsed, ["a", "b", "c"]) @@ -226,7 +227,7 @@ pub fn get_string( /// Get a date from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = 1979-05-27") /// get_date(parsed, ["a", "b", "c"]) @@ -248,7 +249,7 @@ pub fn get_date( /// Get a time from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = 07:32:00") /// get_time(parsed, ["a", "b", "c"]) @@ -270,7 +271,7 @@ pub fn get_time( /// Get a date-time from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = 1979-05-27T07:32:00") /// get_date_time(parsed, ["a", "b", "c"]) @@ -292,7 +293,7 @@ pub fn get_date_time( /// Get an array from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = [1, 2]") /// get_array(parsed, ["a", "b", "c"]) @@ -315,7 +316,7 @@ pub fn get_array( /// Get a table from a TOML document. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = { d = 1 }") /// get_table(parsed, ["a", "b", "c"]) @@ -339,7 +340,7 @@ pub fn get_table( /// This could be an int, a float, a NaN, or an infinity. /// /// ## Examples -/// +/// /// ```gleam /// let assert Ok(parsed) = parse("a.b.c = { d = inf }") /// get_number(parsed, ["a", "b", "c"]) @@ -1328,3 +1329,107 @@ fn parse_offset_hours(input: Tokens, sign: Sign) -> Parsed(Offset) { use #(hours, minutes), input <- do(parse_hour_minute(input)) Ok(#(Offset(sign, hours, minutes), input)) } + +pub fn serialize(toml_document: Dict(String, Toml)) -> String { + let fold_to_string = fn(acc, key, val) { + case val { + ArrayOfTables(_) -> acc <> "[[" <> key <> "]]" <> "\n" + Table(_) -> acc <> "[" <> key <> "]\n" + _ -> acc <> key <> " = " + } + <> value_to_string(val) + <> "\n" + } + toml_document + |> dict.fold("", fold_to_string) +} + +/// Serialization +pub fn value_to_string(val: Toml) -> String { + case val { + Float(value) -> value |> float.to_string + Int(value) -> value |> int.to_string + Infinity(sign) -> sign |> sign_to_string <> "inf" + Nan(sign) -> sign |> sign_to_string <> "nan" + Bool(value) -> value |> bool.to_string |> string.lowercase + String(value) -> "\"" <> value <> "\"" + Date(value) -> date_to_string(value) + Time(value) -> time_to_string(value) + DateTime(value) -> date_time_to_string(value) + Array(list) -> array_to_string(list) + ArrayOfTables(list) -> table_array_to_string(list) + Table(table) -> table_to_string(table) + InlineTable(table) -> inline_table_to_string(table) + } +} + +fn array_to_string(array: List(Toml)) -> String { + let joined = + array + |> list.map(value_to_string) + |> string.join(", ") + "[" <> joined <> "]" +} + +fn sign_to_string(sign: Sign) -> String { + case sign { + Positive -> "+" + Negative -> "-" + } +} + +fn table_array_to_string(table_array: List(Dict(String, Toml))) { + table_array + |> list.fold("", fn(acc, table) { acc <> table_to_string(table) }) +} + +fn table_to_string(table: Dict(String, Toml)) -> String { + let fold_to_string = fn(acc: String, key: String, value: Toml) { + case value { + Table(child_table) -> key <> "." <> table_to_string(child_table) + _ -> acc <> key <> " = " <> value |> value_to_string <> "\n" + } + } + table + |> dict.fold("", fold_to_string) +} + +fn inline_table_to_string(table: Dict(String, Toml)) -> String { + let fold_to_string = fn(acc, key, value) { + acc <> key <> " = " <> value |> value_to_string <> ", " + } + table |> dict.fold("{ ", fold_to_string) <> "}" +} + +fn date_to_string(date: Date) -> String { + [date.year, date.month, date.day] + |> list.map(int.to_string) + |> string.join("-") +} + +fn time_to_string(time: Time) -> String { + let ms = int.to_string(time.millisecond) + [time.hour, time.minute, time.second] + |> list.map(int.to_string) + |> string.join(":") + <> "." + <> ms +} + +fn date_time_to_string(date_time: DateTime) -> String { + let date = date_time.date |> date_to_string + let time = date_time.time |> time_to_string + let offset = date_time.offset |> offset_to_string + date <> " " <> time <> offset +} + +fn offset_to_string(offset: Offset) -> String { + case offset { + Local -> "" + Offset(sign, hours, minutes) -> + sign_to_string(sign) + <> int.to_string(hours) + <> ":" + <> int.to_string(minutes) + } +} diff --git a/test/tom_test.gleam b/test/tom_test.gleam index 16ee24c..326d6f6 100644 --- a/test/tom_test.gleam +++ b/test/tom_test.gleam @@ -53,6 +53,13 @@ pub fn parse_true_test() { |> should.equal(Ok(expected)) } +pub fn serialize_true_test() { + let expected = "cool = true\n" + dict.from_list([#("cool", tom.Bool(True))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_false_test() { let expected = dict.from_list([#("cool", tom.Bool(False))]) "cool = false\n" @@ -60,6 +67,13 @@ pub fn parse_false_test() { |> should.equal(Ok(expected)) } +pub fn serialize_false_test() { + let expected = "cool = false\n" + dict.from_list([#("cool", tom.Bool(False))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_unicode_key_test() { let expected = dict.from_list([#("பெண்", tom.Bool(False))]) "பெண் = false\n" @@ -74,6 +88,13 @@ pub fn parse_int_test() { |> should.equal(Ok(expected)) } +pub fn serialize_int_test() { + let expected = "it = 1\n" + dict.from_list([#("it", tom.Int(1))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_int_underscored_test() { let expected = dict.from_list([#("it", tom.Int(1_000_009))]) "it = 1_000_0__0_9\n" @@ -95,6 +116,13 @@ pub fn parse_int_negative_test() { |> should.equal(Ok(expected)) } +pub fn serialize_int_negative_test() { + let expected = "it = -234\n" + dict.from_list([#("it", tom.Int(-234))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_string_test() { let expected = dict.from_list([#("hello", tom.String("Joe"))]) "hello = \"Joe\"\n" @@ -102,6 +130,13 @@ pub fn parse_string_test() { |> should.equal(Ok(expected)) } +pub fn serialize_string_test() { + let expected = "hello = \"Joe\"\n" + dict.from_list([#("hello", tom.String("Joe"))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_string_escaped_quote_test() { let expected = dict.from_list([#("hello", tom.String("\""))]) "hello = \"\\\"\"\n" @@ -144,6 +179,13 @@ pub fn parse_float_test() { |> should.equal(Ok(expected)) } +pub fn serialize_float_test() { + let expected = "it = 1.0\n" + dict.from_list([#("it", tom.Float(1.0))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_bigger_float_test() { let expected = dict.from_list([#("it", tom.Float(123_456_789.9876))]) "it = 123456789.9876\n" @@ -151,6 +193,13 @@ pub fn parse_bigger_float_test() { |> should.equal(Ok(expected)) } +pub fn serialize_bigger_float_test() { + let expected = "it = 123456789.9876\n" + dict.from_list([#("it", tom.Float(123_456_789.9876))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_multi_segment_key_test() { let expected = dict.from_list([ @@ -168,6 +217,22 @@ pub fn parse_multi_segment_key_test() { |> should.equal(Ok(expected)) } +pub fn serialize_multi_segment_key_test() { + let expected = "one.two.three = true\n\n" + dict.from_list([ + #( + "one", + tom.Table( + dict.from_list([ + #("two", tom.Table(dict.from_list([#("three", tom.Bool(True))]))), + ]), + ), + ), + ]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_multi_segment_key_with_spaeces_test() { let expected = dict.from_list([ @@ -209,6 +274,13 @@ pub fn parse_multiple_keys_test() { |> should.equal(Ok(expected)) } +pub fn serialize_multiple_keys_test() { + let expected = "a = 1\nb = 2\n" + dict.from_list([#("a", tom.Int(1)), #("b", tom.Int(2))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_duplicate_key_test() { "a = 1\na = 2\n" |> tom.parse @@ -228,6 +300,13 @@ pub fn parse_empty_array_test() { |> should.equal(Ok(expected)) } +pub fn serialize_empty_array_test() { + let expected = "a = []\n" + dict.from_list([#("a", tom.Array([]))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_array_test() { let expected = dict.from_list([#("a", tom.Array([tom.Int(1), tom.Int(2)]))]) "a = [1, 2]\n" @@ -235,6 +314,13 @@ pub fn parse_array_test() { |> should.equal(Ok(expected)) } +pub fn serialize_array_test() { + let expected = "a = [1, 2]\n" + dict.from_list([#("a", tom.Array([tom.Int(1), tom.Int(2)]))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_multi_line_array_test() { let expected = dict.from_list([#("a", tom.Array([tom.Int(1), tom.Int(2)]))]) "a = [\n 1 \n ,\n 2,\n]\n" @@ -249,6 +335,13 @@ pub fn parse_table_test() { |> should.equal(Ok(expected)) } +pub fn serialize_table_test() { + let expected = "[a]\n" + dict.from_list([#("a", tom.Table(dict.from_list([])))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_table_with_values_test() { let expected = dict.from_list([ @@ -270,6 +363,23 @@ b.c = 2 |> should.equal(Ok(expected)) } +pub fn serialize_table_with_values_test() { + let expected = "a.a = 1\na.b.c = 2" + dict.from_list([ + #( + "a", + tom.Table( + dict.from_list([ + #("a", tom.Int(1)), + #("b", tom.Table(dict.from_list([#("c", tom.Int(2))]))), + ]), + ), + ), + ]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_table_with_values_before_test() { let expected = dict.from_list([ From 76450631d4aeff1e7d18fd2b735852ef8a744994 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Mon, 16 Sep 2024 21:57:14 -0500 Subject: [PATCH 2/4] checkpoint before root table behavior --- src/tom.gleam | 41 ++++++++++++------------ test/tom_test.gleam | 78 ++++++++++++++++++++++++++++++++------------- 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/tom.gleam b/src/tom.gleam index 9417f0a..2e0b975 100644 --- a/src/tom.gleam +++ b/src/tom.gleam @@ -28,6 +28,7 @@ import gleam/bool import gleam/dict.{type Dict} import gleam/float import gleam/int +import gleam/io import gleam/list import gleam/result import gleam/string @@ -1330,21 +1331,10 @@ fn parse_offset_hours(input: Tokens, sign: Sign) -> Parsed(Offset) { Ok(#(Offset(sign, hours, minutes), input)) } -pub fn serialize(toml_document: Dict(String, Toml)) -> String { - let fold_to_string = fn(acc, key, val) { - case val { - ArrayOfTables(_) -> acc <> "[[" <> key <> "]]" <> "\n" - Table(_) -> acc <> "[" <> key <> "]\n" - _ -> acc <> key <> " = " - } - <> value_to_string(val) - <> "\n" - } - toml_document - |> dict.fold("", fold_to_string) +pub fn serialize(value: Dict(String, Toml)) -> String { + value_to_string(Table(value)) } -/// Serialization pub fn value_to_string(val: Toml) -> String { case val { Float(value) -> value |> float.to_string @@ -1363,7 +1353,7 @@ pub fn value_to_string(val: Toml) -> String { } } -fn array_to_string(array: List(Toml)) -> String { +pub fn array_to_string(array: List(Toml)) -> String { let joined = array |> list.map(value_to_string) @@ -1384,14 +1374,23 @@ fn table_array_to_string(table_array: List(Dict(String, Toml))) { } fn table_to_string(table: Dict(String, Toml)) -> String { - let fold_to_string = fn(acc: String, key: String, value: Toml) { - case value { - Table(child_table) -> key <> "." <> table_to_string(child_table) - _ -> acc <> key <> " = " <> value |> value_to_string <> "\n" - } - } table - |> dict.fold("", fold_to_string) + |> dict.fold("", fold_table_to_string) +} + +fn fold_table_to_string(acc: String, key: String, value: Toml) -> String { + case value { + Table(child_table) -> acc <> format_table(key, child_table) + _ -> acc <> key <> " = " <> value_to_string(value) <> "\n" + } +} + +fn format_table(key: String, table: Dict(String, Toml)) -> String { + case dict.size(table) { + 0 -> "[" <> key <> "]\n\n" + 1 -> key <> "." <> table_to_string(table) + _ -> "[" <> key <> "]\n" <> table_to_string(table) <> "\n" + } } fn inline_table_to_string(table: Dict(String, Toml)) -> String { diff --git a/test/tom_test.gleam b/test/tom_test.gleam index 326d6f6..fbd4380 100644 --- a/test/tom_test.gleam +++ b/test/tom_test.gleam @@ -1,4 +1,5 @@ import gleam/dict +import gleam/io import gleam/result import gleeunit import gleeunit/should @@ -218,7 +219,7 @@ pub fn parse_multi_segment_key_test() { } pub fn serialize_multi_segment_key_test() { - let expected = "one.two.three = true\n\n" + let expected = "one.two.three = true\n" dict.from_list([ #( "one", @@ -336,7 +337,7 @@ pub fn parse_table_test() { } pub fn serialize_table_test() { - let expected = "[a]\n" + let expected = "[a]\n\n" dict.from_list([#("a", tom.Table(dict.from_list([])))]) |> tom.serialize |> should.equal(expected) @@ -364,7 +365,7 @@ b.c = 2 } pub fn serialize_table_with_values_test() { - let expected = "a.a = 1\na.b.c = 2" + let expected = "[a]\na = 1\nb.c = 2\n\n" dict.from_list([ #( "a", @@ -406,34 +407,60 @@ b.c = 2 |> should.equal(Ok(expected)) } -pub fn parse_multiple_tables_test() { +pub fn serialize_table_with_values_before_test() { let expected = - dict.from_list([ - #("name", tom.String("Joe")), - #("size", tom.Int(123)), - #( - "a", - tom.Table( - dict.from_list([ - #("a", tom.Int(1)), - #("b", tom.Table(dict.from_list([#("c", tom.Int(2))]))), - ]), - ), - ), - #("b", tom.Table(dict.from_list([#("a", tom.Int(1))]))), - ]) - "name = \"Joe\" + "[a] +a = 1 +b.c = 2 + +name = \"Joe\" size = 123 +" + dict.from_list([ + #("name", tom.String("Joe")), + #("size", tom.Int(123)), + #( + "a", + tom.Table( + dict.from_list([ + #("a", tom.Int(1)), + #("b", tom.Table(dict.from_list([#("c", tom.Int(2))]))), + ]), + ), + ), + ]) + |> tom.serialize + |> should.equal(expected) +} -[a] +pub fn serialize_multiple_tables_test() { + let expected = + "[a] a = 1 b.c = 2 [b] a = 1 + +name = \"Joe\" +size = 123 " - |> tom.parse - |> should.equal(Ok(expected)) + dict.from_list([ + #("name", tom.String("Joe")), + #("size", tom.Int(123)), + #( + "a", + tom.Table( + dict.from_list([ + #("a", tom.Int(1)), + #("b", tom.Table(dict.from_list([#("c", tom.Int(2))]))), + ]), + ), + ), + #("b", tom.Table(dict.from_list([#("a", tom.Int(1))]))), + ]) + |> tom.serialize + |> should.equal(expected) } pub fn parse_inline_table_empty_test() { @@ -1064,3 +1091,10 @@ still_a_field = 1" tom.get(toml, ["still_a_section", "still_a_field"]) |> should.equal(Ok(tom.Int(1))) } + +pub fn array_to_string_test() { + let expected = "[123, true, \"test\"]" + let content = [tom.Int(123), tom.Bool(True), tom.String("test")] + tom.array_to_string(content) + |> should.equal(expected) +} From 1985d49b79f66426d67509b77a375e216f55bf41 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 18 Sep 2024 01:35:55 -0500 Subject: [PATCH 3/4] checkpoint --- src/tom.gleam | 178 +++++++++++++++++++++++++++++++++++++++----- test/tom_test.gleam | 43 +++++++++-- 2 files changed, 196 insertions(+), 25 deletions(-) diff --git a/src/tom.gleam b/src/tom.gleam index 2e0b975..596b88b 100644 --- a/src/tom.gleam +++ b/src/tom.gleam @@ -1332,10 +1332,10 @@ fn parse_offset_hours(input: Tokens, sign: Sign) -> Parsed(Offset) { } pub fn serialize(value: Dict(String, Toml)) -> String { - value_to_string(Table(value)) + serialize_root_table(value) } -pub fn value_to_string(val: Toml) -> String { +pub fn serialize_value(val: Toml) -> String { case val { Float(value) -> value |> float.to_string Int(value) -> value |> int.to_string @@ -1348,12 +1348,19 @@ pub fn value_to_string(val: Toml) -> String { DateTime(value) -> date_time_to_string(value) Array(list) -> array_to_string(list) ArrayOfTables(list) -> table_array_to_string(list) - Table(table) -> table_to_string(table) + Table(table) -> serialize_table(table) InlineTable(table) -> inline_table_to_string(table) } } -pub fn array_to_string(array: List(Toml)) -> String { +fn serialize_table(table: Dict(String, Toml)) -> String { + let #(child_values, child_tables) = split_tables_dict(table) + let serialized_child_values = dict.fold(child_values, "", fold_values) + let serialized_table_values = dict.fold(child_tables, "", fold_child_tables) + todo +} + +pub fn serialize_array(array: List(Toml)) -> String { let joined = array |> list.map(value_to_string) @@ -1361,35 +1368,168 @@ pub fn array_to_string(array: List(Toml)) -> String { "[" <> joined <> "]" } -fn sign_to_string(sign: Sign) -> String { +fn serialize_sign(sign: Sign) -> String { case sign { Positive -> "+" Negative -> "-" } } -fn table_array_to_string(table_array: List(Dict(String, Toml))) { +fn serialize_table_array(table_array: List(Dict(String, Toml))) { table_array - |> list.fold("", fn(acc, table) { acc <> table_to_string(table) }) + |> list.fold("", fn(acc, table) { acc <> table_to_string("", table) }) +} + +// fn collapse_table(name: String, table: Dict(String, Toml)) -> String { +// case name, dict.to_list(table) { +// "", _ -> panic as "Error: Table has no name" +// name, [] -> "[" <> name <> "]\n\n" +// name, [#(child_name, Table(child_table))] -> +// collapse_table(name <> "." <> child_name, child_table) +// name, [#(child_name, value)] -> +// name <> "." <> child_name <> " = " <> value_to_string(value) <> "\n" +// name, children -> { +// let #(children_values, children_tables) = split_tables(children) +// let table_header = "[" <> name <> "]\n" +// let serialized_values = list.fold(children_values, "", fold_values) +// let serialized_tables = list.fold(children_tables, "", fold_child_tables) +// table_header <> serialized_values <> serialized_tables +// } +// } +// } + +// fn table_to_string(name: String, table: Dict(String, Toml)) -> String { +// let #(child_values, child_tables) = split_tables(dict.to_list(table)) +// let serialized_values = +// child_values +// |> list.map(fn(value) { #(name <> "." <> value.0, value.1) }) +// |> list.fold("", fold_values) +// let serialized_tables = +// child_tables +// |> list.map(fn(value) { #(name <> "." <> value.0, value.1) }) +// |> list.fold("", fold_child_tables) +// serialized_values <> serialized_tables <> "\n" +// } + +fn serialize_root_table(table: Dict(String, Toml)) { + let #(child_values, child_tables) = split_tables(dict.to_list(table)) + let child_values_serialized = list.fold(child_values, "", fold_values) + let child_tables_serialized = list.fold(child_tables, "", fold_root_tables) + case child_values_serialized, child_tables_serialized { + "\n", _ -> + panic as "Erroneous \n on child_values_serialized in serialize_root_tables" + _, "\n" -> + panic as "Erroneous \n on child_tables_serialized in serialize_root_table" + "", tables -> tables + values, "" -> values + values, tables -> values <> "\n" <> tables + } +} + +fn fold_root_tables(acc, entry: #(String, Toml)) -> String { + case entry { + #("", _) -> panic as { "Error: Table has no name" } + #(name, Table(child)) -> { + let #(collapsed_name, collapsed_table) = collapse_table(name, child) + "[" <> collapsed_name <> "]\n" <> serialize_table(collapsed_table) + } + #(_, _) -> + panic as "Error: Unexpected non-table value. This is an issue with the Tom library." + } +} + + + +fn fold_child_tables(acc: String, key: String, value: Toml) { + case key, value { + "", _ -> panic as "Table has no name" + child_name, Table(child) -> {} + _, _ -> panic as "Unexpected non-table value" + } +} + +fn collapse_table( + name: String, + table: Dict(String, Toml), +) -> #(String, Dict(String, Toml)) { + case dict.to_list(table) { + [#(child_name, Table(child_table))] -> + collapse_table(name <> "." <> child_name, child_table) + _ -> #(name, table) + } } -fn table_to_string(table: Dict(String, Toml)) -> String { - table - |> dict.fold("", fold_table_to_string) +// fn collapse_table_to_values( +// table_name: String, +// table: Dict(String, Toml), +// ) -> Dict(String, Toml) { +// let appended_name = case table_name { +// "" -> "" +// name -> name <> "." +// } +// table +// |> dict.fold(dict.from_list([]), fn(acc, key, value) { +// case value { +// Table(child) -> collapse_table_to_values(appended_name <> key, child) +// child_value -> dict.from_list([#(appended_name <> key, child_value)]) +// } +// }) +// } + +// fn fold_child_tables(acc, entry: #(String, Toml)) -> String { + // case entry { + // #("", _) -> panic as { "Error: Table has no name" } + // #(name, Table(child)) -> acc <> table_to_string(name, child) + // #(_, _) -> + // panic as "Error: Unexpected non-table value. This is an issue with the Tom library." + // } +// } + +fn fold_values(acc, k, v) -> String { + case k, v { + "", value -> + panic as { + "Error: Entry has no name, but value of " <> value_to_string(value) + } + name, value -> acc <> name <> " = " <> value_to_string(value) <> "\n" + } +} + +fn split_tables(values: List(#(String, Toml))) { + #(list.filter(values, filter_values), list.filter(values, filter_tables)) +} + +fn split_tables_dict( + table: Dict(String, Toml), +) -> #(Dict(String, Toml), Dict(String, Toml)) { + let values = + dict.filter(table, fn(k, v) { + case v { + Table(_) -> False + _ -> True + } + }) + let tables = + dict.filter(table, fn(_, v) { + case v { + Table(_) -> True + _ -> False + } + }) + #(values, tables) } -fn fold_table_to_string(acc: String, key: String, value: Toml) -> String { - case value { - Table(child_table) -> acc <> format_table(key, child_table) - _ -> acc <> key <> " = " <> value_to_string(value) <> "\n" +fn filter_tables(value: #(String, Toml)) -> Bool { + case value.1 { + Table(_) -> True + _ -> False } } -fn format_table(key: String, table: Dict(String, Toml)) -> String { - case dict.size(table) { - 0 -> "[" <> key <> "]\n\n" - 1 -> key <> "." <> table_to_string(table) - _ -> "[" <> key <> "]\n" <> table_to_string(table) <> "\n" +fn filter_values(value: #(String, Toml)) -> Bool { + case value.1 { + Table(_) -> False + _ -> True } } diff --git a/test/tom_test.gleam b/test/tom_test.gleam index fbd4380..d558c18 100644 --- a/test/tom_test.gleam +++ b/test/tom_test.gleam @@ -409,12 +409,13 @@ b.c = 2 pub fn serialize_table_with_values_before_test() { let expected = - "[a] + "name = \"Joe\" +size = 123 + +[a] a = 1 b.c = 2 -name = \"Joe\" -size = 123 " dict.from_list([ #("name", tom.String("Joe")), @@ -433,17 +434,47 @@ size = 123 |> should.equal(expected) } -pub fn serialize_multiple_tables_test() { +pub fn parse_multiple_tables_test() { let expected = - "[a] + dict.from_list([ + #("name", tom.String("Joe")), + #("size", tom.Int(123)), + #( + "a", + tom.Table( + dict.from_list([ + #("a", tom.Int(1)), + #("b", tom.Table(dict.from_list([#("c", tom.Int(2))]))), + ]), + ), + ), + #("b", tom.Table(dict.from_list([#("a", tom.Int(1))]))), + ]) + "name = \"Joe\" +size = 123 + +[a] a = 1 b.c = 2 [b] a = 1 +" + |> tom.parse + |> should.equal(Ok(expected)) +} -name = \"Joe\" +pub fn serialize_multiple_tables_test() { + let expected = + "name = \"Joe\" size = 123 + +[a] +a = 1 +b.c = 2 + +[b] +a = 1 " dict.from_list([ #("name", tom.String("Joe")), From 1d9d1c372592e8357ac98d5c0da06acf8badc233 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 18 Sep 2024 02:40:04 -0500 Subject: [PATCH 4/4] table serialization working --- src/tom.gleam | 267 ++++++++++++++++++-------------------------- test/tom_test.gleam | 47 +++++--- 2 files changed, 140 insertions(+), 174 deletions(-) diff --git a/src/tom.gleam b/src/tom.gleam index 596b88b..2897c72 100644 --- a/src/tom.gleam +++ b/src/tom.gleam @@ -1339,31 +1339,46 @@ pub fn serialize_value(val: Toml) -> String { case val { Float(value) -> value |> float.to_string Int(value) -> value |> int.to_string - Infinity(sign) -> sign |> sign_to_string <> "inf" - Nan(sign) -> sign |> sign_to_string <> "nan" + Infinity(sign) -> sign |> serialize_sign <> "inf" + Nan(sign) -> sign |> serialize_sign <> "nan" Bool(value) -> value |> bool.to_string |> string.lowercase String(value) -> "\"" <> value <> "\"" - Date(value) -> date_to_string(value) - Time(value) -> time_to_string(value) - DateTime(value) -> date_time_to_string(value) - Array(list) -> array_to_string(list) - ArrayOfTables(list) -> table_array_to_string(list) + Date(value) -> serialize_date(value) + Time(value) -> serialize_time(value) + DateTime(value) -> serialize_date_time(value) + Array(list) -> serialize_array(list) + ArrayOfTables(list) -> serialize_table_array(list) Table(table) -> serialize_table(table) - InlineTable(table) -> inline_table_to_string(table) + InlineTable(table) -> serialize_inline_table(table) + } +} + +fn serialize_root_table(table: Dict(String, Toml)) { + let #(child_values, child_tables) = split_tables(table) + let child_values_serialized = dict.fold(child_values, "", fold_values) + let child_tables_serialized = dict.fold(child_tables, "", fold_root_tables) + case child_values_serialized, child_tables_serialized { + "\n", _ -> + panic as "Erroneous \n on child_values_serialized in serialize_root_tables" + _, "\n" -> + panic as "Erroneous \n on child_tables_serialized in serialize_root_table" + "", tables -> tables + values, "" -> values + values, tables -> values <> "\n" <> tables } } fn serialize_table(table: Dict(String, Toml)) -> String { - let #(child_values, child_tables) = split_tables_dict(table) + let #(child_values, child_tables) = split_tables(table) let serialized_child_values = dict.fold(child_values, "", fold_values) let serialized_table_values = dict.fold(child_tables, "", fold_child_tables) - todo + serialized_child_values <> serialized_table_values } pub fn serialize_array(array: List(Toml)) -> String { let joined = array - |> list.map(value_to_string) + |> list.map(serialize_value) |> string.join(", ") "[" <> joined <> "]" } @@ -1377,77 +1392,101 @@ fn serialize_sign(sign: Sign) -> String { fn serialize_table_array(table_array: List(Dict(String, Toml))) { table_array - |> list.fold("", fn(acc, table) { acc <> table_to_string("", table) }) -} - -// fn collapse_table(name: String, table: Dict(String, Toml)) -> String { -// case name, dict.to_list(table) { -// "", _ -> panic as "Error: Table has no name" -// name, [] -> "[" <> name <> "]\n\n" -// name, [#(child_name, Table(child_table))] -> -// collapse_table(name <> "." <> child_name, child_table) -// name, [#(child_name, value)] -> -// name <> "." <> child_name <> " = " <> value_to_string(value) <> "\n" -// name, children -> { -// let #(children_values, children_tables) = split_tables(children) -// let table_header = "[" <> name <> "]\n" -// let serialized_values = list.fold(children_values, "", fold_values) -// let serialized_tables = list.fold(children_tables, "", fold_child_tables) -// table_header <> serialized_values <> serialized_tables -// } -// } -// } - -// fn table_to_string(name: String, table: Dict(String, Toml)) -> String { -// let #(child_values, child_tables) = split_tables(dict.to_list(table)) -// let serialized_values = -// child_values -// |> list.map(fn(value) { #(name <> "." <> value.0, value.1) }) -// |> list.fold("", fold_values) -// let serialized_tables = -// child_tables -// |> list.map(fn(value) { #(name <> "." <> value.0, value.1) }) -// |> list.fold("", fold_child_tables) -// serialized_values <> serialized_tables <> "\n" -// } + |> list.fold("", fn(acc, table) { acc <> serialize_table(table) }) +} -fn serialize_root_table(table: Dict(String, Toml)) { - let #(child_values, child_tables) = split_tables(dict.to_list(table)) - let child_values_serialized = list.fold(child_values, "", fold_values) - let child_tables_serialized = list.fold(child_tables, "", fold_root_tables) - case child_values_serialized, child_tables_serialized { - "\n", _ -> - panic as "Erroneous \n on child_values_serialized in serialize_root_tables" - _, "\n" -> - panic as "Erroneous \n on child_tables_serialized in serialize_root_table" - "", tables -> tables - values, "" -> values - values, tables -> values <> "\n" <> tables +fn serialize_inline_table(table: Dict(String, Toml)) -> String { + let fold_to_string = fn(acc, key, value) { + acc <> key <> "." <> value |> serialize_value <> ", " + } + table |> dict.fold("{", fold_to_string) <> "}" +} + +fn serialize_date(date: Date) -> String { + [date.year, date.month, date.day] + |> list.map(int.to_string) + |> string.join("-") +} + +fn serialize_time(time: Time) -> String { + let ms = int.to_string(time.millisecond) + [time.hour, time.minute, time.second] + |> list.map(int.to_string) + |> string.join(":") + <> "." + <> ms +} + +fn serialize_date_time(date_time: DateTime) -> String { + let date = date_time.date |> serialize_date + let time = date_time.time |> serialize_time + let offset = date_time.offset |> serialize_offset + date <> " " <> time <> offset +} + +fn serialize_offset(offset: Offset) -> String { + case offset { + Local -> "" + Offset(sign, hours, minutes) -> + serialize_sign(sign) + <> int.to_string(hours) + <> ":" + <> int.to_string(minutes) } } -fn fold_root_tables(acc, entry: #(String, Toml)) -> String { - case entry { - #("", _) -> panic as { "Error: Table has no name" } - #(name, Table(child)) -> { - let #(collapsed_name, collapsed_table) = collapse_table(name, child) - "[" <> collapsed_name <> "]\n" <> serialize_table(collapsed_table) +fn fold_root_tables(acc, key, value) -> String { + case key, value { + "", _ -> panic as { "Error: Table has no name" } + name, Table(table) -> { + let #(collapsed_name, collapsed_table) = collapse_table(name, table) + io.debug(collapsed_name <> ":" <> serialize_table(collapsed_table)) + case dict.to_list(collapsed_table) { + [] -> acc <> "[" <> collapsed_name <> "]\n\n" + [#(child_name, child_value)] -> + collapsed_name + <> "." + <> child_name + <> " = " + <> serialize_value(child_value) + <> "\n\n" + <> acc + _ -> + acc + <> "[" + <> collapsed_name + <> "]\n" + <> serialize_table(collapsed_table) + <> "\n" + } } - #(_, _) -> + _, _ -> panic as "Error: Unexpected non-table value. This is an issue with the Tom library." } } - - fn fold_child_tables(acc: String, key: String, value: Toml) { case key, value { "", _ -> panic as "Table has no name" - child_name, Table(child) -> {} + child_name, Table(child_table) -> { + let #(collapsed_name, collapsed_table) = + collapse_table(child_name, child_table) + acc <> collapsed_name <> "." <> serialize_table(collapsed_table) + } _, _ -> panic as "Unexpected non-table value" } } +fn fold_values(acc, k, v) -> String { + case k, v { + "", value -> + panic as { + "Error: Entry has no name, but value of " <> serialize_value(value) + } + name, value -> acc <> name <> " = " <> serialize_value(value) <> "\n" + } +} + fn collapse_table( name: String, table: Dict(String, Toml), @@ -1459,47 +1498,7 @@ fn collapse_table( } } -// fn collapse_table_to_values( -// table_name: String, -// table: Dict(String, Toml), -// ) -> Dict(String, Toml) { -// let appended_name = case table_name { -// "" -> "" -// name -> name <> "." -// } -// table -// |> dict.fold(dict.from_list([]), fn(acc, key, value) { -// case value { -// Table(child) -> collapse_table_to_values(appended_name <> key, child) -// child_value -> dict.from_list([#(appended_name <> key, child_value)]) -// } -// }) -// } - -// fn fold_child_tables(acc, entry: #(String, Toml)) -> String { - // case entry { - // #("", _) -> panic as { "Error: Table has no name" } - // #(name, Table(child)) -> acc <> table_to_string(name, child) - // #(_, _) -> - // panic as "Error: Unexpected non-table value. This is an issue with the Tom library." - // } -// } - -fn fold_values(acc, k, v) -> String { - case k, v { - "", value -> - panic as { - "Error: Entry has no name, but value of " <> value_to_string(value) - } - name, value -> acc <> name <> " = " <> value_to_string(value) <> "\n" - } -} - -fn split_tables(values: List(#(String, Toml))) { - #(list.filter(values, filter_values), list.filter(values, filter_tables)) -} - -fn split_tables_dict( +fn split_tables( table: Dict(String, Toml), ) -> #(Dict(String, Toml), Dict(String, Toml)) { let values = @@ -1518,57 +1517,3 @@ fn split_tables_dict( }) #(values, tables) } - -fn filter_tables(value: #(String, Toml)) -> Bool { - case value.1 { - Table(_) -> True - _ -> False - } -} - -fn filter_values(value: #(String, Toml)) -> Bool { - case value.1 { - Table(_) -> False - _ -> True - } -} - -fn inline_table_to_string(table: Dict(String, Toml)) -> String { - let fold_to_string = fn(acc, key, value) { - acc <> key <> " = " <> value |> value_to_string <> ", " - } - table |> dict.fold("{ ", fold_to_string) <> "}" -} - -fn date_to_string(date: Date) -> String { - [date.year, date.month, date.day] - |> list.map(int.to_string) - |> string.join("-") -} - -fn time_to_string(time: Time) -> String { - let ms = int.to_string(time.millisecond) - [time.hour, time.minute, time.second] - |> list.map(int.to_string) - |> string.join(":") - <> "." - <> ms -} - -fn date_time_to_string(date_time: DateTime) -> String { - let date = date_time.date |> date_to_string - let time = date_time.time |> time_to_string - let offset = date_time.offset |> offset_to_string - date <> " " <> time <> offset -} - -fn offset_to_string(offset: Offset) -> String { - case offset { - Local -> "" - Offset(sign, hours, minutes) -> - sign_to_string(sign) - <> int.to_string(hours) - <> ":" - <> int.to_string(minutes) - } -} diff --git a/test/tom_test.gleam b/test/tom_test.gleam index d558c18..1ec0899 100644 --- a/test/tom_test.gleam +++ b/test/tom_test.gleam @@ -219,7 +219,7 @@ pub fn parse_multi_segment_key_test() { } pub fn serialize_multi_segment_key_test() { - let expected = "one.two.three = true\n" + let expected = "one.two.three = true\n\n" dict.from_list([ #( "one", @@ -408,15 +408,7 @@ b.c = 2 } pub fn serialize_table_with_values_before_test() { - let expected = - "name = \"Joe\" -size = 123 - -[a] -a = 1 -b.c = 2 - -" + let expected = "name = \"Joe\"\nsize = 123\n\n[a]\na = 1\nb.c = 2\n\n" dict.from_list([ #("name", tom.String("Joe")), #("size", tom.Int(123)), @@ -469,12 +461,12 @@ pub fn serialize_multiple_tables_test() { "name = \"Joe\" size = 123 +b.a = 1 + [a] a = 1 b.c = 2 -[b] -a = 1 " dict.from_list([ #("name", tom.String("Joe")), @@ -501,6 +493,13 @@ pub fn parse_inline_table_empty_test() { |> should.equal(Ok(expected)) } +pub fn serialize_inline_table_empty_test() { + let expected = "a = {}\n" + dict.from_list([#("a", tom.InlineTable(dict.from_list([])))]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_inline_table_test() { let expected = dict.from_list([ @@ -523,6 +522,28 @@ pub fn parse_inline_table_test() { |> should.equal(Ok(expected)) } +pub fn serialize_inline_table_test() { + let expected = + "a = { + a = 1, + b.c = 2 +} +" + dict.from_list([ + #( + "a", + tom.InlineTable( + dict.from_list([ + #("a", tom.Int(1)), + #("b", tom.Table(dict.from_list([#("c", tom.Int(2))]))), + ]), + ), + ), + ]) + |> tom.serialize + |> should.equal(expected) +} + pub fn parse_inline_trailing_comma_table_test() { let expected = dict.from_list([ @@ -1126,6 +1147,6 @@ still_a_field = 1" pub fn array_to_string_test() { let expected = "[123, true, \"test\"]" let content = [tom.Int(123), tom.Bool(True), tom.String("test")] - tom.array_to_string(content) + tom.serialize_array(content) |> should.equal(expected) }