defmodule YarnParser.Encoder do @moduledoc false def encode(map, opts \\ []) do version = Keyword.get(opts, :version, 1) no_header = Keyword.get(opts, :no_header, false) if no_header do do_encode(map, top_level: true) else """ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v#{version} #{do_encode(map, top_level: true)} """ end end defp do_encode(map, opts) do indent = Keyword.get(opts, :indent, "") top_level = Keyword.get(opts, :top_level, false) keys = Map.keys(map) |> sort_keys() {_, lines} = Enum.reduce(keys, {[], []}, fn "comments", {pk, acc} -> {pk, acc} key, {processed_keys, acc} -> val = map[key] cond do is_map(val) and key not in processed_keys -> matched_keys = Enum.filter(keys, fn x -> map[x] == val end) keys_line = matched_keys |> Enum.sort() |> Enum.map(&maybe_wrap/1) |> Enum.join(", ") processed_keys = processed_keys ++ matched_keys trailing_str = if top_level, do: "\n", else: "" encoded_content = do_encode(val, indent: indent <> " ") line = "#{keys_line}:\n#{encoded_content}#{trailing_str}" {processed_keys, acc ++ [line]} is_map(val) -> {processed_keys, acc} true -> {processed_keys, acc ++ ["#{key} #{maybe_wrap(val)}"]} end end) indent <> Enum.join(lines, "\n#{indent}") end defp maybe_wrap(key) when is_atom(key) do Atom.to_string(key) end defp maybe_wrap(key) when is_binary(key) do if should_wrap_key(key) do ~s("#{key}") else key end end defp maybe_wrap(key), do: "#{key}" defp should_wrap_key(key)do String.starts_with?(key, "true") || String.starts_with?(key, "false") || Regex.match?(~r/[:\s\n\\",\[\]]/, key) || Regex.match?(~r/^[0-9]/, key) || !Regex.match?(~r/^[a-zA-Z]/, key) end @priorities [ "name", "version", "uid", "resolved", "integrity", "registry", "dependencies" ] |> Enum.with_index(1) |> Enum.into(%{}) defp sort_keys(keys) do Enum.sort(keys, fn a, b -> if @priorities[a] || @priorities[b] do (@priorities[a] || 100) < (@priorities[b] || 100) else a < b end end) end end