%%%-------------------------------------------------------------------
%%% File: erlydtl_filters.erl
%%% @author Roberto Saccon [http://rsaccon.com]
%%% @author Evan Miller
%%% @copyright 2008 Roberto Saccon, Evan Miller
%%% @doc
%%% Template filters
%%% @end
%%%
%%% The MIT License
%%%
%%% Copyright (c) 2007 Roberto Saccon, Evan Miller
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%%% THE SOFTWARE.
%%%
%%% @since 2007-11-11 by Roberto Saccon, Evan Miller
%%%-------------------------------------------------------------------
-module(erlydtl_filters).
-author('rsaccon@gmail.com').
-author('emmiller@gmail.com').
-author('drew dot gulino at google dot com').
-ifdef(TEST).
-undef(TEST).
-endif.
-define(TEST,"").
%-define(NOTEST,1).
-define(NODEBUG,1).
-include_lib("eunit/include/eunit.hrl").
-ifdef(TEST).
-export([cast_to_float/1,cast_to_integer/1,stringformat_io/7,round/2,unjoin/2,addDefaultURI/1]).
-endif.
-export([add/2,
addslashes/1,
capfirst/1,
center/2,
cut/2,
date/1,
date/2,
default/2,
default_if_none/2,
dictsort/2,
dictsortreversed/2,
divisibleby/2,
%escape/, - implemented in erlydtl_compiler
escapejs/1,
filesizeformat/1,
first/1,
fix_ampersands/1,
floatformat/2,
force_escape/1,
format_integer/1,
format_number/1,
get_digit/2,
iriencode/1,
join/2,
last/1,
length/1,
length_is/2,
linebreaks/1,
linebreaksbr/1,
linenumbers/1,
ljust/2,
lower/1,
make_list/1,
phone2numeric/1,
pluralize/1,
pluralize/2,
pprint/1,
random/1,
random_num/1,
random_range/1,
removetags/2,
rjust/2,
%safe/, - implemented in erlydtl_compiler
%safeseq/, - implemented in erlydtl_compiler
slice/2,
slugify/1,
stringformat/2,
striptags/1,
time/1,
time/2,
timesince/1,
timesince/2,
timeuntil/1,
timeuntil/2,
title/1,
truncatechars/2,
truncatewords/2,
truncatewords_html/2,
unordered_list/1,
upper/1,
urlencode/1,
urlize/1,
urlize/2,
urlizetrunc/2,
wordcount/1,
wordwrap/2,
yesno/2]).
-define(NO_ENCODE(C), ((C >= $a andalso C =< $z) orelse
(C >= $A andalso C =< $Z) orelse
(C >= $0 andalso C =< $9) orelse
(C =:= $\. orelse C =:= $-
orelse C =:= $~ orelse C =:= $_))).
-define(NO_IRI_ENCODE(C), (?NO_ENCODE(C) orelse (
C =:= $/ orelse
C =:= $# orelse
C =:= $[ orelse
C =:= $] orelse
C =:= $= orelse
C =:= $: orelse
C =:= $; orelse
C =:= $$ orelse
C =:= $& orelse
C =:= $( orelse
C =:= $) orelse
C =:= $+ orelse
C =:= $, orelse
C =:= $! orelse
C =:= $? orelse
C =:= $* orelse
C =:= $@ orelse
C =:= $' orelse
C =:= $~))).
-define(KILOBYTE, 1024).
-define(MEGABYTE, (1024 * ?KILOBYTE)).
-define(GIGABYTE, (1024 * ?MEGABYTE)).
-define(SECONDS_PER_MINUTE, 60).
-define(SECONDS_PER_HOUR, (60 * ?SECONDS_PER_MINUTE)).
-define(SECONDS_PER_DAY, (24 * ?SECONDS_PER_HOUR)).
-define(SECONDS_PER_WEEK, (7 * ?SECONDS_PER_DAY)).
-define(SECONDS_PER_MONTH, (30 * ?SECONDS_PER_DAY)).
-define(SECONDS_PER_YEAR, (365 * ?SECONDS_PER_DAY)).
%% @doc Adds to values
add(LHS, RHS) when is_number(LHS), is_number(RHS) ->
LHS + RHS;
add(LHS, RHS) when is_binary(LHS) ->
add(binary_to_list(LHS), RHS);
add(LHS, RHS) when is_binary(RHS) ->
add(LHS, binary_to_list(RHS));
add(LHS, RHS) when is_list(LHS), is_list(RHS) ->
case {to_numeric(LHS), to_numeric(RHS)} of
{{number, LHSNum}, {number, RHSNum}} ->
LHSNum + RHSNum;
_ ->
LHS ++ RHS
end;
add(LHS, RHS) when is_list(LHS), is_number(RHS) ->
case to_numeric(LHS) of
{number, LHSNum} ->
LHSNum + RHS;
_ ->
LHS ++ to_string(RHS)
end;
add(LHS, RHS) when is_number(LHS), is_list(RHS) ->
case to_numeric(RHS) of
{number, RHSNum} ->
LHS + RHSNum;
_ ->
to_string(LHS) ++ RHS
end.
to_string(Num) when is_integer(Num) ->
integer_to_list(Num);
to_string(Num) when is_float(Num) ->
float_to_list(Num).
to_numeric(List) ->
try
{number, list_to_integer(List)}
catch
error:badarg ->
try
{number, list_to_float(List)}
catch
error:badarg ->
undefined
end
end.
%% @doc Adds slashes before quotes.
addslashes(Input) when is_binary(Input) ->
addslashes(binary_to_list(Input));
addslashes(Input) when is_list(Input) ->
addslashes(Input, []).
%% @doc Capitalizes the first character of the value.
capfirst([H|T]) when H >= $a andalso H =< $z ->
[H + $A - $a | T];
capfirst(Other) when is_list(Other) ->
Other;
capfirst(<>) when Byte >= $a andalso Byte =< $z ->
[(Byte + $A - $a)|binary_to_list(Binary)];
capfirst(Other) when is_binary(Other) ->
Other.
%% @doc Centers the value in a field of a given width.
center(Input, Number) when is_binary(Input) ->
list_to_binary(center(binary_to_list(Input), Number));
center(Input, Number) when is_list(Input) ->
string:centre(Input, Number).
%% @doc Removes all values of arg from the given string.
cut(Input, Arg) when is_binary(Arg) ->
cut(Input, binary_to_list(Arg));
cut(Input, Arg) when is_binary(Input) ->
cut(binary_to_list(Input), Arg);
cut(Input, [Char]) when is_list(Input) ->
cut(Input, Char, []).
%% @doc Formats a date according to the default format.
date(Input) ->
date(Input, "F j, Y").
%% @doc Formats a date according to the given format.
date(Input, FormatStr) when is_binary(Input) ->
list_to_binary(date(binary_to_list(Input), FormatStr));
date({{_,_,_} = Date,{_,_,_} = Time}, FormatStr) ->
erlydtl_dateformat:format({Date, Time}, FormatStr);
date({_,_,_} = Date, FormatStr) ->
erlydtl_dateformat:format(Date, FormatStr);
date(Input, _FormatStr) when is_list(Input) ->
io:format("Unexpected date parameter : ~p~n", [Input]),
"".
%% @doc If value evaluates to `false', use given default. Otherwise, use the value.
default(Input, Default) ->
case erlydtl_runtime:is_false(Input) of
true -> Default;
false -> Input
end.
%% @doc If (and only if) value is `undefined', use given default. Otherwise, use the value.
default_if_none(undefined, Default) ->
Default;
default_if_none(Input, _) ->
Input.
%% @doc Takes a list of dictionaries or proplists and returns that list sorted by the key given in the argument.
dictsort(DictList, Key) when is_binary(Key) ->
dictsort(DictList, [binary_to_atom(B,latin1) ||
B <- binary:split(Key,<<".">>)]);
dictsort(DictList, Key) ->
case lists:all(
fun(Dict) ->
erlydtl_runtime:find_deep_value(Key, Dict) /= undefined
end, DictList) of
true ->
lists:sort(
fun(K1,K2) ->
erlydtl_runtime:find_deep_value(Key,K1) =<
erlydtl_runtime:find_deep_value(Key,K2)
end, DictList);
false -> error
end.
%% @doc Same as dictsort, but the list is reversed.
dictsortreversed(DictList, Key) ->
lists:reverse(dictsort(DictList, Key)).
%% @doc Returns `true' if the value is divisible by the argument.
divisibleby(Input, Divisor) when is_binary(Input) ->
divisibleby(binary_to_list(Input), Divisor);
divisibleby(Input, Divisor) when is_list(Input) ->
divisibleby(list_to_integer(Input), Divisor);
divisibleby(Input, Divisor) when is_binary(Divisor) ->
divisibleby(Input, binary_to_list(Divisor));
divisibleby(Input, Divisor) when is_list(Divisor) ->
divisibleby(Input, list_to_integer(Divisor));
divisibleby(Input, Divisor) when is_integer(Input), is_integer(Divisor) ->
Input rem Divisor =:= 0.
%% @doc Escapes characters for use in JavaScript strings.
escapejs(Input) when is_binary(Input) ->
escapejs(binary_to_list(Input));
escapejs(Input) when is_list(Input) ->
escapejs(Input, []).
%% @doc Format the value like a human-readable file size.
filesizeformat(Input) when is_binary(Input) ->
filesizeformat(binary_to_list(Input));
filesizeformat(Input) when is_list(Input) ->
filesizeformat(list_to_integer(Input));
filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?GIGABYTE->
filesizeformat(Bytes / ?GIGABYTE, "GB");
filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?MEGABYTE ->
filesizeformat(Bytes / ?MEGABYTE, "MB");
filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?KILOBYTE ->
filesizeformat(Bytes / ?KILOBYTE, "KB");
filesizeformat(Bytes) when is_integer(Bytes) ->
integer_to_list(Bytes) ++ " bytes".
%% @doc Returns the first item in a list.
first([First|_Rest]) ->
[First];
first(<>) ->
<>.
%% @doc Replaces ampersands with & entities.
fix_ampersands(Input) when is_binary(Input) ->
fix_ampersands(Input, 0);
fix_ampersands(Input) when is_list(Input) ->
fix_ampersands(Input, []).
%% @doc When used without an argument, rounds a floating-point number to one decimal place
%% -- but only if there's a decimal part to be displayed
floatformat(Number, Place) when is_binary(Number) ->
floatformat(binary_to_list(Number), Place);
floatformat(Number, Place) ->
floatformat_io(Number, cast_to_integer(Place)).
floatformat_io(Number, []) ->
floatformat_io(Number, -1);
floatformat_io(Number, Precision) when Precision > 0 ->
Format = lists:flatten(io_lib:format("~~.~Bf",[Precision])),
[Result] = io_lib:format(Format,[Number]),
Result;
floatformat_io(Number, Precision) when Precision < 0 ->
Round = erlang:round(Number),
RoundPrecision = round(Number, -Precision),
case RoundPrecision == Round of
true ->
%Format = lists:flatten(io_lib:format("~~~BB",[-Precision])),
[Result] = io_lib:format("~B",[Round]);
false ->
Format = lists:flatten(io_lib:format("~~.~Bf",[-Precision])),
[Result] = io_lib:format(Format,[RoundPrecision])
end,
Result.
round(Number, Precision) ->
P = math:pow(10, Precision),
round(Number * P) / P.
%% @doc Applies HTML escaping to a string.
force_escape(Input) when is_list(Input) ->
escape(Input, []);
force_escape(Input) when is_binary(Input) ->
escape(Input, 0);
force_escape(Input) ->
Input.
format_integer(Input) when is_integer(Input) ->
integer_to_list(Input);
format_integer(Input) ->
Input.
format_number(Input) when is_integer(Input) ->
integer_to_list(Input);
format_number(Input) when is_float(Input) ->
io_lib:format("~p", [Input]);
format_number(Input) when is_function(Input, 0) ->
format_number(Input());
format_number(Input) ->
Input.
%% @doc Given a whole number, returns the requested digit, where 1 is the right-most digit.
get_digit(Input, Digit) when is_binary(Input) ->
get_digit(binary_to_list(Input), Digit);
get_digit(Input, Digit) when is_integer(Input) ->
get_digit(integer_to_list(Input), Digit);
get_digit(Input, Digit) when is_binary(Digit) ->
get_digit(Input, binary_to_list(Digit));
get_digit(Input, Digit) when is_list(Digit) ->
get_digit(Input, list_to_integer(Digit));
get_digit(Input, Digit) when Digit > erlang:length(Input) ->
0;
get_digit(Input, Digit) when Digit > 0 ->
lists:nth(Digit, lists:reverse(Input)) - $0;
get_digit(Input, _) ->
Input.
iriencode(Input) ->
iriencode(unicode:characters_to_list(Input), []).
%% @doc Joins a list with a given separator.
join(Input, Separator) when is_list(Input) ->
join_io(Input, Separator).
%% @doc Returns the last item in a list.
last(Input) when is_binary(Input) ->
case size(Input) of
0 -> Input;
N ->
Offset = N - 1,
<<_:Offset/binary, Byte/binary>> = Input,
Byte
end;
last(Input) when is_list(Input) ->
[lists:last(Input)].
%% @doc Returns the length of the value.
length(Input) when is_list(Input) ->
integer_to_list(erlang:length(Input));
length(Input) when is_binary(Input) ->
integer_to_list(size(Input)).
%% @doc Returns True iff the value's length is the argument.
length_is(Input, Number) when is_list(Input), is_integer(Number) ->
length_is(Input, integer_to_list(Number));
length_is(Input, Number) when is_list(Input), is_list(Number) ->
?MODULE:length(Input) =:= Number.
%% @doc Replaces line breaks in plain text with appropriate HTML
linebreaks(Input) when is_binary(Input) ->
linebreaks(binary_to_list(Input),[]);
linebreaks(Input) ->
linebreaks(Input,[]).
linebreaks([],Acc) ->
"" ++ lists:reverse(Acc) ++ "
";
linebreaks([$\n|T], ">p<"++_ = Acc) ->
linebreaks(T, Acc);
linebreaks([$\r|T], ">p<"++_ = Acc) ->
linebreaks(T, Acc);
linebreaks([$\n, $\n|T],Acc) ->
linebreaks(T, lists:reverse("
", Acc));
linebreaks([$\r, $\n, $\r, $\n|T],Acc) ->
linebreaks(T, lists:reverse("
", Acc));
linebreaks([$\r, $\n|T], Acc) ->
linebreaks(T, lists:reverse("
", Acc));
linebreaks([$\n|T], Acc) ->
linebreaks(T, lists:reverse("
", Acc));
linebreaks([C|T], Acc) ->
linebreaks(T, [C|Acc]).
%% @doc Converts all newlines to HTML line breaks.
linebreaksbr(Input) when is_binary(Input) ->
linebreaksbr(Input, 0);
linebreaksbr(Input) ->
linebreaksbr(Input, []).
%% @doc Displays text with line numbers.
linenumbers(Input) when is_binary(Input) ->
linenumbers(binary_to_list(Input));
linenumbers(Input) when is_list(Input) ->
linenumbers_io(Input, [], 1).
linenumbers_io([], Acc, _) ->
lists:reverse(Acc);
linenumbers_io(Input, [], LineNumber) ->
linenumbers_io(Input, lists:reverse(integer_to_list(LineNumber)++". "), LineNumber + 1);
linenumbers_io("\n"++Rest, Acc, LineNumber) ->
linenumbers_io(Rest, lists:reverse("\n" ++ integer_to_list(LineNumber) ++ ". ", Acc), LineNumber + 1);
linenumbers_io([H|T], Acc, LineNumber) ->
linenumbers_io(T, [H|Acc], LineNumber).
%% @doc Left-aligns the value in a field of a given width.
ljust(Input, Number) when is_binary(Input) ->
list_to_binary(ljust(binary_to_list(Input), Number));
ljust(Input, Number) when is_list(Input) ->
string:left(Input, Number).
%% @doc Converts a string into all lowercase.
lower(Input) when is_binary(Input) ->
lower(Input, 0);
lower(Input) ->
string:to_lower(Input).
%% @doc Returns the value turned into a list. For an integer, it's a list of digits.
%% For a string, it's a list of characters.
%% Added this for DTL compatibility, but since strings are lists in Erlang, no need for this.
make_list(Input) when is_binary(Input) ->
make_list(binary_to_list(Input));
make_list(Input) ->
unjoin(Input,"").
%% @doc Converts a phone number (possibly containing letters) to its numerical equivalent.
phone2numeric(Input) when is_binary(Input) ->
phone2numeric(binary_to_list(Input));
phone2numeric(Input) when is_list(Input) ->
phone2numeric(Input, []).
%% @doc Returns a plural suffix if the value is not 1. By default, this suffix is 's'.
pluralize(Number, Suffix) when is_binary(Suffix) ->
pluralize_io(Number, binary_to_list(Suffix) );
pluralize(Number, Suffix) when is_list(Suffix) ->
pluralize_io(Number, Suffix).
pluralize(Number) ->
pluralize(Number, "s").
pluralize_io(Number, Suffix) ->
case lists:member($, , Suffix) of
true ->
[Singular, Plural] = string:tokens(Suffix,","),
case Number of
0 -> Plural;
1 -> Singular;
_ -> Plural
end;
false ->
case Number of
0 -> Suffix;
1 -> [];
_ -> Suffix
end
end.
%% @doc "pretty print" arbitrary data structures. Used for debugging.
pprint(Input) ->
io_lib:format("~p",[Input]).
%% @doc Returns a random item from the given list.
random(Input) when is_list(Input) ->
lists:nth(random:uniform(erlang:length(Input)), Input);
random(_) ->
"".
random_num(Value) ->
{A1,A2,A3} = now(),
random:seed(A1, A2, A3),
Rand = random:uniform(Value),
Rand.
%% random tags to be used when using erlydtl in testing
random_range(Range) ->
[Start, End] = string:tokens(Range,","),
%?debugFmt("Start, End: ~p,~p~n",[Start,End]),
random_range(cast_to_integer(Start),cast_to_integer(End)).
random_range(Start, End) when End >= Start ->
%?debugFmt("Input, Start, End: ~p,~p,~p~n",[Input,Start,End]),
Range = End - Start,
Rand = random:uniform(Range),
Num = Rand + Start,
lists:flatten(io_lib:format("~B",[Num])).
removetags(Input, Tags) when is_binary(Input) ->
removetags(binary_to_list(Input), Tags);
removetags(Input, Tags) when is_binary(Tags) ->
removetags(Input, binary_to_list(Tags));
removetags(Input, Tags) ->
TagList = string:tokens(Tags," "),
TagListString = string:join(TagList,"|"),
Regex = lists:flatten(io_lib:format("?(~s)( |\n)?>",[TagListString])),
Result = re:replace(Input,Regex,"", [global,{return,list}]),
Result.
%% @doc Right-aligns the value in a field of a given width.
rjust(Input, Number) when is_binary(Input) ->
list_to_binary(rjust(binary_to_list(Input), Number));
rjust(Input, Number) ->
string:right(Input, Number).
%% @doc Returns a slice of the list.
slice(Input, Index) when is_binary(Input) ->
erlydtl_slice:slice(binary_to_list(Input), Index);
slice(Input, Index) when is_list(Input) ->
erlydtl_slice:slice(Input, Index).
%% regex " ^([#0-\s+].)([0-9\*]+)(\.[0-9]+)([diouxXeEfFgGcrs]) " matches ALL of "-10.0f"
%% ([#0-\s+]?)([0-9\*]+)?(\.?)([0-9]?)([diouxXeEfFgGcrs])
%% @doc Returns a formatted string
stringformat(Input, Conversion) when is_binary(Input) ->
stringformat(binary_to_list(Input), Conversion);
stringformat(Input, Conversion) when is_binary(Conversion) ->
stringformat(Input, binary_to_list(Conversion));
stringformat(Input, Conversion) ->
ParsedConversion = re:replace(Conversion, "([\-#\+ ]?)([0-9\*]+)?(\.?)([0-9]?)([diouxXeEfFgGcrs])", "\\1 ,\\2 ,\\3 ,\\4 ,\\5 ", [{return,list}]),
?debugFmt("ParsedConversion: ~p~n", [ParsedConversion]),
ParsedConversion1 = lists:map(fun(X) -> string:strip(X) end, string:tokens(ParsedConversion, ",")),
[ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType] = ParsedConversion1,
?debugFmt("ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType: ~p, ~p, ~p, ~p, ~p ~n", [ConversionFlag, cast_to_integer(MinFieldWidth), Precision, cast_to_integer(PrecisionLength), ConversionType]),
[String] = stringformat_io(Input, Conversion, ConversionFlag, cast_to_integer(MinFieldWidth), Precision, cast_to_integer(PrecisionLength), ConversionType),
lists:flatten(String).
%% @doc
%% A conversion specifier contains two or more characters and has the following components, which must occur in this order:
%%
%% 1. The "%" character, which marks the start of the specifier.
%% 2. Mapping key (optional), consisting of a parenthesised sequence of characters (for example, (somename)).
%% 3. Conversion flags (optional), which affect the result of some conversion types.
%% 4. Minimum field width (optional). If specified as an "*" (asterisk), the actual width is read from the next element of the tuple in values, and the object to convert comes after the minimum field width and optional precision.
%% 5. Precision (optional), given as a "." (dot) followed by the precision. If specified as "*" (an asterisk), the actual width is read from the next element of the tuple in values, and the value to convert comes after the precision.
%% 6. Length modifier (optional).
%% 7. Conversion type.
stringformat_io(Input, _Conversion, _ConversionFlag, [],
_Precision, _PrecisionLength, "s") when is_list(Input) ->
Format = lists:flatten(io_lib:format("~~s", [])),
io_lib:format(Format, [Input]);
stringformat_io(Input, _Conversion, ConversionFlag, MinFieldWidth,
_Precision, _PrecisionLength, "s") when is_list(Input) ->
%Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType],
InputLength = erlang:length(Input),
case erlang:abs(MinFieldWidth) < InputLength of
true ->
MFW = InputLength;
false ->
MFW = MinFieldWidth
end,
Format = lists:flatten(io_lib:format("~~~s~ps", [ConversionFlag,MFW])),
io_lib:format(Format, [Input]);
stringformat_io(Input, _Conversion, _ConversionFlag, MinFieldWidth,
Precision, PrecisionLength, "f") when Precision == ".", MinFieldWidth == 0 ->
Conversion1 = lists:concat(["","",Precision,PrecisionLength,"f"]),
stringformat_io(Input, Conversion1, [], [], Precision, PrecisionLength, "f");
stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth,
Precision, "", "f") when Precision == "." ->
Format = re:replace(Conversion, "f", "d", [{return, list}] ),
stringformat_io(Input, Format, ConversionFlag, MinFieldWidth,
Precision, "", "d");
stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
_Precision, _PrecisionLength, "f")->
%Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType],
Format = "~" ++ Conversion,
io_lib:format(Format, [cast_to_float(Input)]);
stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
[], [], "d")->
%?debugMsg("plain d"),
%Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType],
Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ),
io_lib:format(Format, [cast_to_integer(Input)]);
stringformat_io(Input, _Conversion, "-", MinFieldWidth,
_Precision, PrecisionLength, "d") when MinFieldWidth > 0 ->
%Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ),
DecimalFormat = "~" ++ integer_to_list(PrecisionLength) ++ "..0B",
Decimal = lists:flatten( io_lib:format(DecimalFormat, [cast_to_integer(Input)]) ),
SpaceFormat = "~" ++ integer_to_list(MinFieldWidth - erlang:length(Decimal)) ++ "s",
Spaces = io_lib:format(SpaceFormat,[""]),
?debugFmt("Spaces: |~s|", [Spaces]),
?debugFmt("Decimal: ~s", [Decimal]),
[lists:flatten(Decimal ++ Spaces)];
stringformat_io(Input, _Conversion, _ConversionFlag, MinFieldWidth,
_Precision, PrecisionLength, "d") when MinFieldWidth > 0 ->
%Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ),
DecimalFormat = "~" ++ integer_to_list(PrecisionLength) ++ "..0B",
Decimal = lists:flatten( io_lib:format(DecimalFormat, [cast_to_integer(Input)]) ),
SpaceFormat = "~" ++ integer_to_list(MinFieldWidth - erlang:length(Decimal)) ++ "s",
Spaces = io_lib:format(SpaceFormat,[""]),
?debugFmt("Spaces: |~s|", [Spaces]),
?debugFmt("Decimal: ~s", [Decimal]),
[lists:flatten(Spaces ++ Decimal)];
stringformat_io(Input, _Conversion, _ConversionFlag, _MinFieldWidth,
_Precision, PrecisionLength, "d") ->
%Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType],
%Format = "~" ++ PrecisionLength ++ "..0" ++ re:replace(Conversion, "d", "B", [{return, list}] ),
%?debugFmt("precision d, Conversion: ~p~n", [Conversion]),
Format = lists:flatten("~" ++ io_lib:format("~B..0B",[PrecisionLength])),
?debugFmt("Format: ~p~n",[Format]),
io_lib:format(Format, [cast_to_integer(Input)]);
stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
_Precision, _PrecisionLength, "i")->
Format = "~" ++ re:replace(Conversion, "i", "B", [{return, list}] ),
io_lib:format(Format, [cast_to_integer(Input)]);
stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
_Precision, _PrecisionLength, "X")->
Format = "~" ++ re:replace(Conversion, "X", ".16B", [{return, list}] ),
io_lib:format(Format, [cast_to_integer(Input)]);
stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
_Precision, _PrecisionLength, "x")->
Format = "~" ++ re:replace(Conversion, "x", ".16b", [{return, list}] ),
io_lib:format(Format, [cast_to_integer(Input)]);
stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
_Precision, _PrecisionLength, "o")->
Format = "~" ++ re:replace(Conversion, "o", ".8b", [{return, list}] ),
io_lib:format(Format, [cast_to_integer(Input)]);
stringformat_io(Input, _Conversion, _ConversionFlag, _MinFieldWidth,
Precision, PrecisionLength, "e") when is_integer(PrecisionLength), PrecisionLength >= 2->
?debugFmt("PrecisionLength ~p~n", [PrecisionLength]),
Conversion1 = lists:concat(["","",Precision,PrecisionLength + 1,"e"]),
Format = "~" ++ Conversion1,
io_lib:format(Format, [cast_to_float(Input)]);
stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth,
"", [], "e")->
Format = "~" ++ re:replace(Conversion, "e", ".6e", [{return, list}] ),
Raw = lists:flatten(stringformat_io(Input, Format, ConversionFlag, MinFieldWidth,
".", 6, "e")
),
%io:format("Raw: ~p~n", [Raw]),
Elocate = string:rstr(Raw,"e+"),
%io:format("Elocate: ~p~n", [Elocate]),
String = [string:substr(Raw,1,Elocate-1) ++ "e+"
++ io_lib:format("~2..0B",[list_to_integer(string:substr(Raw,Elocate+2))])], %works till +99, then outputs "**"
%io:format("String: ~p~n", [String]),
String;
stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth,
Precision, PrecisionLength, "E")->
Format = re:replace(Conversion, "E", "e", [{return, list}] ),
[Str] = stringformat_io(Input, Format, ConversionFlag, MinFieldWidth,
Precision, PrecisionLength, "e"),
[string:to_upper(Str)].
%% @doc Strips all [X]HTML tags.
striptags(Input) when is_binary(Input) ->
striptags(binary_to_list(Input));
striptags(Input) ->
Regex = "(<[^>]+>)",
Result = re:replace(Input,Regex,"", [global,{return,list}]),
Result.
cast_to_float([]) ->
[];
cast_to_float(Input) when is_float(Input) ->
Input;
cast_to_float(Input) when is_integer(Input) ->
Input + 0.0;
cast_to_float(Input) ->
try list_to_float(Input)
catch
error:_Reason ->
list_to_integer(Input) + 0.0
end.
cast_to_integer([]) ->
[];
cast_to_integer(Input) when is_integer(Input) ->
Input;
cast_to_integer(Input) when is_float(Input) ->
erlang:round(Input);
cast_to_integer(Input) when is_binary(Input) ->
cast_to_integer(binary_to_list(Input));
cast_to_integer(Input) when is_list(Input)->
case lists:member($., Input) of
true ->
erlang:round(erlang:list_to_float(Input));
false ->
erlang:list_to_integer(Input)
end.
%% @doc Converts to lowercase, removes non-word characters (alphanumerics and underscores) and converts spaces to hyphens.
slugify(Input) when is_binary(Input) ->
slugify(binary_to_list(Input));
slugify(Input) when is_list(Input) ->
slugify(Input, []).
%% @doc Formats a time according to the given format.
time(Input) ->
date(Input, "f a").
time(Input, FormatStr) ->
date(Input, FormatStr).
timesince(Date) ->
timesince(Date, calendar:local_time()).
%%algorithm taken from django code
timesince(Date,Comparison) ->
Since = calendar:datetime_to_gregorian_seconds(Comparison) - calendar:datetime_to_gregorian_seconds(Date),
timesince0(Since, [], 0).
timesince0(_, Acc, 2) ->
string:join(lists:reverse(Acc), ", ");
timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_YEAR ->
Years = Seconds div ?SECONDS_PER_YEAR,
timesince0(Seconds rem ?SECONDS_PER_YEAR, [io_lib:format("~B ~s~s", [Years, "year", pluralize(Years)])|Acc], Terms+1);
timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_MONTH ->
Months = Seconds div ?SECONDS_PER_MONTH,
timesince0(Seconds rem ?SECONDS_PER_MONTH, [io_lib:format("~B ~s~s", [Months, "month", pluralize(Months)])|Acc], Terms+1);
timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_WEEK ->
Weeks = Seconds div ?SECONDS_PER_WEEK,
timesince0(Seconds rem ?SECONDS_PER_WEEK, [io_lib:format("~B ~s~s", [Weeks, "week", pluralize(Weeks)])|Acc], Terms+1);
timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_DAY ->
Days = Seconds div ?SECONDS_PER_DAY,
timesince0(Seconds rem ?SECONDS_PER_DAY, [io_lib:format("~B ~s~s", [Days, "day", pluralize(Days)])|Acc], Terms+1);
timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_HOUR ->
Hours = Seconds div ?SECONDS_PER_HOUR,
timesince0(Seconds rem ?SECONDS_PER_HOUR, [io_lib:format("~B ~s~s", [Hours, "hour", pluralize(Hours)])|Acc], Terms+1);
timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_MINUTE ->
Minutes = Seconds div ?SECONDS_PER_MINUTE,
timesince0(Seconds rem ?SECONDS_PER_MINUTE,[io_lib:format("~B ~s~s", [Minutes, "minute", pluralize(Minutes)])|Acc], Terms+1);
timesince0(Seconds, Acc, Terms) when Seconds >= 1 ->
timesince0(0, [io_lib:format("~B ~s~s", [Seconds, "second", pluralize(Seconds)])|Acc], Terms+1);
timesince0(Seconds, [], 0) when Seconds =< 0 ->
timesince0(0, ["0 minutes"], 1);
timesince0(0, Acc, Terms) ->
timesince0(0, Acc, Terms+1).
timeuntil(Date) ->
timesince(calendar:local_time(),Date).
timeuntil(Date,Comparison) ->
timesince(Comparison,Date).
%% @doc Converts a string into titlecase.
title(Input) when is_binary(Input) ->
title(binary_to_list(Input));
title(Input) when is_list(Input) ->
title(lower(Input), []).
%% @doc Truncates a string after a certain number of characters.
truncatechars(_Input, Max) when Max =< 0 ->
"";
truncatechars(Input, Max) when is_binary(Input) ->
list_to_binary(truncatechars(binary_to_list(Input), Max));
truncatechars(Input, Max) ->
truncatechars(Input, Max, []).
%% @doc Truncates a string after a certain number of words.
truncatewords(_Input, Max) when Max =< 0 ->
"";
truncatewords(Input, Max) when is_binary(Input) ->
list_to_binary(truncatewords(binary_to_list(Input), Max));
truncatewords(Input, Max) ->
truncatewords(Input, Max, []).
%% @doc Similar to truncatewords, except that it is aware of HTML tags.
truncatewords_html(_Input, Max) when Max =< 0 ->
"";
truncatewords_html(Input, Max) when is_binary(Input) ->
truncatewords_html(binary_to_list(Input), Max);
truncatewords_html(Input, Max) ->
truncatewords_html(Input, Max, [], [], text).
%% @doc Recursively takes a self-nested list and returns an HTML unordered list -- WITHOUT opening and closing `
' tags.
unordered_list(List) ->
String = lists:flatten(unordered_list(List, [])),
string:substr(String, 5, erlang:length(String) - 9).
unordered_list([], Acc) ->
[""];
unordered_list([First|_] = List, []) when is_integer(First) ->
"- "++List;
unordered_list([First|Rest], Acc) when is_list(First), Rest == [] ->
unordered_list(Rest, ["
"] ++ [unordered_list(First, []) | Acc ]) ;
unordered_list([First|Rest], Acc) when is_list(First), is_integer(hd(hd(Rest))) ->
unordered_list(Rest, [unordered_list(First, []) ++ "" |Acc]);
unordered_list([First|Rest], Acc) when is_list(First) ->
unordered_list(Rest, [unordered_list(First, [])|Acc]).
%% @doc Converts a string into all uppercase.
upper(Input) when is_binary(Input) ->
list_to_binary(upper(binary_to_list(Input)));
upper(Input) ->
string:to_upper(Input).
%% @doc Escapes a value for use in a URL.
urlencode(Input) when is_binary(Input) ->
urlencode(Input, 0);
urlencode(Input) when is_list(Input) ->
urlencode(Input, []).
%% @doc Returns the number of words.
wordcount(Input) when is_binary(Input) ->
wordcount(binary_to_list(Input));
wordcount(Input) when is_list(Input) ->
wordcount(Input, 0).
%% @doc Wraps words at specified line length, uses `
' html tag to delimit lines
wordwrap(Input, Number) when is_binary(Input) ->
wordwrap(binary_to_list(Input), Number);
wordwrap(Input, Number) when is_list(Input) ->
wordwrap(Input, [], [], 0, Number).
%% @doc Given a string mapping values for true, false and (optionally) undefined, returns one of those strings according to the value.
yesno(Bool, Choices) when is_binary(Bool) ->
yesno_io(binary_to_list(Bool), Choices);
yesno(Bool, Choices) when is_binary(Choices) ->
yesno_io(Bool, binary_to_list(Choices));
yesno(Bool, Choices) when is_list(Choices) ->
yesno_io(Bool, Choices).
% internal
addslashes([], Acc) ->
lists:reverse(Acc);
addslashes([H|T], Acc) when H =:= $"; H =:= $' ->
addslashes(T, [H, $\\|Acc]);
addslashes([H|T], Acc) ->
addslashes(T, [H|Acc]).
cut([], _, Acc) ->
lists:reverse(Acc);
cut([H|T], H, Acc) ->
cut(T, H, Acc);
cut([H|T], Char, Acc) ->
cut(T, Char, [H|Acc]).
escape(Binary, Index) when is_binary(Binary) ->
case Binary of
<> ->
process_binary_match(Pre, <<"<">>, size(Post), escape(Post, 0));
<, Post/binary>> ->
process_binary_match(Pre, <<">">>, size(Post), escape(Post, 0));
<> ->
process_binary_match(Pre, <<"&">>, size(Post), escape(Post, 0));
<> ->
process_binary_match(Pre, <<""">>, size(Post), escape(Post, 0));
<> ->
process_binary_match(Pre, <<"'">>, size(Post), escape(Post, 0));
<<_:Index/binary, _, _/binary>> ->
escape(Binary, Index + 1);
Binary ->
Binary
end;
escape([], Acc) ->
lists:reverse(Acc);
escape("<" ++ Rest, Acc) ->
escape(Rest, lists:reverse("<", Acc));
escape(">" ++ Rest, Acc) ->
escape(Rest, lists:reverse(">", Acc));
escape("&" ++ Rest, Acc) ->
escape(Rest, lists:reverse("&", Acc));
escape("\"" ++ Rest, Acc) ->
escape(Rest, lists:reverse(""", Acc));
escape("'" ++ Rest, Acc) ->
escape(Rest, lists:reverse("'", Acc));
escape([C | Rest], Acc) ->
escape(Rest, [C | Acc]).
escapejs([], Acc) ->
lists:reverse(Acc);
escapejs([C | Rest], Acc) when C < 32; C =:= $"; C =:= $'; C =:= $\\; C =:= $<;
C =:= $>; C =:= $&; C =:= $=; C =:= $-; C =:= $;;
C =:= 8232; C =:= 8233 -> % just following django here...
escapejs(Rest, lists:reverse(lists:flatten(io_lib:format("\\u~4.16.0B", [C])), Acc));
escapejs([C | Rest], Acc) ->
escapejs(Rest, [C | Acc]).
filesizeformat(Bytes, UnitStr) ->
lists:flatten(io_lib:format("~.1f ~s", [Bytes, UnitStr])).
fix_ampersands(Input, Index) when is_binary(Input) ->
case Input of
<> ->
process_binary_match(Pre, <<"&">>, size(Post), Post);
<<_:Index/binary, _/binary>> ->
fix_ampersands(Input, Index + 1);
_ ->
Input
end;
fix_ampersands([], Acc) ->
lists:reverse(Acc);
fix_ampersands("&" ++ Rest, Acc) ->
fix_ampersands(Rest, lists:reverse("&", Acc));
fix_ampersands([C | Rest], Acc) ->
fix_ampersands(Rest, [C | Acc]).
iriencode([], Acc) ->
lists:reverse(Acc);
iriencode([C | Rest], Acc) when ?NO_IRI_ENCODE(C) ->
iriencode(Rest, [C | Acc]);
iriencode([$\s | Rest], Acc) ->
iriencode(Rest, [$+ | Acc]);
iriencode([C | Rest], Acc) ->
<> = <>,
iriencode(Rest, [hexdigit(Lo), hexdigit(Hi), $\% | Acc]).
join_io([], _Sep) -> [];
join_io([_] = X, _Sep) -> X;
join_io([X|T], Sep) -> [X,Sep] ++ join_io(T, Sep).
linebreaksbr(Input, Index) when is_binary(Input) ->
Break = <<"
">>,
case Input of
<> ->
process_binary_match(Pre, Break, size(Post), linebreaksbr(Post, 0));
<> ->
process_binary_match(Pre, Break, size(Post), linebreaksbr(Post, 0));
<<_:Index/binary, _/binary>> ->
linebreaksbr(Input, Index + 1);
_ ->
Input
end;
linebreaksbr([], Acc) ->
lists:reverse(Acc);
linebreaksbr("\r\n" ++ Rest, Acc) ->
linebreaksbr(Rest, lists:reverse("
", Acc));
linebreaksbr("\n" ++ Rest, Acc) ->
linebreaksbr(Rest, lists:reverse("
", Acc));
linebreaksbr([C | Rest], Acc) ->
linebreaksbr(Rest, [C | Acc]).
lower(Input, Index) ->
case Input of
<> when Byte >= $A andalso Byte =< $Z ->
process_binary_match(Pre, <<(Byte - $A + $a)>>, size(Post), lower(Post, 0));
<<_:Index/binary, _/binary>> ->
lower(Input, Index + 1);
_ ->
Input
end.
phone2numeric([], Acc) ->
lists:reverse(Acc);
phone2numeric([H|T], Acc) when H >= $a, H =< $c; H >= $A, H =< $C ->
phone2numeric(T, [$2|Acc]);
phone2numeric([H|T], Acc) when H >= $d, H =< $f; H >= $D, H =< $F ->
phone2numeric(T, [$3|Acc]);
phone2numeric([H|T], Acc) when H >= $g, H =< $i; H >= $G, H =< $I ->
phone2numeric(T, [$4|Acc]);
phone2numeric([H|T], Acc) when H >= $j, H =< $l; H >= $J, H =< $L ->
phone2numeric(T, [$5|Acc]);
phone2numeric([H|T], Acc) when H >= $m, H =< $o; H >= $M, H =< $O ->
phone2numeric(T, [$6|Acc]);
phone2numeric([H|T], Acc) when H >= $p, H =< $s; H >= $P, H =< $S ->
phone2numeric(T, [$7|Acc]);
phone2numeric([H|T], Acc) when H >= $t, H =< $v; H >= $T, H =< $V ->
phone2numeric(T, [$8|Acc]);
phone2numeric([H|T], Acc) when H >= $w, H =< $z; H >= $W, H =< $Z ->
phone2numeric(T, [$9|Acc]);
phone2numeric([H|T], Acc) ->
phone2numeric(T, [H|Acc]).
slugify([], Acc) ->
lists:reverse(Acc);
slugify([H|T], Acc) when H >= $A, H =< $Z ->
slugify(T, [H-$A+$a|Acc]);
slugify([$\ |T], Acc) ->
slugify(T, [$-|Acc]);
slugify([H|T], Acc) when H >= $a, H =< $z; H >= $0, H =< $9; H =:= $_ ->
slugify(T, [H|Acc]);
slugify([_|T], Acc) ->
slugify(T, Acc).
title([], Acc) ->
lists:reverse(Acc);
title([Char | Rest], [] = Acc) when Char >= $a, Char =< $z ->
title(Rest, [Char + ($A - $a) | Acc]);
title([Char | Rest], [Sep|[Sep2|_Other]] = Acc)
when Char >= $a, Char =< $z,
not (Sep >= $a andalso Sep =< $z),
not (Sep >= $A andalso Sep =< $Z),
not (Sep >= $0 andalso Sep =< $9),
not (Sep =:= $' andalso (Sep2 >= $a andalso Sep2 =< $z)) ->
title(Rest, [Char + ($A - $a) | Acc]);
title([Char | Rest], Acc) ->
title(Rest, [Char | Acc]).
truncatechars([], _CharsLeft, Acc) ->
lists:reverse(Acc);
truncatechars(_Input, 0, Acc) ->
lists:reverse("..." ++ Acc);
truncatechars([C|Rest], CharsLeft, Acc) when C >= 2#11111100 ->
truncatechars(Rest, CharsLeft + 4, [C|Acc]);
truncatechars([C|Rest], CharsLeft, Acc) when C >= 2#11111000 ->
truncatechars(Rest, CharsLeft + 3, [C|Acc]);
truncatechars([C|Rest], CharsLeft, Acc) when C >= 2#11110000 ->
truncatechars(Rest, CharsLeft + 2, [C|Acc]);
truncatechars([C|Rest], CharsLeft, Acc) when C >= 2#11100000 ->
truncatechars(Rest, CharsLeft + 1, [C|Acc]);
truncatechars([C|Rest], CharsLeft, Acc) when C >= 2#11000000 ->
truncatechars(Rest, CharsLeft, [C|Acc]);
truncatechars([C|Rest], CharsLeft, Acc) ->
truncatechars(Rest, CharsLeft - 1, [C|Acc]).
truncatewords(Value, _WordsLeft, _Acc) when is_atom(Value) ->
Value;
truncatewords([], _WordsLeft, Acc) ->
lists:reverse(Acc);
truncatewords(_Input, 0, Acc) ->
lists:reverse("..." ++ Acc);
truncatewords([C1, C2|Rest], WordsLeft, Acc) when C1 =/= $\ andalso C2 =:= $\ ->
truncatewords([C2|Rest], WordsLeft - 1, [C1|Acc]);
truncatewords([C1|Rest], WordsLeft, Acc) ->
truncatewords(Rest, WordsLeft, [C1|Acc]).
truncatewords_html([], _WordsLeft, Acc, [], _) ->
lists:reverse(Acc);
truncatewords_html(_Input, 0, Acc, [], _) ->
lists:reverse(Acc);
truncatewords_html(Input, 0, Acc, [Tag|RestOfTags], done) ->
truncatewords_html(Input, 0, ">"++Tag++"/<" ++ Acc, RestOfTags, done);
truncatewords_html(Input, 0, Acc, [Tag|RestOfTags], _) ->
truncatewords_html(Input, 0, "...>"++Tag++"/<" ++ Acc, RestOfTags, done);
truncatewords_html([], WordsLeft, Acc, [Tag|RestOfTags], _) ->
truncatewords_html([], WordsLeft, ">"++Tag++"/<" ++ Acc, RestOfTags, text);
truncatewords_html([C|Rest], WordsLeft, Acc, Tags, text) when C =:= $< ->
truncatewords_html(Rest, WordsLeft, [C|Acc], [""|Tags], tag);
truncatewords_html([C1, C2|Rest], WordsLeft, Acc, Tags, text) when C1 =/= $\ , C2 =:= $\ ; C1 =/= $\ , C2 =:= $< ->
truncatewords_html([C2|Rest], WordsLeft - 1, [C1|Acc], Tags, text);
truncatewords_html([C|Rest], WordsLeft, Acc, Tags, text) ->
truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, text);
truncatewords_html([C|Rest], WordsLeft, Acc, [""|Tags], tag) when C =:= $/ ->
truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, close_tag);
truncatewords_html([C|Rest], WordsLeft, Acc, [Tag|RestOfTags], tag) when C >= $a, C =< $z; C >= $A, C =< $Z ->
truncatewords_html(Rest, WordsLeft, [C|Acc], [[C|Tag]|RestOfTags], tag);
truncatewords_html([C|Rest], WordsLeft, Acc, Tags, tag) when C =:= $> ->
truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, text);
truncatewords_html([C|Rest], WordsLeft, Acc, Tags, tag) ->
truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, attrs);
truncatewords_html([C|Rest], WordsLeft, Acc, Tags, attrs) when C =:= $> ->
truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, text);
truncatewords_html([C|Rest], WordsLeft, Acc, [_Tag|RestOfTags], close_tag) when C =:= $> ->
truncatewords_html(Rest, WordsLeft, [C|Acc], RestOfTags, text).
wordcount([], Count) ->
Count;
wordcount([C1], Count) when C1 =/= $\ ->
Count+1;
wordcount([C1, C2|Rest], Count) when C1 =/= $\ andalso C2 =:= $\ ->
wordcount([C2|Rest], Count + 1);
wordcount([_|Rest], Count) ->
wordcount(Rest, Count).
% No more input, we're done
wordwrap([], Acc, WordAcc, _LineLength, _WrapAt) ->
lists:reverse(WordAcc ++ Acc);
% Premature newline
wordwrap([$\n | Rest], Acc, WordAcc, _LineLength, WrapAt) ->
wordwrap(Rest, [$\n | WordAcc ++ Acc], [], 0, WrapAt);
% Hit the wrap length at a space character. Add a newline
wordwrap([$\ | Rest], Acc, WordAcc, WrapAt, WrapAt) ->
wordwrap(Rest, [$\n | WordAcc ++ Acc], [], 0, WrapAt);
% Hit a space character before the wrap length. Keep going
wordwrap([$\ | Rest], Acc, WordAcc, LineLength, WrapAt) ->
wordwrap(Rest, [$\ | WordAcc ++ Acc], [], LineLength + 1 + erlang:length(WordAcc), WrapAt);
% Overflowed the current line while building a word
wordwrap([C | Rest], Acc, WordAcc, 0, WrapAt) when erlang:length(WordAcc) > WrapAt ->
wordwrap(Rest, Acc, [C | WordAcc], 0, WrapAt);
wordwrap([C | Rest], Acc, WordAcc, LineLength, WrapAt) when erlang:length(WordAcc) + LineLength > WrapAt ->
wordwrap(Rest, [$\n | Acc], [C | WordAcc], 0, WrapAt);
% Just building a word...
wordwrap([C | Rest], Acc, WordAcc, LineLength, WrapAt) ->
wordwrap(Rest, Acc, [C | WordAcc], LineLength, WrapAt).
% Taken from quote_plus of mochiweb_util
urlencode(Input, Index) when is_binary(Input) ->
case Input of
<<_:Index/binary, Byte, _/binary>> when ?NO_ENCODE(Byte) ->
urlencode(Input, Index + 1);
<> ->
process_binary_match(Pre, <<"+">>, size(Post), urlencode(Post, 0));
<> ->
HiDigit = hexdigit(Hi),
LoDigit = hexdigit(Lo),
Code = <<$\%, HiDigit, LoDigit>>,
process_binary_match(Pre, Code, size(Post), urlencode(Post, 0));
Input ->
Input
end;
urlencode([], Acc) ->
lists:reverse(Acc);
urlencode([C | Rest], Acc) when ?NO_ENCODE(C) ->
urlencode(Rest, [C | Acc]);
urlencode([$\s | Rest], Acc) ->
urlencode(Rest, [$+ | Acc]);
urlencode([C | Rest], Acc) ->
<> = <>,
urlencode(Rest, [hexdigit(Lo), hexdigit(Hi), $\% | Acc]).
%% @doc Converts URLs in text into clickable links.
%%TODO: Autoescape not yet implemented
urlize(Input) when is_binary(Input) ->
urlize(binary_to_list(Input),0);
urlize(Input) ->
urlize(Input,0).
urlize(Input, Trunc) when is_binary(Input) ->
urlize(binary_to_list(Input),Trunc);
urlize(Input, Trunc) ->
{ok,RE} = re:compile("(([[:alpha:]]+://|www\.)[^<>[:space:]]+[[:alnum:]/])"),
RegexResult = re:run(Input,RE,[global]),
case RegexResult of
{match, Matches} ->
Indexes = lists:map(fun(Match) -> lists:nth(2,Match) end, Matches),
Domains = lists:map(fun({Start, Length}) -> lists:sublist(Input, Start+1, Length) end, Indexes),
URIDomains = lists:map(fun(Domain) -> addDefaultURI(Domain) end, Domains),
case Trunc == 0 of
true ->
DomainsTrunc = Domains;
false ->
DomainsTrunc = lists:map(fun(Domain) -> string:concat( string:substr(Domain,1,Trunc-3), "...") end, Domains)
end,
ReplaceList = lists:zip(URIDomains,DomainsTrunc),
ReplaceStrings = lists:map(fun({URIDomain,Domain}) -> lists:flatten(io_lib:format("~s",[URIDomain,Domain])) end, ReplaceList),
Template = re:replace(Input,"(([[:alpha:]]+://|www\.)[^<>[:space:]]+[[:alnum:]/])", "~s", [global,{return,list}]),
Result = lists:flatten(io_lib:format(Template,ReplaceStrings)),
Result;
nomatch ->
Input
end.
%% @doc Converts URLs into clickable links just like urlize, but truncates URLs longer than the given character limit.
urlizetrunc(Input, Trunc) ->
urlize(Input, Trunc).
addDefaultURI(Domain) ->
case string:str(Domain,"://") of
0 ->
Domain1 = string:concat("http://",Domain);
_ ->
Domain1 = Domain
end,
Domain1.
hexdigit(C) when C < 10 -> $0 + C;
hexdigit(C) when C < 16 -> $A + (C - 10).
process_binary_match(Pre, Insertion, SizePost, Post) ->
case {size(Pre), SizePost} of
{0, 0} -> Insertion;
{0, _} -> [Insertion, Post];
{_, 0} -> [Pre, Insertion];
_ -> [Pre, Insertion, Post]
end.
yesno_io(Bool, Choices) ->
%% io:format("Bool, Choices: ~p, ~p ~n",[Bool, Choices]),
Terms = string:tokens(Choices, ","),
case Bool of
true ->
lists:nth(1, Terms);
false ->
lists:nth(2, Terms);
undefined when erlang:length(Terms) == 2 -> % (converts undefined to false if no mapping for undefined is given)
lists:nth(2, Terms);
undefined when erlang:length(Terms) == 3 ->
lists:nth(3, Terms);
_ ->
error
end.
%% unjoin == split in other languages; inverse of join
%%FROM: http://www.erlang.org/pipermail/erlang-questions/2008-October/038896.html
unjoin(String, []) ->
unjoin0(String);
unjoin(String, [Sep]) when is_integer(Sep) ->
unjoin1(String, Sep);
unjoin(String, [C1,C2|L]) when is_integer(C1), is_integer(C2) ->
unjoin2(String, C1, C2, L).
%% Split a string at "", which is deemed to occur _between_
%% adjacent characters, but queerly, not at the beginning
%% or the end.
unjoin0([C|Cs]) ->
[[C] | unjoin0(Cs)];
unjoin0([]) ->
[].
%% Split a string at a single character separator.
unjoin1(String, Sep) ->
unjoin1_loop(String, Sep, "").
unjoin1_loop([Sep|String], Sep, Rev) ->
[lists:reverse(Rev) | unjoin1(String, Sep)];
unjoin1_loop([Chr|String], Sep, Rev) ->
unjoin1_loop(String, Sep, [Chr|Rev]);
unjoin1_loop([], _, Rev) ->
[lists:reverse(Rev)].
%% Split a string at a multi-character separator
%% [C1,C2|L]. These components are split out for
%% a fast match.
unjoin2(String, C1, C2, L) ->
unjoin2_loop(String, C1, C2, L, "").
unjoin2_loop([C1|S = [C2|String]], C1, C2, L, Rev) ->
case unjoin_prefix(L, String)
of no -> unjoin2_loop(S, C1, C2, L, [C1|Rev])
; Rest -> [lists:reverse(Rev) | unjoin2(Rest, C1, C2, L)]
end;
unjoin2_loop([Chr|String], C1, C2, L, Rev) ->
unjoin2_loop(String, C1, C2, L, [Chr|Rev]);
unjoin2_loop([], _, _, _, Rev) ->
[lists:reverse(Rev)].
unjoin_prefix([C|L], [C|S]) -> unjoin_prefix(L, S);
unjoin_prefix([], S) -> S;
unjoin_prefix(_, _) -> no.