Current section

Files

Jump to
cloudi_core src cloudi_string.erl
Raw

src/cloudi_string.erl

%-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
% ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
%%%
%%%------------------------------------------------------------------------
%%% @doc
%%% ==String manipulation functions==
%%% @end
%%%
%%% BSD LICENSE
%%%
%%% Copyright (c) 2009-2015, Michael Truog <mjtruog at gmail dot com>
%%% All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions are met:
%%%
%%% * Redistributions of source code must retain the above copyright
%%% notice, this list of conditions and the following disclaimer.
%%% * Redistributions in binary form must reproduce the above copyright
%%% notice, this list of conditions and the following disclaimer in
%%% the documentation and/or other materials provided with the
%%% distribution.
%%% * All advertising materials mentioning features or use of this
%%% software must display the following acknowledgment:
%%% This product includes software developed by Michael Truog
%%% * The name of the author may not be used to endorse or promote
%%% products derived from this software without specific prior
%%% written permission
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%% CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
%%% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
%%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
%%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
%%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
%%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
%%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
%%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
%%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
%%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
%%% DAMAGE.
%%%
%%% @author Michael Truog <mjtruog [at] gmail (dot) com>
%%% @copyright 2009-2015 Michael Truog
%%% @version 1.5.1 {@date} {@time}
%%%------------------------------------------------------------------------
-module(cloudi_string).
-author('mjtruog [at] gmail (dot) com').
%% external interface
-export([afterl/2,
afterl/3,
afterr/2,
afterr/3,
beforel/2,
beforel/3,
beforer/2,
beforer/3,
splitl/2,
splitl/3,
splitr/2,
splitr/3,
binary_to_term/1,
list_to_term/1,
term_to_binary/1,
term_to_list/1,
format/2,
format_to_list/2,
format_to_binary/2,
compare_constant/2,
compare_constant_list/2,
compare_constant_binary/2]).
%%%------------------------------------------------------------------------
%%% External interface functions
%%%------------------------------------------------------------------------
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the string that occurs after a character, otherwise return an empty string, when traversing left to right.===
%% @end
%%-------------------------------------------------------------------------
-spec afterl(Char :: pos_integer(), string()) -> string().
afterl(_, []) ->
[];
afterl(Char, [Char | Rest]) when is_integer(Char) ->
Rest;
afterl(Char, [_ | Rest]) when is_integer(Char) ->
afterl(Char, Rest).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the string that occurs after a character, otherwise return based on the failure atom, when traversing left to right.===
%% @end
%%-------------------------------------------------------------------------
-spec afterl(Char :: pos_integer(),
Input :: string(), 'empty' | 'input') -> string().
afterl(Char, Input, empty) when is_integer(Char), is_list(Input) ->
afterl(Char, Input);
afterl(Char, Input, input) when is_integer(Char), is_list(Input) ->
afterl_input(Char, Input, Input).
afterl_input(_, [], Input) ->
Input;
afterl_input(Char, [Char | Rest], _) ->
Rest;
afterl_input(Char, [_ | Rest], Input) ->
afterl_input(Char, Rest, Input).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the string that occurs after a character, otherwise return an empty string, when traversing right to left.===
%% @end
%%-------------------------------------------------------------------------
-spec afterr(Char :: pos_integer(), string()) -> string().
afterr(Char, Input) when is_integer(Char), is_list(Input) ->
afterr_empty([], Char, Input).
afterr_empty(L, _, []) ->
L;
afterr_empty(_, Char, [Char | Rest]) ->
afterr_empty(Rest, Char, Rest);
afterr_empty(L, Char, [_ | Rest]) ->
afterr_empty(L, Char, Rest).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the string that occurs after a character, otherwise return based on the failure atom, when traversing right to left.===
%% @end
%%-------------------------------------------------------------------------
-spec afterr(Char :: pos_integer(), string(), 'empty' | 'input') -> string().
afterr(Char, Input, empty) when is_integer(Char), is_list(Input) ->
afterr_empty([], Char, Input);
afterr(Char, Input, input) when is_integer(Char), is_list(Input) ->
afterr_input([], Char, Input, Input).
afterr_input([], _, [], Input) ->
Input;
afterr_input(L, _, [], _) ->
L;
afterr_input(_, Char, [Char | Rest], Input) ->
afterr_input(Rest, Char, Rest, Input);
afterr_input(L, Char, [_ | Rest], Input) ->
afterr_input(L, Char, Rest, Input).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the string that occurs before a character, otherwise return an empty string, when traversing left to right.===
%% @end
%%-------------------------------------------------------------------------
-spec beforel(Char :: pos_integer(), string()) -> string().
beforel(Char, Input) when is_integer(Char), is_list(Input) ->
beforel_empty([], Char, Input).
beforel_empty(_, _, []) ->
[];
beforel_empty(Before, Char, [Char | _]) ->
lists:reverse(Before);
beforel_empty(Before, Char, [H | Input]) ->
beforel_empty([H | Before], Char, Input).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the string that occurs before a character, otherwise return based on the failure atom, when traversing left to right.===
%% @end
%%-------------------------------------------------------------------------
-spec beforel(Char :: pos_integer(), string(), 'empty' | 'input') -> string().
beforel(Char, Input, empty) when is_integer(Char), is_list(Input) ->
beforel_empty([], Char, Input);
beforel(Char, Input, input) when is_integer(Char), is_list(Input) ->
beforel_input([], Char, Input, Input).
beforel_input(_, _, [], Input) ->
Input;
beforel_input(Before, Char, [Char | _], _) ->
lists:reverse(Before);
beforel_input(Before, Char, [H | Rest], Input) ->
beforel_input([H | Before], Char, Rest, Input).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the string that occurs before a character, otherwise return an empty string, when traversing right to left.===
%% @end
%%-------------------------------------------------------------------------
-spec beforer(Char :: pos_integer(), string()) -> string().
beforer(Char, Input) when is_integer(Char), is_list(Input) ->
beforer_empty([], [], Char, Input).
beforer_empty(Before, _, _, []) ->
Before;
beforer_empty(Before, L, Char, [Char | Rest]) ->
beforer_empty(Before ++ lists:reverse(L), [Char], Char, Rest);
beforer_empty(Before, L, Char, [H | Rest]) ->
beforer_empty(Before, [H | L], Char, Rest).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the string that occurs before a character, otherwise return based on the failure atom, when traversing right to left.===
%% @end
%%-------------------------------------------------------------------------
-spec beforer(Char :: pos_integer(), string(), 'empty' | 'input') -> string().
beforer(Char, Input, empty) when is_integer(Char), is_list(Input) ->
beforer_empty([], [], Char, Input);
beforer(Char, Input, input) when is_integer(Char), is_list(Input) ->
beforer_input([], [], Char, Input, Input).
beforer_input([], _, _, [], Input) ->
Input;
beforer_input(Before, _, _, [], _) ->
Before;
beforer_input(Before, L, Char, [Char | Rest], Input) ->
beforer_input(Before ++ lists:reverse(L), [Char], Char, Rest, Input);
beforer_input(Before, L, Char, [H | Rest], Input) ->
beforer_input(Before, [H | L], Char, Rest, Input).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the two strings split at the first occurrence of the character, otherwise return an empty string, when traversing left to right.===
%% @end
%%-------------------------------------------------------------------------
-spec splitl(Char :: pos_integer(), string()) -> {string(), string()}.
splitl(Char, Input) when is_integer(Char), is_list(Input) ->
splitl_empty([], Char, Input).
splitl_empty(_, _, []) ->
{[], []};
splitl_empty(Before, Char, [Char | Rest]) ->
{lists:reverse(Before), Rest};
splitl_empty(Before, Char, [H | Rest]) ->
splitl_empty([H | Before], Char, Rest).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the two strings split at the first occurrence of the character, otherwise return based on the failure atom, when traversing left to right.===
%% @end
%%-------------------------------------------------------------------------
-spec splitl(Char :: pos_integer(),
string(), 'empty' | 'input') -> {string(), string()}.
splitl(Char, Input, empty) when is_integer(Char), is_list(Input) ->
splitl_empty([], Char, Input);
splitl(Char, Input, input) when is_integer(Char), is_list(Input) ->
splitl_input([], Char, Input, Input).
splitl_input(_, _, [], Input) ->
{Input, []};
splitl_input(Before, Char, [Char | Rest], _) ->
{lists:reverse(Before), Rest};
splitl_input(Before, Char, [H | Rest], Input) ->
splitl_input([H | Before], Char, Rest, Input).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the two strings split at the first occurrence of the character, otherwise return an empty string, when traversing right to left.===
%% @end
%%-------------------------------------------------------------------------
-spec splitr(Char :: pos_integer(), string()) -> {string(), string()}.
splitr(Char, Input) when is_integer(Char), is_list(Input) ->
splitr_empty([], [], Char, Input).
splitr_empty([Char | L1], [], Char, []) ->
{lists:reverse(L1), []};
splitr_empty(_, [], _, []) ->
{[], []};
splitr_empty(L1, L2, Char, []) ->
{splitr_prefix(L1, L2, Char), L2};
splitr_empty(L1, _, Char, [Char | Rest]) ->
splitr_empty([Char | L1], Rest, Char, Rest);
splitr_empty(L1, L2, Char, [C | Rest]) ->
splitr_empty([C | L1], L2, Char, Rest).
splitr_prefix([Char | L1], [], Char) ->
lists:reverse(L1);
splitr_prefix([_ | L1], [_ | L2], Char) ->
splitr_prefix(L1, L2, Char).
%%-------------------------------------------------------------------------
%% @doc
%% ===Return the two strings split at the first occurrence of the character, otherwise return based on the failure atom, when traversing right to left.===
%% @end
%%-------------------------------------------------------------------------
-spec splitr(Char :: pos_integer(), string(),
'empty' | 'input') -> {string(), string()}.
splitr(Char, Input, empty) when is_integer(Char), is_list(Input) ->
splitr_empty([], [], Char, Input);
splitr(Char, Input, input) when is_integer(Char), is_list(Input) ->
splitr_input([], [], Char, Input, Input).
splitr_input([Char | L1], [], Char, [], _) ->
{lists:reverse(L1), []};
splitr_input(_, [], _, [], Input) ->
{[], Input};
splitr_input(L1, L2, Char, [], _) ->
{splitr_prefix(L1, L2, Char), L2};
splitr_input(L1, _, Char, [Char | Rest], Input) ->
splitr_input([Char | L1], Rest, Char, Rest, Input);
splitr_input(L1, L2, Char, [C | Rest], Input) ->
splitr_input([C | L1], L2, Char, Rest, Input).
%%-------------------------------------------------------------------------
%% @doc
%% ===Convert a binary string to an Erlang term.===
%% @end
%%-------------------------------------------------------------------------
-spec binary_to_term(B :: binary()) -> any().
binary_to_term(B) when is_binary(B) ->
list_to_term(erlang:binary_to_list(B)).
%%-------------------------------------------------------------------------
%% @doc
%% ===Convert a string to an Erlang term.===
%% @end
%%-------------------------------------------------------------------------
-spec list_to_term(L :: string()) -> any().
list_to_term(L) when is_list(L) ->
{ok, S, _} = erl_scan:string(L ++ "."),
case erl_parse:parse_term(S) of
{ok, Term} ->
Term;
{error, Reason} ->
throw(Reason)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Convert an Erlang term to a binary string.===
%% @end
%%-------------------------------------------------------------------------
term_to_binary(T) ->
erlang:iolist_to_binary(io_lib:format("~p", [T])).
%%-------------------------------------------------------------------------
%% @doc
%% ===Convert an Erlang term to a string.===
%% @end
%%-------------------------------------------------------------------------
-spec term_to_list(T :: any()) -> string().
term_to_list(T) ->
format("~p", [T]).
%%-------------------------------------------------------------------------
%% @doc
%% ===Format a string based on the arguments.===
%% @end
%%-------------------------------------------------------------------------
-spec format(L :: string(), A :: list()) -> string().
format(L, A) when is_list(L), is_list(A) ->
format_to_list(L, A).
%%-------------------------------------------------------------------------
%% @doc
%% ===Format a string based on the arguments, stored as a list.===
%% @end
%%-------------------------------------------------------------------------
-spec format_to_list(L :: string(), A :: list()) -> string().
-compile({inline, [{format_to_list, 2}]}).
format_to_list(L, A) when is_list(L), is_list(A) ->
lists:flatten(io_lib:format(L, A)).
%%-------------------------------------------------------------------------
%% @doc
%% ===Format a string based on the arguments, stored as a binary.===
%% @end
%%-------------------------------------------------------------------------
-spec format_to_binary(L :: string(), A :: list()) -> binary().
format_to_binary(L, A) when is_list(L), is_list(A) ->
erlang:iolist_to_binary(io_lib:format(L, A)).
%%-------------------------------------------------------------------------
%% @doc
%% ===Time insensitive compare to avoid a timing leak.===
%% Use for password or other authentication comparisons.
%% Execution time is based on the length of Test.
%% @end
%%-------------------------------------------------------------------------
-spec compare_constant(Test :: string(),
Correct :: nonempty_string()) ->
boolean().
compare_constant(Test, [_ | _] = Correct) when is_list(Test) ->
compare_constant(Test, Correct, 0) =:= 0.
compare_constant([], [], Bits) ->
Bits;
compare_constant([], [_ | _], _) ->
1;
compare_constant([C | Test], [] = Correct, Bits) ->
compare_constant(Test, Correct, Bits bor (C bxor -1));
compare_constant([C1 | Test], [C2 | Correct], Bits) ->
compare_constant(Test, Correct, Bits bor (C1 bxor C2)).
%%-------------------------------------------------------------------------
%% @doc
%% ===Time insensitive compare to avoid a timing leak with strings as lists.===
%% Use for password or other authentication comparisons.
%% Execution time is based on the length of Test.
%% @end
%%-------------------------------------------------------------------------
-spec compare_constant_list(Test :: string(),
Correct :: nonempty_string()) ->
boolean().
compare_constant_list(Test, Correct) ->
compare_constant(Test, Correct).
%%-------------------------------------------------------------------------
%% @doc
%% ===Time insensitive compare to avoid a timing leak with strings as binaries.===
%% Use for password or other authentication comparisons.
%% Execution time is based on the length of Test.
%% @end
%%-------------------------------------------------------------------------
-spec compare_constant_binary(Test :: binary(),
Correct :: binary()) ->
boolean().
compare_constant_binary(Test, Correct)
when is_binary(Test), is_binary(Correct) ->
compare_constant(erlang:binary_to_list(Test),
erlang:binary_to_list(Correct)).
%%%------------------------------------------------------------------------
%%% Private functions
%%%------------------------------------------------------------------------
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
afterl_test() ->
"this-is-all-input" = afterl($/, "this-is-all-input", input),
"" = afterl($/, "this-is-all-input", empty),
"" = afterl($/, "this-is-all-input"),
"is-all-input" = afterl($-, "this-is-all-input", input),
"is-all-input" = afterl($-, "this-is-all-input", empty),
"is-all-input" = afterl($-, "this-is-all-input"),
ok.
afterr_test() ->
"this-is-all-input" = afterr($/, "this-is-all-input", input),
"" = afterr($/, "this-is-all-input", empty),
"" = afterr($/, "this-is-all-input"),
"input" = afterr($-, "this-is-all-input", input),
"input" = afterr($-, "this-is-all-input", empty),
"input" = afterr($-, "this-is-all-input"),
ok.
beforel_test() ->
"this-is-all-input" = beforel($/, "this-is-all-input", input),
"" = beforel($/, "this-is-all-input", empty),
"" = beforel($/, "this-is-all-input"),
"this" = beforel($-, "this-is-all-input", input),
"this" = beforel($-, "this-is-all-input", empty),
"this" = beforel($-, "this-is-all-input"),
ok.
beforer_test() ->
"this-is-all-input" = beforer($/, "this-is-all-input", input),
"" = beforer($/, "this-is-all-input", empty),
"" = beforer($/, "this-is-all-input"),
"this-is-all" = beforer($-, "this-is-all-input", input),
"this-is-all" = beforer($-, "this-is-all-input", empty),
"this-is-all" = beforer($-, "this-is-all-input"),
ok.
splitl_test() ->
{"this-is-all-input", ""} = splitl($/, "this-is-all-input", input),
{"", ""} = splitl($/, "this-is-all-input", empty),
{"", ""} = splitl($/, "this-is-all-input"),
{"this", "is-all-input"} = splitl($-, "this-is-all-input", input),
{"this", "is-all-input"} = splitl($-, "this-is-all-input", empty),
{"this", "is-all-input"} = splitl($-, "this-is-all-input"),
ok.
splitr_test() ->
{"", "this-is-all-input"} = splitr($/, "this-is-all-input", input),
{"", ""} = splitr($/, "this-is-all-input", empty),
{"", ""} = splitr($/, "this-is-all-input"),
{"this-is-all", "input"} = splitr($-, "this-is-all-input", input),
{"this-is-all", "input"} = splitr($-, "this-is-all-input", empty),
{"this-is-all", "input"} = splitr($-, "this-is-all-input"),
ok.
-endif.