• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xu-chris / toon_ex / 0d6dfa778ba2d45cc665b3716f8a063d6bb4bd99

21 Jan 2026 06:57AM UTC coverage: 54.957% (-8.8%) from 63.791%
0d6dfa778ba2d45cc665b3716f8a063d6bb4bd99

push

github

Chris Xu
Revert "refactor: improve code quality and test coverage (#10)"

This reverts commit c762e615a.

136 of 290 new or added lines in 6 files covered. (46.9%)

61 existing lines in 9 files now uncovered.

510 of 928 relevant lines covered (54.96%)

16.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.61
/lib/toon/encode/objects.ex
1
defmodule Toon.Encode.Objects do
2
  @moduledoc """
3
  Encoding of TOON objects (maps).
4
  """
5

6
  alias Toon.Constants
7
  alias Toon.Encode.{Arrays, Primitives, Strings, Writer}
8
  alias Toon.Utils
9

10
  @doc """
11
  Encodes a map to TOON format.
12

13
  ## Examples
14

15
      iex> opts = %{indent: 2, delimiter: ",", length_marker: nil}
16
      iex> map = %{"name" => "Alice", "age" => 30}
17
      iex> Toon.Encode.Objects.encode(map, 0, opts)
18

19
  """
20
  @spec encode(map(), non_neg_integer(), map()) :: [iodata()]
21
  def encode(map, depth, opts) when is_map(map) do
22
    writer = Writer.new(opts.indent)
38✔
23

24
    # Get the keys in the correct order
25
    keys = get_ordered_keys(map, Map.get(opts, :key_order), [])
38✔
26

27
    writer =
38✔
28
      keys
29
      |> Enum.reduce(writer, fn key, acc ->
30
        value = Map.get(map, key)
57✔
31
        encode_entry(acc, key, value, depth, opts)
57✔
32
      end)
33

34
    Writer.to_iodata(writer)
38✔
35
  end
36

37
  # Get keys in the correct order based on key_order option
38
  defp get_ordered_keys(map, key_order, path) do
39
    cond do
38✔
40
      # If we have key order information for this path, use it
41
      is_map(key_order) and Map.has_key?(key_order, path) ->
38✔
NEW
42
        ordered = Map.get(key_order, path)
×
43
        # Filter to only include keys that exist in the map
UNCOV
44
        Enum.filter(ordered, &Map.has_key?(map, &1))
×
45

46
      # If key_order is a list (simple case) and this is the root level, use it
47
      is_list(key_order) and not Enum.empty?(key_order) and path == [] ->
38✔
48
        # Filter to only include keys that exist in the map and match the order
NEW
49
        existing_keys = Map.keys(map)
×
NEW
50
        ordered_existing = Enum.filter(key_order, &(&1 in existing_keys))
×
51

52
        # If all keys are in the order list, use it; otherwise fallback to sorted
NEW
53
        if length(ordered_existing) == length(existing_keys) do
×
NEW
54
          ordered_existing
×
55
        else
NEW
56
          Enum.sort(existing_keys)
×
57
        end
58

59
      # If key_order is nil or not applicable, sort keys alphabetically for consistent output
60
      true ->
38✔
61
        Map.keys(map) |> Enum.sort()
38✔
62
    end
63
  end
64

65
  @doc """
66
  Encodes a single key-value pair.
67
  """
68
  @spec encode_entry(Writer.t(), String.t(), term(), non_neg_integer(), map()) :: Writer.t()
69
  def encode_entry(writer, key, value, depth, opts) do
70
    encoded_key = Strings.encode_key(key)
57✔
71

72
    cond do
57✔
73
      Utils.primitive?(value) ->
74
        # Inline format: key: value
75
        line = [
38✔
76
          encoded_key,
77
          Constants.colon(),
78
          Constants.space(),
79
          Primitives.encode(value, opts.delimiter)
38✔
80
        ]
81

82
        Writer.push(writer, line, depth)
38✔
83

84
      Utils.list?(value) ->
19✔
85
        # Delegate to Arrays module
86
        array_lines = Arrays.encode(key, value, depth, opts)
7✔
87
        append_lines(writer, array_lines, depth)
7✔
88

89
      Utils.map?(value) ->
12✔
90
        # Nested object
91
        header = [encoded_key, Constants.colon()]
12✔
92
        writer = Writer.push(writer, header, depth)
12✔
93

94
        nested_lines = encode(value, depth + 1, opts)
12✔
95
        append_iodata(writer, nested_lines, depth + 1)
12✔
96

NEW
97
      true ->
×
98
        # Unsupported type, encode as null
NEW
99
        line = [encoded_key, Constants.colon(), Constants.space(), Constants.null_literal()]
×
NEW
100
        Writer.push(writer, line, depth)
×
101
    end
102
  end
103

104
  # Private helpers
105

106
  defp append_lines(writer, [header | data_rows], depth) do
107
    # For arrays, the first line is the header at current depth
108
    # Subsequent lines (data rows for tabular format) should be one level deeper
109
    writer = Writer.push(writer, header, depth)
7✔
110

111
    Enum.reduce(data_rows, writer, fn row, acc ->
7✔
112
      Writer.push(acc, row, depth + 1)
8✔
113
    end)
114
  end
115

116
  defp append_iodata(writer, iodata, _base_depth) do
117
    # Convert iodata to string, split by lines, and add to writer
118
    iodata
119
    |> IO.iodata_to_binary()
120
    |> String.split("\n")
121
    |> Enum.reduce(writer, fn line, acc ->
12✔
122
      # Lines from nested encode already have relative indentation,
123
      # but we need to add them without additional depth since encode()
124
      # already handles depth
125
      if line == "" do
23✔
126
        acc
1✔
127
      else
128
        # Extract existing indentation and preserve it
129
        Writer.push(acc, line, 0)
22✔
130
      end
131
    end)
132
  end
133
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc