cow_iolists.erl (3318B)
1 %% Copyright (c) 2017-2018, 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 15 -module(cow_iolists). 16 17 -export([split/2]). 18 19 -ifdef(TEST). 20 -include_lib("proper/include/proper.hrl"). 21 -endif. 22 23 -spec split(non_neg_integer(), iodata()) -> {iodata(), iodata()}. 24 split(N, Iolist) -> 25 case split(N, Iolist, []) of 26 {ok, Before, After} -> 27 {Before, After}; 28 {more, _, Before} -> 29 {lists:reverse(Before), <<>>} 30 end. 31 32 split(0, Rest, Acc) -> 33 {ok, lists:reverse(Acc), Rest}; 34 split(N, [], Acc) -> 35 {more, N, Acc}; 36 split(N, Binary, Acc) when byte_size(Binary) =< N -> 37 {more, N - byte_size(Binary), [Binary|Acc]}; 38 split(N, Binary, Acc) when is_binary(Binary) -> 39 << Before:N/binary, After/bits >> = Binary, 40 {ok, lists:reverse([Before|Acc]), After}; 41 split(N, [Binary|Tail], Acc) when byte_size(Binary) =< N -> 42 split(N - byte_size(Binary), Tail, [Binary|Acc]); 43 split(N, [Binary|Tail], Acc) when is_binary(Binary) -> 44 << Before:N/binary, After/bits >> = Binary, 45 {ok, lists:reverse([Before|Acc]), [After|Tail]}; 46 split(N, [Char|Tail], Acc) when is_integer(Char) -> 47 split(N - 1, Tail, [Char|Acc]); 48 split(N, [List|Tail], Acc0) -> 49 case split(N, List, Acc0) of 50 {ok, Before, After} -> 51 {ok, Before, [After|Tail]}; 52 {more, More, Acc} -> 53 split(More, Tail, Acc) 54 end. 55 56 -ifdef(TEST). 57 58 split_test_() -> 59 Tests = [ 60 {10, "Hello world!", "Hello worl", "d!"}, 61 {10, <<"Hello world!">>, "Hello worl", "d!"}, 62 {10, ["He", [<<"llo">>], $\s, [["world"], <<"!">>]], "Hello worl", "d!"}, 63 {10, ["Hello "|<<"world!">>], "Hello worl", "d!"}, 64 {10, "Hello!", "Hello!", ""}, 65 {10, <<"Hello!">>, "Hello!", ""}, 66 {10, ["He", [<<"ll">>], $o, [["!"]]], "Hello!", ""}, 67 {10, ["Hel"|<<"lo!">>], "Hello!", ""}, 68 {10, [[<<>>|<<>>], [], <<"Hello world!">>], "Hello worl", "d!"}, 69 {10, [[<<"He">>|<<"llo">>], [$\s], <<"world!">>], "Hello worl", "d!"}, 70 {10, [[[]|<<"He">>], [[]|<<"llo wor">>]|<<"ld!">>], "Hello worl", "d!"} 71 ], 72 [{iolist_to_binary(V), fun() -> 73 {B, A} = split(N, V), 74 true = iolist_to_binary(RB) =:= iolist_to_binary(B), 75 true = iolist_to_binary(RA) =:= iolist_to_binary(A) 76 end} || {N, V, RB, RA} <- Tests]. 77 78 prop_split_test() -> 79 ?FORALL({N, Input}, 80 {non_neg_integer(), iolist()}, 81 begin 82 Size = iolist_size(Input), 83 {Before, After} = split(N, Input), 84 if 85 N >= Size -> 86 ((iolist_size(After) =:= 0) 87 andalso iolist_to_binary(Before) =:= iolist_to_binary(Input)); 88 true -> 89 <<ExpectBefore:N/binary, ExpectAfter/bits>> = iolist_to_binary(Input), 90 (ExpectBefore =:= iolist_to_binary(Before)) 91 andalso (ExpectAfter =:= iolist_to_binary(After)) 92 end 93 end). 94 95 -endif.