zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

formatter.ex (8189B)


      1 defmodule Jason.Formatter do
      2   @moduledoc ~S"""
      3   Pretty-printing and minimizing functions for JSON-encoded data.
      4 
      5   Input is required to be in an 8-bit-wide encoding such as UTF-8 or Latin-1
      6   in `t:iodata/0` format. Input must have valid JSON, invalid JSON may produce
      7   unexpected results or errors.
      8   """
      9 
     10   @type opts :: [
     11           {:indent, iodata}
     12           | {:line_separator, iodata}
     13           | {:record_separator, iodata}
     14           | {:after_colon, iodata}
     15         ]
     16 
     17   import Record
     18   defrecordp :opts, [:indent, :line, :record, :colon]
     19 
     20   @dialyzer :no_improper_lists
     21 
     22   @doc ~S"""
     23   Pretty-prints JSON-encoded `input`.
     24 
     25   `input` may contain multiple JSON objects or arrays, optionally separated
     26   by whitespace (e.g., one object per line). Objects in output will be
     27   separated by newlines. No trailing newline is emitted.
     28 
     29   ## Options
     30 
     31     * `:indent` - used for nested objects and arrays (default: two spaces - `"  "`);
     32     * `:line_separator` - used in nested objects (default: `"\n"`);
     33     * `:record_separator` - separates root-level objects and arrays
     34       (default is the value for `:line_separator` option);
     35     * `:after_colon` - printed after a colon inside objects (default: one space - `" "`).
     36 
     37   ## Examples
     38 
     39       iex> Jason.Formatter.pretty_print(~s|{"a":{"b": [1, 2]}}|)
     40       ~s|{
     41         "a": {
     42           "b": [
     43             1,
     44             2
     45           ]
     46         }
     47       }|
     48 
     49   """
     50   @spec pretty_print(iodata, opts) :: binary
     51   def pretty_print(input, opts \\ []) do
     52     input
     53     |> pretty_print_to_iodata(opts)
     54     |> IO.iodata_to_binary()
     55   end
     56 
     57   @doc ~S"""
     58   Pretty-prints JSON-encoded `input` and returns iodata.
     59 
     60   This function should be preferred to `pretty_print/2`, if the pretty-printed
     61   JSON will be handed over to one of the IO functions or sent
     62   over the socket. The Erlang runtime is able to leverage vectorised
     63   writes and avoid allocating a continuous buffer for the whole
     64   resulting string, lowering memory use and increasing performance.
     65   """
     66   @spec pretty_print_to_iodata(iodata, opts) :: iodata
     67   def pretty_print_to_iodata(input, opts \\ []) do
     68     opts = parse_opts(opts, "  ", "\n", nil, " ")
     69 
     70     depth = :first
     71     empty = false
     72 
     73     {output, _state} = pp_iodata(input, [], depth, empty, opts)
     74 
     75     output
     76   end
     77 
     78   @doc ~S"""
     79   Minimizes JSON-encoded `input`.
     80 
     81   `input` may contain multiple JSON objects or arrays, optionally
     82   separated by whitespace (e.g., one object per line). Minimized
     83   output will contain one object per line. No trailing newline is emitted.
     84 
     85   ## Options
     86 
     87     * `:record_separator` - controls the string used as newline (default: `"\n"`).
     88 
     89   ## Examples
     90 
     91       iex> Jason.Formatter.minimize(~s|{ "a" : "b" , "c": \n\n 2}|)
     92       ~s|{"a":"b","c":2}|
     93 
     94   """
     95   @spec minimize(iodata, opts) :: binary
     96   def minimize(input, opts \\ []) do
     97     input
     98     |> minimize_to_iodata(opts)
     99     |> IO.iodata_to_binary()
    100   end
    101 
    102   @doc ~S"""
    103   Minimizes JSON-encoded `input` and returns iodata.
    104 
    105   This function should be preferred to `minimize/2`, if the minimized
    106   JSON will be handed over to one of the IO functions or sent
    107   over the socket. The Erlang runtime is able to leverage vectorised
    108   writes and avoid allocating a continuous buffer for the whole
    109   resulting string, lowering memory use and increasing performance.
    110   """
    111   @spec minimize_to_iodata(iodata, opts) :: iodata
    112   def minimize_to_iodata(input, opts) do
    113     record = Keyword.get(opts, :record_separator, "\n")
    114     opts = opts(indent: "", line: "", record: record, colon: "")
    115 
    116     depth = :first
    117     empty = false
    118 
    119     {output, _state} = pp_iodata(input, [], depth, empty, opts)
    120 
    121     output
    122   end
    123 
    124   defp parse_opts([{option, value} | opts], indent, line, record, colon) do
    125     value = IO.iodata_to_binary(value)
    126     case option do
    127       :indent -> parse_opts(opts, value, line, record, colon)
    128       :record_separator -> parse_opts(opts, indent, line, value, colon)
    129       :after_colon -> parse_opts(opts, indent, line, record, value)
    130       :line_separator -> parse_opts(opts, indent, value, record || value, colon)
    131     end
    132   end
    133 
    134   defp parse_opts([], indent, line, record, colon) do
    135     opts(indent: indent, line: line, record: record || line, colon: colon)
    136   end
    137 
    138   for depth <- 1..16 do
    139     defp tab("  ", unquote(depth)), do: unquote(String.duplicate("  ", depth))
    140   end
    141 
    142   defp tab("", _), do: ""
    143   defp tab(indent, depth), do: List.duplicate(indent, depth)
    144 
    145   defp pp_iodata(<<>>, output_acc, depth, empty, opts) do
    146     {output_acc, &pp_iodata(&1, &2, depth, empty, opts)}
    147   end
    148 
    149   defp pp_iodata(<<byte, rest::binary>>, output_acc, depth, empty, opts) do
    150     pp_byte(byte, rest, output_acc, depth, empty, opts)
    151   end
    152 
    153   defp pp_iodata([], output_acc, depth, empty, opts) do
    154     {output_acc, &pp_iodata(&1, &2, depth, empty, opts)}
    155   end
    156 
    157   defp pp_iodata([byte | rest], output_acc, depth, empty, opts) when is_integer(byte) do
    158     pp_byte(byte, rest, output_acc, depth, empty, opts)
    159   end
    160 
    161   defp pp_iodata([head | tail], output_acc, depth, empty, opts) do
    162     {output_acc, cont} = pp_iodata(head, output_acc, depth, empty, opts)
    163     cont.(tail, output_acc)
    164   end
    165 
    166   defp pp_byte(byte, rest, output, depth, empty, opts) when byte in ' \n\r\t' do
    167     pp_iodata(rest, output, depth, empty, opts)
    168   end
    169 
    170   defp pp_byte(byte, rest, output, depth, empty, opts) when byte in '{[' do
    171     {out, depth} =
    172       cond do
    173         depth == :first -> {byte, 1}
    174         depth == 0 -> {[opts(opts, :record), byte], 1}
    175         empty -> {[opts(opts, :line), tab(opts(opts, :indent), depth), byte], depth + 1}
    176         true -> {byte, depth + 1}
    177       end
    178 
    179     empty = true
    180     pp_iodata(rest, [output, out], depth, empty, opts)
    181   end
    182 
    183   defp pp_byte(byte, rest, output, depth, true = _empty, opts) when byte in '}]' do
    184     empty = false
    185     depth = depth - 1
    186     pp_iodata(rest, [output, byte], depth, empty, opts)
    187   end
    188 
    189   defp pp_byte(byte, rest, output, depth, false = empty, opts) when byte in '}]' do
    190     depth = depth - 1
    191     out = [opts(opts, :line), tab(opts(opts, :indent), depth), byte]
    192     pp_iodata(rest, [output, out], depth, empty, opts)
    193   end
    194 
    195   defp pp_byte(byte, rest, output, depth, _empty, opts) when byte in ',' do
    196     empty = false
    197     out = [byte, opts(opts, :line), tab(opts(opts, :indent), depth)]
    198     pp_iodata(rest, [output, out], depth, empty, opts)
    199   end
    200 
    201   defp pp_byte(byte, rest, output, depth, empty, opts) when byte in ':' do
    202     out = [byte, opts(opts, :colon)]
    203     pp_iodata(rest, [output, out], depth, empty, opts)
    204   end
    205 
    206   defp pp_byte(byte, rest, output, depth, empty, opts) do
    207     out = if empty, do: [opts(opts, :line), tab(opts(opts, :indent), depth), byte], else: byte
    208     empty = false
    209 
    210     if byte == ?" do
    211       pp_string(rest, [output, out], _in_bs = false, &pp_iodata(&1, &2, depth, empty, opts))
    212     else
    213       pp_iodata(rest, [output, out], depth, empty, opts)
    214     end
    215   end
    216 
    217   defp pp_string(<<>>, output_acc, in_bs, cont) do
    218     {output_acc, &pp_string(&1, &2, in_bs, cont)}
    219   end
    220 
    221   defp pp_string(binary, output_acc, true = _in_bs, cont) when is_binary(binary) do
    222     <<byte, rest::binary>> = binary
    223     pp_string(rest, [output_acc, byte], false, cont)
    224   end
    225 
    226   defp pp_string(binary, output_acc, false = _in_bs, cont) when is_binary(binary) do
    227     case :binary.match(binary, ["\"", "\\"]) do
    228       :nomatch ->
    229         {[output_acc | binary], &pp_string(&1, &2, false, cont)}
    230       {pos, 1} ->
    231         {head, tail} = :erlang.split_binary(binary, pos + 1)
    232         case :binary.at(binary, pos) do
    233           ?\\ -> pp_string(tail, [output_acc | head], true, cont)
    234           ?" -> cont.(tail, [output_acc | head])
    235         end
    236     end
    237   end
    238 
    239   defp pp_string([], output_acc, in_bs, cont) do
    240     {output_acc, &pp_string(&1, &2, in_bs, cont)}
    241   end
    242 
    243   defp pp_string([byte | rest], output_acc, in_bs, cont) when is_integer(byte) do
    244     cond do
    245       in_bs -> pp_string(rest, [output_acc, byte], false, cont)
    246       byte == ?" -> cont.(rest, [output_acc, byte])
    247       true -> pp_string(rest, [output_acc, byte], byte == ?\\, cont)
    248     end
    249   end
    250 
    251   defp pp_string([head | tail], output_acc, in_bs, cont) do
    252     {output_acc, cont} = pp_string(head, output_acc, in_bs, cont)
    253     cont.(tail, output_acc)
    254   end
    255 end