cowboy_iolists.erl 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. %% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(cowboy_iolists).
  15. -export([split/2]).
  16. -ifdef(TEST).
  17. -include_lib("proper/include/proper.hrl").
  18. -endif.
  19. -spec split(non_neg_integer(), iodata()) -> {iodata(), iodata()}.
  20. split(N, Iolist) ->
  21. case split(N, Iolist, []) of
  22. {ok, Before, After} ->
  23. {Before, After};
  24. {more, _, Before} ->
  25. {lists:reverse(Before), <<>>}
  26. end.
  27. split(0, Rest, Acc) ->
  28. {ok, lists:reverse(Acc), Rest};
  29. split(N, [], Acc) ->
  30. {more, N, Acc};
  31. split(N, Binary, Acc) when byte_size(Binary) =< N ->
  32. {ok, lists:reverse([Binary|Acc]), <<>>};
  33. split(N, Binary, Acc) when is_binary(Binary) ->
  34. << Before:N/binary, After/bits >> = Binary,
  35. {ok, lists:reverse([Before|Acc]), After};
  36. split(N, [Binary|Tail], Acc) when byte_size(Binary) =< N ->
  37. split(N - byte_size(Binary), Tail, [Binary|Acc]);
  38. split(N, [Binary|Tail], Acc) when is_binary(Binary) ->
  39. << Before:N/binary, After/bits >> = Binary,
  40. {ok, lists:reverse([Before|Acc]), [After|Tail]};
  41. split(N, [Char|Tail], Acc) when is_integer(Char) ->
  42. split(N - 1, Tail, [Char|Acc]);
  43. split(N, [List|Tail], Acc0) ->
  44. case split(N, List, Acc0) of
  45. {ok, Before, After} ->
  46. IolistSize = iolist_size(Before),
  47. if
  48. IolistSize < N ->
  49. split(N - IolistSize, [After|Tail], lists:reverse(Before));
  50. true ->
  51. {ok, Before, [After|Tail]}
  52. end;
  53. {more, More, Acc} ->
  54. split(More, Tail, Acc)
  55. end.
  56. -ifdef(TEST).
  57. split_test_() ->
  58. Tests = [
  59. {10, "Hello world!", "Hello worl", "d!"},
  60. {10, <<"Hello world!">>, "Hello worl", "d!"},
  61. {10, ["He", [<<"llo">>], $\s, [["world"], <<"!">>]], "Hello worl", "d!"},
  62. {10, ["Hello "|<<"world!">>], "Hello worl", "d!"},
  63. {10, "Hello!", "Hello!", ""},
  64. {10, <<"Hello!">>, "Hello!", ""},
  65. {10, ["He", [<<"ll">>], $o, [["!"]]], "Hello!", ""},
  66. {10, ["Hel"|<<"lo!">>], "Hello!", ""},
  67. {10, [[<<>>|<<>>], [], <<"Hello world!">>], "Hello worl", "d!"},
  68. {10, [[<<"He">>|<<"llo">>], [$\s], <<"world!">>], "Hello worl", "d!"}
  69. ],
  70. [{iolist_to_binary(V), fun() ->
  71. {B, A} = split(N, V),
  72. true = iolist_to_binary(RB) =:= iolist_to_binary(B),
  73. true = iolist_to_binary(RA) =:= iolist_to_binary(A)
  74. end} || {N, V, RB, RA} <- Tests].
  75. prop_split_test() ->
  76. ?FORALL({N, Input},
  77. {non_neg_integer(), iolist()},
  78. begin
  79. Size = iolist_size(Input),
  80. {Before, After} = split(N, Input),
  81. if
  82. N >= Size ->
  83. ((iolist_size(After) =:= 0)
  84. andalso iolist_to_binary(Before) =:= iolist_to_binary(Input));
  85. true ->
  86. <<ExpectBefore:N/binary, ExpectAfter/bits>> = iolist_to_binary(Input),
  87. (ExpectBefore =:= iolist_to_binary(Before))
  88. andalso (ExpectAfter =:= iolist_to_binary(After))
  89. end
  90. end).
  91. -endif.