diff --git a/Emakefile b/Emakefile index 8036f0c..d1667f9 100755 --- a/Emakefile +++ b/Emakefile @@ -1,6 +1,4 @@ -{"src/erlydtl/*", [debug_info, {outdir, "ebin"}]}. -{"src/erlydtl/i18n/*", [debug_info, {outdir, "ebin"}]}. -{"src/erlydtl/filter_lib/*", [debug_info, {outdir, "ebin"}]}. -{"src/tests/*", [debug_info, {outdir, "ebintest"}]}. -{"src/tests/i18n/*", [debug_info, {outdir, "ebintest"}]}. -{"src/demo/*", [debug_info, {outdir, "ebintest"}]}. +{"src/*", [debug_info, {outdir, "ebin"}]}. +{"src/i18n/*", [debug_info, {outdir, "ebin"}]}. +{"src/filter_lib/*", [debug_info, {outdir, "ebin"}]}. +{"tests/src/*", [debug_info, {outdir, "ebintest"}]}. diff --git a/Makefile b/Makefile index 4433e8e..67ac840 100755 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ ERL=erl ERLC=erlc -PARSER=src/erlydtl/erlydtl_parser +PARSER=src/erlydtl_parser all: compile @@ -11,7 +11,7 @@ compile: $(PARSER).erl $(ERL) -make $(PARSER).erl: $(PARSER).yrl - $(ERLC) -o src/erlydtl src/erlydtl/erlydtl_parser.yrl + $(ERLC) -o src src/erlydtl_parser.yrl run: compile $(ERL) -pa ebin @@ -29,4 +29,4 @@ clean: rm -fv ebin/*.beam rm -fv ebintest/* rm -fv erl_crash.dump $(PARSER).erl - rm -fv examples/rendered_output/* + rm -fv tests/output/* diff --git a/README.markdown b/README.markdown index 1f41506..ddab73d 100644 --- a/README.markdown +++ b/README.markdown @@ -125,4 +125,4 @@ From a Unix shell, run: make test -Note that the tests will create some output in examples/rendered_output. +Note that the tests will create some output in tests/output. diff --git a/ebin/erlydtl.app b/ebin/erlydtl.app index 6fdc187..d8349db 100644 --- a/ebin/erlydtl.app +++ b/ebin/erlydtl.app @@ -1,21 +1,17 @@ %% -*- mode: erlang -*- {application, erlydtl, [{description, "ErlyDTL implements most but not all of the Django Template Language"}, - {vsn, "0.6.1"}, + {vsn, "0.7.0"}, {modules, [ erlydtl, erlydtl_compiler, erlydtl_dateformat, - erlydtl_dateformat_tests, erlydtl_deps, - erlydtl_example_variable_storage, erlydtl_filters, - erlydtl_functional_tests, erlydtl_parser, erlydtl_runtime, erlydtl_scanner, erlydtl_slice, - erlydtl_unittests, erlydtl_i18n, i18n_manager, po_generator, diff --git a/src/erlydtl/erlydtl.erl b/src/erlydtl.erl similarity index 100% rename from src/erlydtl/erlydtl.erl rename to src/erlydtl.erl diff --git a/src/erlydtl/erlydtl_compiler.erl b/src/erlydtl_compiler.erl similarity index 98% rename from src/erlydtl/erlydtl_compiler.erl rename to src/erlydtl_compiler.erl index dfa935c..dff3f77 100755 --- a/src/erlydtl/erlydtl_compiler.erl +++ b/src/erlydtl_compiler.erl @@ -677,20 +677,23 @@ filter_ast(Variable, Filter, Context, TreeWalker) -> filter_ast_noescape(Variable, [{identifier, _, 'escape'}], Context, TreeWalker) -> value_ast(Variable, true, Context, TreeWalker); filter_ast_noescape(Variable, Filter, Context, TreeWalker) -> - {{VariableAst, Info}, TreeWalker2} = value_ast(Variable, true, Context, TreeWalker), - VarValue = filter_ast1(Filter, VariableAst), - {{VarValue, Info}, TreeWalker2}. - -filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst) -> - filter_ast2(Name, VariableAst, [erl_syntax:string(unescape_string_literal(ArgName))]); -filter_ast1([{identifier, _, Name}, {number_literal, _, ArgName}], VariableAst) -> - filter_ast2(Name, VariableAst, [erl_syntax:integer(list_to_integer(ArgName))]); -filter_ast1([{identifier, _, Name}|_], VariableAst) -> - filter_ast2(Name, VariableAst, []). - -filter_ast2(Name, VariableAst, AdditionalArgs) -> - erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(Name), - [VariableAst | AdditionalArgs]). + {{VariableAst, Info1}, TreeWalker2} = value_ast(Variable, true, Context, TreeWalker), + {VarValue, Info2} = filter_ast1(Filter, VariableAst, Context), + {{VarValue, merge_info(Info1, Info2)}, TreeWalker2}. + +filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst, _Context) -> + filter_ast2(Name, VariableAst, [erl_syntax:string(unescape_string_literal(ArgName))], []); +filter_ast1([{identifier, _, Name}, {number_literal, _, ArgName}], VariableAst, _Context) -> + filter_ast2(Name, VariableAst, [erl_syntax:integer(list_to_integer(ArgName))], []); +filter_ast1([{identifier, _, Name}, ArgVariable], VariableAst, Context) -> + {ArgAst, ArgVarName} = resolve_variable_ast(ArgVariable, Context), + filter_ast2(Name, VariableAst, [ArgAst], [ArgVarName]); +filter_ast1([{identifier, _, Name}], VariableAst, _Context) -> + filter_ast2(Name, VariableAst, [], []). + +filter_ast2(Name, VariableAst, AdditionalArgs, VarNames) -> + {erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(Name), + [VariableAst | AdditionalArgs]), #ast_info{var_names = VarNames}}. search_for_escape_filter(_, _, #dtl_context{auto_escape = on}) -> on; @@ -707,7 +710,6 @@ search_for_escape_filter(_Variable, _Filter) -> off. - resolve_variable_ast(VarTuple, Context) -> resolve_variable_ast(VarTuple, Context, 'find_value'). @@ -726,11 +728,6 @@ resolve_variable_ast({variable, {identifier, _, VarName}}, Context, FinderFuncti end, {VarValue, VarName}; -resolve_variable_ast({apply_filter, Variable, Filter}, Context, FinderFunction) -> - {VarAst, VarName} = resolve_variable_ast(Variable, Context, FinderFunction), - VarValue = filter_ast1(Filter, erl_syntax:list([VarAst])), - {VarValue, VarName}; - resolve_variable_ast(What, _Context, _FinderFunction) -> error_logger:error_msg("~p:resolve_variable_ast unhandled: ~p~n", [?MODULE, What]). diff --git a/src/erlydtl/erlydtl_deps.erl b/src/erlydtl_deps.erl similarity index 100% rename from src/erlydtl/erlydtl_deps.erl rename to src/erlydtl_deps.erl diff --git a/src/erlydtl/erlydtl_filters.erl b/src/erlydtl_filters.erl similarity index 61% rename from src/erlydtl/erlydtl_filters.erl rename to src/erlydtl_filters.erl index d2787f9..e505031 100755 --- a/src/erlydtl/erlydtl_filters.erl +++ b/src/erlydtl_filters.erl @@ -3,9 +3,9 @@ %%% @author Roberto Saccon [http://rsaccon.com] %%% @author Evan Miller %%% @copyright 2008 Roberto Saccon, Evan Miller -%%% @doc +%%% @doc %%% Template filters -%%% @end +%%% @end %%% %%% The MIT License %%% @@ -42,82 +42,93 @@ -include_lib("eunit/include/eunit.hrl"). -ifdef(TEST). - -export([cast_to_float/1,cast_to_integer/1,stringformat_io/7,round/2,unjoin/2]). + -export([cast_to_float/1,cast_to_integer/1,stringformat_io/7,round/2,unjoin/2,addDefaultURI/1]). -endif. - --export([add/2, + +-export([add/2, addslashes/1, - capfirst/1, - center/2, + capfirst/1, + center/2, cut/2, - date/2, + date/2, default/2, - default_if_none/2, - %dictsort/, - %dictsortreversed/, + default_if_none/2, + dictsort/2, + dictsortreversed/2, divisibleby/2, %escape/, escapejs/1, filesizeformat/1, - first/1, - fix_ampersands/1, + first/1, + fix_ampersands/1, floatformat/2, - force_escape/1, - format_integer/1, + force_escape/1, + format_integer/1, format_number/1, get_digit/2, %iriencode/1, - join/2, - last/1, - length/1, - length_is/2, + join/2, + last/1, + length/1, + length_is/2, linebreaks/1, - linebreaksbr/1, + linebreaksbr/1, linenumbers/1, ljust/2, - lower/1, + lower/1, make_list/1, phone2numeric/1, pluralize/1, pluralize/2, - %pprint/, + pprint/1, random/1, random_num/1, random_range/1, removetags/2, - rjust/2, + rjust/2, %safe/, %safeseq/, slice/2, slugify/1, stringformat/2, - %striptags/, + striptags/1, + time/1, time/2, - %timesince/, - %timeuntil/, + timesince/1, + timesince/2, + timeuntil/1, + timeuntil/2, title/1, - truncatewords/2, + truncatewords/2, %truncatewords_html/, - %unordered_list/, - upper/1, + %unordered_list/1, + upper/1, urlencode/1, - %urlize/, - %urlizetrunc/, + 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 =:= $- + (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 a number to the value. add(Input, Number) when is_binary(Input) -> list_to_binary(add(binary_to_list(Input), Number)); @@ -125,31 +136,35 @@ add(Input, Number) when is_list(Input) -> integer_to_list(add(list_to_integer(Input), Number)); add(Input, Number) when is_integer(Input) -> Input + Number. - + %% @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)]. - + [(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(Input) -> cut(binary_to_list(Input), Arg); cut(Input, [Char]) when is_list(Input) -> cut(Input, Char, []). - + %% @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)); @@ -176,6 +191,17 @@ default_if_none(undefined, Default) -> default_if_none(Input, _) -> Input. +%% @doc Takes a list of dictionaries and returns that list sorted by the key given in the argument. +dictsort(DictList, Key) -> + case lists:all(fun(Dict) -> dict:is_key(Key, Dict) end, DictList) of + true -> lists:sort(fun(K1,K2) -> dict:find(Key,K1) =< dict:find(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); @@ -186,8 +212,6 @@ divisibleby(Input, Divisor) when is_list(Divisor) -> divisibleby(Input, Divisor) when is_integer(Input), is_integer(Divisor) -> Input rem Divisor =:= 0. -%% @doc Escapes - %% @doc Escapes characters for use in JavaScript strings. escapejs(Input) when is_binary(Input) -> escapejs(binary_to_list(Input)); @@ -225,7 +249,7 @@ fix_ampersands(Input) when is_list(Input) -> floatformat(Number, Place) when is_binary(Number) -> floatformat(binary_to_list(Number), Place); floatformat(Number, Place) -> - floatformat_io(Number, Place). + floatformat_io(Number, cast_to_integer(Place)). floatformat_io(Number, []) -> floatformat_io(Number, -1); @@ -249,7 +273,7 @@ floatformat_io(Number, Precision) when Precision < 0 -> 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, []); @@ -332,44 +356,20 @@ linebreaksbr(Input) -> linebreaksbr(Input, []). %% @doc Displays text with line numbers. -%% probably slow implementation linenumbers(Input) when is_binary(Input) -> - linenumbers(binary_to_list(Input)); + linenumbers(binary_to_list(Input)); linenumbers(Input) when is_list(Input) -> - linenumbers_io(Input, [], 1). - -%linenumbers_io(Input) -> -% Lines = string:tokens(Input, "\n"), -% Numbers = lists:seq(1, erlang:length(Lines)), -% lists:concat(lists:zipwith(fun(Number, Line) -> erlang:integer_to_list(Number) ++ ". " ++ Line ++ "\n" end, Numbers, Lines)). - -%linenumbers(Input) -> -% linenumbers_io(Input, [], 1). + 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(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(Rest, lists:reverse("\n" ++ integer_to_list(LineNumber) ++ ". ", Acc), LineNumber + 1); linenumbers_io([H|T], Acc, LineNumber) -> - linenumbers_io(T, [H|Acc], LineNumber). - -%% @doc Displays text with line numbers. -%% tail recursive implementation -%% linenumbers(Input) when is_binary(Input) -> -%% linenumbers_io(binary_to_list(Input), [], 0); -%% linenumbers(Input) when is_list(Input) -> -%% linenumbers_io(Input, [], 0). -%% -%% linenumbers([], Acc, ) -> -%% lists:reverse(Acc); -%% linenumbers([Head|Rest], Acc, CountAcc) -> -%% Count = CountAcc + 1, -%% LineNumber = integer_to_list(Count) ++ ". ", -%% linenumbers(Rest, lists:append(LineNumber, Acc)); - + 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)); @@ -389,7 +389,7 @@ 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)); @@ -398,33 +398,34 @@ phone2numeric(Input) when is_list(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_io(Number, binary_to_list(Suffix) ); pluralize(Number, Suffix) when is_list(Suffix) -> - pluralize_io(Number, Suffix). - + pluralize_io(Number, Suffix). + pluralize(Number) -> - pluralize(Number, "s"). - + pluralize(Number, "s"). + pluralize_io(Number, Suffix) -> -%% io:format("Number, Suffix: ~p, ~p ~n", [Number, Suffix]), - case lists:member($, , Suffix) of - true -> - [Singular, Plural] = string:tokens(Suffix,","), - case Number > 1 of - false -> - Singular; - true -> - Plural - end; - false -> - case Number > 1 of - false -> - []; - true -> - Suffix - end - end. - + 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); @@ -458,7 +459,7 @@ removetags(Input, Tags) -> Regex = lists:flatten(io_lib:format("",[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)); @@ -467,23 +468,23 @@ rjust(Input, Number) -> %% @doc Returns a slice of the list. slice(Input, Index) when is_binary(Input) -> - erlydtl_slice:slice(binary_to_list(Input), Index); + erlydtl_slice:slice(binary_to_list(Input), Index); slice(Input, Index) when is_list(Input) -> - erlydtl_slice:slice(Input, Index). + 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(binary_to_list(Input), 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). + 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: @@ -495,140 +496,148 @@ stringformat(Input, Conversion) -> %% 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]); + _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]); + _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 -> + 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 == "." -> + Precision, "", "f") when Precision == "." -> Format = re:replace(Conversion, "f", "d", [{return, list}] ), stringformat_io(Input, Format, ConversionFlag, MinFieldWidth, - Precision, "", "d"); + 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)]); + _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)]); + [], [], "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)]; + _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)]; + _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)]); + _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)]); + _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)]); + _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)]); + _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)]); + _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)]); + 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; + "", [], "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)]. - + 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; + Input; cast_to_float(Input) when is_integer(Input) -> - Input + 0.0; + Input + 0.0; cast_to_float(Input) -> - try list_to_float(Input) - catch - error:_Reason -> - list_to_integer(Input) + 0.0 - end. + 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; + Input; cast_to_integer(Input) when is_float(Input) -> - erlang:round(Input); + erlang:round(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. - + 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)); @@ -636,16 +645,57 @@ 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) -> + 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(Input, [], capnext). + title(Input, []). %% @doc Truncates a string after a certain number of words. truncatewords(Input, Max) when is_binary(Input) -> @@ -655,6 +705,28 @@ truncatewords(_Input, Max) when Max =< 0 -> truncatewords(Input, Max) -> truncatewords(Input, Max, []). +%% @doc Recursively takes a self-nested list and returns an HTML unordered list -- WITHOUT opening and closing
    tags. +%%TODO: finish unordered_list +%% unordered_list(List) -> +%% lists:reverse(unordered_list(List, [])). +%% +%% unordered_list([], Acc) -> +%% Acc; +%% unordered_list(List, Acc) -> +%% [First|Rest] = List, +%% io:format("First is_list: ~p, ~p~n", [First, is_list(First)]), +%% case is_list(First) of +%% true -> +%% [First|Rest] = First, +%% Return = [First | Acc], +%% "
      " ++ unordered_list(Rest,Return) + "
    "; +%% false -> +%% Return = [First | Acc], +%% "
  • " ++ unordered_list(Rest,Return) + "
  • " +%% end. + + + %% @doc Converts a string into all uppercase. upper(Input) when is_binary(Input) -> list_to_binary(upper(binary_to_list(Input))); @@ -675,22 +747,22 @@ wordcount(Input) when is_list(Input) -> %% @doc Wraps words at specified line length, uses
    html tag to delimit lines wordwrap(Input, Number) when is_binary(Input) -> - wordwrap_io(binary_to_list(Input), Number); + wordwrap(binary_to_list(Input), Number); wordwrap(Input, Number) when is_list(Input) -> - wordwrap_io(Input, Number). - + 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(Choices) -> - yesno_io(binary_to_list(Bool), Choices); + yesno_io(binary_to_list(Bool), Choices); yesno(Bool, Choices) when is_list(Choices) -> - yesno_io(Bool, 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) when H =:= $"; H =:= $' -> +addslashes(T, [H, $\\|Acc]); addslashes([H|T], Acc) -> addslashes(T, [H|Acc]). @@ -737,9 +809,9 @@ escape([C | Rest], 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)); +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]). @@ -800,26 +872,26 @@ lower(Input, Index) -> phone2numeric([], Acc) -> lists:reverse(Acc); phone2numeric([H|T], Acc) when H >= $a, H =< $c; H >= $A, H =< $C -> - phone2numeric(T, [$2|Acc]); +phone2numeric(T, [$2|Acc]); phone2numeric([H|T], Acc) when H >= $d, H =< $f; H >= $D, H =< $F -> - phone2numeric(T, [$3|Acc]); +phone2numeric(T, [$3|Acc]); phone2numeric([H|T], Acc) when H >= $g, H =< $i; H >= $G, H =< $I -> - phone2numeric(T, [$4|Acc]); +phone2numeric(T, [$4|Acc]); phone2numeric([H|T], Acc) when H >= $j, H =< $l; H >= $J, H =< $L -> - phone2numeric(T, [$5|Acc]); +phone2numeric(T, [$5|Acc]); phone2numeric([H|T], Acc) when H >= $m, H =< $o; H >= $M, H =< $O -> - phone2numeric(T, [$6|Acc]); +phone2numeric(T, [$6|Acc]); phone2numeric([H|T], Acc) when H >= $p, H =< $s; H >= $P, H =< $S -> - phone2numeric(T, [$7|Acc]); +phone2numeric(T, [$7|Acc]); phone2numeric([H|T], Acc) when H >= $t, H =< $v; H >= $T, H =< $V -> - phone2numeric(T, [$8|Acc]); +phone2numeric(T, [$8|Acc]); phone2numeric([H|T], Acc) when H >= $w, H =< $z; H >= $W, H =< $Z -> - phone2numeric(T, [$9|Acc]); +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 -> @@ -827,22 +899,18 @@ slugify([H|T], Acc) when H >= $A, H =< $Z -> 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, [H|Acc]); slugify([_|T], Acc) -> slugify(T, Acc). -title([], Acc, _) -> +title([], Acc) -> lists:reverse(Acc); -title([$\ = H|T], Acc, capnext) -> - title(T, [H|Acc], capnext); -title([H|T], Acc, capnext) when H >= $a, H =< $z -> - title(T, [H - $a + $A|Acc], dont); -title([H|T], Acc, capnext) -> - title(T, [H|Acc], dont); -title([$\ = H|T], Acc, dont) -> - title(T, [H|Acc], capnext); -title([H|T], Acc, dont) -> - title(T, [H|Acc], dont). +title([Char | Rest], [] = Acc) when Char >= $a, Char =< $z -> + title(Rest, [Char + ($A - $a) | Acc]); +title([Char | Rest], [$\ |_] = Acc) when Char >= $a, Char =< $z -> + title(Rest, [Char + ($A - $a) | Acc]); +title([Char | Rest], Acc) -> + title(Rest, [Char | Acc]). truncatewords([], _WordsLeft, Acc) -> lists:reverse(Acc); @@ -862,28 +930,26 @@ wordcount([C1, C2|Rest], Count) when C1 =/= $\ andalso C2 =:= $\ -> wordcount([_|Rest], Count) -> wordcount(Rest, Count). -% based on: http://pragdave.pragprog.com/pragdave/2007/04/testfirst_word_.html -% This is the exported function: it passes the initial -% result set to the internal versions -wordwrap_io(Input, Number) -> - Words = string:tokens(Input, " "), - string:join(lists:reverse(wordwrap_io(Words, [""], Number)),""). -% When we run out of words, we're done -wordwrap_io([], Result, _Number) -> - Result; -% Adding a word to an empty line -wordwrap_io([ NextWord | Rest ], [ "" | PreviousLines ], Number) -> - wordwrap_io(Rest, [ NextWord | PreviousLines ], Number); -% Or to a line that's already partially full. There are two cases: -% 1. The word fits -wordwrap_io([ NextWord | Rest ], [ CurrentLine | PreviousLines ], Number) - when erlang:length(NextWord) + erlang:length(CurrentLine) < Number -> - %wordwrap_io(Rest, [ NextWord, " ", CurrentLine | PreviousLines ], Number); - wordwrap_io(Rest, [ lists:flatten([CurrentLine," " ,NextWord]) | PreviousLines ], Number); -% 2. The word doesn't fit, so we create a new line -wordwrap_io([ NextWord | Rest], [ CurrentLine | PreviousLines ], Number) -> - wordwrap_io(Rest, [ NextWord, "\n", CurrentLine | PreviousLines ], Number). - +% 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 @@ -911,6 +977,46 @@ 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:]/])"), + {match,Matches} = re:run(Input,RE,[global]), + 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. + +%% @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). @@ -921,69 +1027,69 @@ process_binary_match(Pre, Insertion, SizePost, 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. + %% 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 +%%FROM: http://www.erlang.org/pipermail/erlang-questions/2008-October/038896.html unjoin(String, []) -> - unjoin0(String); + unjoin0(String); unjoin(String, [Sep]) when is_integer(Sep) -> - unjoin1(String, Sep); + unjoin1(String, Sep); unjoin(String, [C1,C2|L]) when is_integer(C1), is_integer(C2) -> - unjoin2(String, C1, C2, L). + 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)]; + [[C] | unjoin0(Cs)]; unjoin0([]) -> - []. + []. %% Split a string at a single character separator. unjoin1(String, Sep) -> - unjoin1_loop(String, Sep, ""). + unjoin1_loop(String, Sep, ""). unjoin1_loop([Sep|String], Sep, Rev) -> - [lists:reverse(Rev) | unjoin1(String, Sep)]; + [lists:reverse(Rev) | unjoin1(String, Sep)]; unjoin1_loop([Chr|String], Sep, Rev) -> - unjoin1_loop(String, Sep, [Chr|Rev]); + unjoin1_loop(String, Sep, [Chr|Rev]); unjoin1_loop([], _, Rev) -> - [lists:reverse(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(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; + 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(String, C1, C2, L, [Chr|Rev]); unjoin2_loop([], _, _, _, Rev) -> - [lists:reverse(Rev)]. + [lists:reverse(Rev)]. unjoin_prefix([C|L], [C|S]) -> unjoin_prefix(L, S); unjoin_prefix([], S) -> S; diff --git a/src/erlydtl/erlydtl_i18n.erl b/src/erlydtl_i18n.erl similarity index 100% rename from src/erlydtl/erlydtl_i18n.erl rename to src/erlydtl_i18n.erl diff --git a/src/erlydtl/erlydtl_parser.yrl b/src/erlydtl_parser.yrl similarity index 99% rename from src/erlydtl/erlydtl_parser.yrl rename to src/erlydtl_parser.yrl index 343014c..6c5b6da 100755 --- a/src/erlydtl/erlydtl_parser.yrl +++ b/src/erlydtl_parser.yrl @@ -262,6 +262,7 @@ EndAutoEscapeBraced -> open_tag endautoescape_keyword close_tag. Filter -> identifier : ['$1']. Filter -> identifier ':' Literal : ['$1', '$3']. +Filter -> identifier ':' Variable : ['$1', '$3']. Literal -> string_literal : '$1'. Literal -> number_literal : '$1'. diff --git a/src/erlydtl/erlydtl_runtime.erl b/src/erlydtl_runtime.erl similarity index 100% rename from src/erlydtl/erlydtl_runtime.erl rename to src/erlydtl_runtime.erl diff --git a/src/erlydtl/erlydtl_scanner.erl b/src/erlydtl_scanner.erl similarity index 100% rename from src/erlydtl/erlydtl_scanner.erl rename to src/erlydtl_scanner.erl diff --git a/src/erlydtl/filter_lib/erlydtl_dateformat.erl b/src/filter_lib/erlydtl_dateformat.erl similarity index 100% rename from src/erlydtl/filter_lib/erlydtl_dateformat.erl rename to src/filter_lib/erlydtl_dateformat.erl diff --git a/src/erlydtl/filter_lib/erlydtl_slice.erl b/src/filter_lib/erlydtl_slice.erl similarity index 90% rename from src/erlydtl/filter_lib/erlydtl_slice.erl rename to src/filter_lib/erlydtl_slice.erl index 1fd52d7..ffedc87 100644 --- a/src/erlydtl/filter_lib/erlydtl_slice.erl +++ b/src/filter_lib/erlydtl_slice.erl @@ -1,6 +1,6 @@ -module(erlydtl_slice). --export([slice/2]). +-export([slice/2,slice_input_cases/7]). -define(TEST,""). -define(NOTEST,1). @@ -28,21 +28,22 @@ slice_input_cases(_List,ListLength,Start,false,[],false,[]) when Start < 0, Star throw(indexError); %[-1] slice_input_cases(List,ListLength,Start,false,[],false,[]) when Start<0 -> - S = start_transform(ListLength,Start+ListLength+1), - %E = end_transform(ListLength,Start+ListLength+2), - Step = 1, + S = start_transform(ListLength,Start+ListLength+1), LowerBound = single_index_bounds(S), - ?debugFmt("slice_transform exit: ~p, ~p, ~p, ~p~n",[List,S,E,Step]), - [Result] = lists:sublist(List,LowerBound,Step), - Result; + ?debugFmt("slice_transform exit: ~p, ~p, ~p~n",[List,S,LowerBound]), + %[Result] = lists:sublist(List,LowerBound,Step), + lists:nth(LowerBound,List); %[1] slice_input_cases(List,ListLength,Start,false,[],false,[]) -> - S = start_transform(ListLength,Start+1), + %S = start_transform(ListLength,Start+1), %E = end_transform(ListLength,Start+2), - Step = 1, - LowerBound = single_index_bounds(S), - ?debugFmt("slice_transform exit: ~p, ~p, ~p, ~p~n",[List,S,E,Step]), - [Result] = lists:sublist(List,LowerBound,Step), + Step = 1, + End = Start + 1, + {Start1,End1,Step1} = index_defaults(ListLength,Start,End,Step), + S = start_transform(ListLength,Start1), + E = end_transform(ListLength,End1), + ?debugFmt("slice_transform: S,E,Step1: ~p,~p,~p~n",[S,E,Step1]), + [Result] = slice_list(List,ListLength,S,false,E,false,Step1), Result; %slice_transform(List, ListLength, Start, C1, End, C2, Step) when End < 0, Step > 0 -> % []; @@ -79,7 +80,6 @@ slice_list(List,ListLength,Start,_C1,End,_C2,Step) when Step > 0 -> {LowerBound,UpperBound} = index_bounds(Step,ListLength,Start,End), ?debugFmt("LowerBound+1, UpperBound+1, UpperBound - LowerBound + 1: ~p, ~p, ~p~n",[LowerBound+1,UpperBound,UpperBound-LowerBound]), BoundList = lists:sublist(List,LowerBound+1,UpperBound-LowerBound), - %{ok, lists:map(fun(N) -> lists:nth(N, List) end, lists:sort(BoundList)) }; SequenceList = lists:seq(1,erlang:length(BoundList),Step), lists:map(fun (N) -> lists:nth(N,BoundList) end,SequenceList); slice_list(List,ListLength,Start,_C1,End,_C2,Step) when Step < 0 -> @@ -192,10 +192,6 @@ end_transform(ListLength, 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_list(Input)-> case lists:member($., Input) of true -> diff --git a/src/erlydtl/i18n/Makefile b/src/i18n/Makefile similarity index 100% rename from src/erlydtl/i18n/Makefile rename to src/i18n/Makefile diff --git a/src/erlydtl/i18n/i18n_manager.erl b/src/i18n/i18n_manager.erl similarity index 100% rename from src/erlydtl/i18n/i18n_manager.erl rename to src/i18n/i18n_manager.erl diff --git a/src/erlydtl/i18n/po_generator.erl b/src/i18n/po_generator.erl similarity index 100% rename from src/erlydtl/i18n/po_generator.erl rename to src/i18n/po_generator.erl diff --git a/src/erlydtl/i18n/po_scanner.erl b/src/i18n/po_scanner.erl similarity index 100% rename from src/erlydtl/i18n/po_scanner.erl rename to src/i18n/po_scanner.erl diff --git a/src/erlydtl/i18n/sources_parser.erl b/src/i18n/sources_parser.erl similarity index 100% rename from src/erlydtl/i18n/sources_parser.erl rename to src/i18n/sources_parser.erl diff --git a/examples/docroot/autoescape b/tests/input/autoescape similarity index 100% rename from examples/docroot/autoescape rename to tests/input/autoescape diff --git a/examples/docroot/base b/tests/input/base similarity index 100% rename from examples/docroot/base rename to tests/input/base diff --git a/examples/docroot/comment b/tests/input/comment similarity index 100% rename from examples/docroot/comment rename to tests/input/comment diff --git a/examples/docroot/custom_call b/tests/input/custom_call similarity index 100% rename from examples/docroot/custom_call rename to tests/input/custom_call diff --git a/examples/docroot/custom_tag b/tests/input/custom_tag similarity index 100% rename from examples/docroot/custom_tag rename to tests/input/custom_tag diff --git a/examples/docroot/cycle b/tests/input/cycle similarity index 100% rename from examples/docroot/cycle rename to tests/input/cycle diff --git a/examples/docroot/extends b/tests/input/extends similarity index 100% rename from examples/docroot/extends rename to tests/input/extends diff --git a/examples/docroot/extends_path b/tests/input/extends_path similarity index 100% rename from examples/docroot/extends_path rename to tests/input/extends_path diff --git a/examples/docroot/extends_path2 b/tests/input/extends_path2 similarity index 100% rename from examples/docroot/extends_path2 rename to tests/input/extends_path2 diff --git a/examples/docroot/filters b/tests/input/filters similarity index 100% rename from examples/docroot/filters rename to tests/input/filters diff --git a/examples/docroot/for b/tests/input/for similarity index 100% rename from examples/docroot/for rename to tests/input/for diff --git a/examples/docroot/for_list b/tests/input/for_list similarity index 100% rename from examples/docroot/for_list rename to tests/input/for_list diff --git a/examples/docroot/for_list_preset b/tests/input/for_list_preset similarity index 100% rename from examples/docroot/for_list_preset rename to tests/input/for_list_preset diff --git a/examples/docroot/for_preset b/tests/input/for_preset similarity index 100% rename from examples/docroot/for_preset rename to tests/input/for_preset diff --git a/examples/docroot/for_records b/tests/input/for_records similarity index 100% rename from examples/docroot/for_records rename to tests/input/for_records diff --git a/examples/docroot/for_records_preset b/tests/input/for_records_preset similarity index 100% rename from examples/docroot/for_records_preset rename to tests/input/for_records_preset diff --git a/examples/docroot/for_tuple b/tests/input/for_tuple similarity index 100% rename from examples/docroot/for_tuple rename to tests/input/for_tuple diff --git a/examples/docroot/if b/tests/input/if similarity index 100% rename from examples/docroot/if rename to tests/input/if diff --git a/examples/docroot/if_preset b/tests/input/if_preset similarity index 100% rename from examples/docroot/if_preset rename to tests/input/if_preset diff --git a/examples/docroot/ifequal b/tests/input/ifequal similarity index 100% rename from examples/docroot/ifequal rename to tests/input/ifequal diff --git a/examples/docroot/ifequal_preset b/tests/input/ifequal_preset similarity index 100% rename from examples/docroot/ifequal_preset rename to tests/input/ifequal_preset diff --git a/examples/docroot/ifnotequal b/tests/input/ifnotequal similarity index 100% rename from examples/docroot/ifnotequal rename to tests/input/ifnotequal diff --git a/examples/docroot/ifnotequal_preset b/tests/input/ifnotequal_preset similarity index 100% rename from examples/docroot/ifnotequal_preset rename to tests/input/ifnotequal_preset diff --git a/examples/docroot/include b/tests/input/include similarity index 100% rename from examples/docroot/include rename to tests/input/include diff --git a/examples/docroot/include.html b/tests/input/include.html similarity index 100% rename from examples/docroot/include.html rename to tests/input/include.html diff --git a/examples/docroot/include_path b/tests/input/include_path similarity index 100% rename from examples/docroot/include_path rename to tests/input/include_path diff --git a/examples/docroot/include_template b/tests/input/include_template similarity index 100% rename from examples/docroot/include_template rename to tests/input/include_template diff --git a/examples/docroot/now b/tests/input/now similarity index 100% rename from examples/docroot/now rename to tests/input/now diff --git a/examples/docroot/path1/base1 b/tests/input/path1/base1 similarity index 100% rename from examples/docroot/path1/base1 rename to tests/input/path1/base1 diff --git a/examples/docroot/path1/base2 b/tests/input/path1/base2 similarity index 100% rename from examples/docroot/path1/base2 rename to tests/input/path1/base2 diff --git a/examples/docroot/path1/include1 b/tests/input/path1/include1 similarity index 100% rename from examples/docroot/path1/include1 rename to tests/input/path1/include1 diff --git a/examples/docroot/path1/template1 b/tests/input/path1/template1 similarity index 100% rename from examples/docroot/path1/template1 rename to tests/input/path1/template1 diff --git a/examples/docroot/path2/base2 b/tests/input/path2/base2 similarity index 100% rename from examples/docroot/path2/base2 rename to tests/input/path2/base2 diff --git a/examples/docroot/path2/template2 b/tests/input/path2/template2 similarity index 100% rename from examples/docroot/path2/template2 rename to tests/input/path2/template2 diff --git a/examples/docroot/trans b/tests/input/trans similarity index 100% rename from examples/docroot/trans rename to tests/input/trans diff --git a/examples/docroot/var b/tests/input/var similarity index 100% rename from examples/docroot/var rename to tests/input/var diff --git a/examples/docroot/var_preset b/tests/input/var_preset similarity index 100% rename from examples/docroot/var_preset rename to tests/input/var_preset diff --git a/tests/py/erlydtl_python_test.py b/tests/py/erlydtl_python_test.py new file mode 100644 index 0000000..0c5869c --- /dev/null +++ b/tests/py/erlydtl_python_test.py @@ -0,0 +1,49 @@ +from erlport import Port, Protocol, String, Atom +from erlport.erlterms import decode +from django.template import Context, Template +from django.conf import settings +import types + +# Inherit custom protocol from erlport.Protocol +class ErlydtlProtocol(Protocol): + + def handle_slice(self, command): + list = command[0] + slice = "%s" % String(command[1]) + slice_test1_string = "%s[%s]" % (list,slice) + try: + result_list = eval(slice_test1_string) + except(IndexError): + result_list = Atom("indexError") + except: + result_list = Atom("error") + #print "result_list: %s" % (result_list) + return result_list + + #@doc Start list with 'object' to pass in a python term along with import statement in the form: + #@doc object|module to import|term (three strings delimited by "|" + def handle_template(self, command): + file = open("/tmp/debug.txt",'a') + template_text = "%s" % String(command[0]) + #value = "%s" % String(command[1]) + value = "%s" % String(command[1]) + value_split = value.split("|") + + if value_split[0] == u"object": + module = value_split[1] + exec "import %s" % module + value = eval(value_split[2]) + #term(((term.year, term.month, term.day), (term.hour, term.minute, term.second))) + c = Context({"value":value}) + t = Template(template_text) + result = String(t.render(c)) + file.close() + return result + + +if __name__ == "__main__": + settings.configure(DEBUG=True, TEMPLATE_DEBUG=True) + proto = ErlydtlProtocol() + # Run protocol with port open on STDIO + proto.run(Port(use_stdio=True)) + diff --git a/tests/py/erlydtl_python_test.pyc b/tests/py/erlydtl_python_test.pyc new file mode 100644 index 0000000..e389e87 Binary files /dev/null and b/tests/py/erlydtl_python_test.pyc differ diff --git a/tests/py/python_dtl_setup.txt b/tests/py/python_dtl_setup.txt new file mode 100644 index 0000000..9e5b19b --- /dev/null +++ b/tests/py/python_dtl_setup.txt @@ -0,0 +1,29 @@ +from erlport import Port, Protocol, String, Atom +from erlport.erlterms import decode +from django.template import Context, Template +from django.conf import settings +import types +from erlydtl_python_test import ErlydtlProtocol as proto +settings.configure(DEBUG=True, TEMPLATE_DEBUG=True) +ep = proto() + +ep.handle_template([u"{{ value|date }}",u"object,datetime,datetime.date.today()"]) +ep.handle_template([u"{{ value|time:\"H:i\" }}",u"object,datetime,datetime.datetime.now()"]) + + +DateFormat = erlydtl_filters_tests:erlydtl_render("{{ value|random }}", [ {value, ["b","c","d","D","F","j","l","L","m","M","n","N","t","w","W","y","Y","z"]} ] ). +Template = "{{ value|date:\"" ++ DateFormat ++ "\" }}". +Value = { {2010,12,1}, {10,11,12} }. +Port = erlydtl_python_test:start(). +PyDate = lists:flatten(io_lib:format("object|datetime|~s", [erlydtl_filters_tests:python_datetime_encode(Value)])). +erlydtl_filters_tests:py_template(Port, Template, PyDate). + + +from django.template import Context, Template +from django.conf import settings +settings.configure(DEBUG=True, TEMPLATE_DEBUG=True) +from erlydtl_python_test import ErlydtlProtocol as proto +ep = proto() +c = Context({"value": "Check out www.yahoo.com"}) +t = Template("{{ value|urlize }}") +t.render(c) \ No newline at end of file diff --git a/tests/py/run_erl_node.sh b/tests/py/run_erl_node.sh new file mode 100755 index 0000000..65a0140 --- /dev/null +++ b/tests/py/run_erl_node.sh @@ -0,0 +1,15 @@ +#! /bin/sh + +# A test case for testing packing/unpacking of erlang-terms: +# +# A message is sent from an erlang node to a python node. +# That message is echoed back to the erlang node, which checks +# if the received message matches the original message. +# + +# First make sure epmd is up and running. (needed by the python-node) +erl -noinput -detach -sname ensure_epmd_started@localhost -s erlang halt + + +erl -sname enode1@localhost \ + -setcookie cookie -pa ../../../../ebin/ diff --git a/src/tests/erlydtl_dateformat_tests.erl b/tests/src/erlydtl_dateformat_tests.erl similarity index 100% rename from src/tests/erlydtl_dateformat_tests.erl rename to tests/src/erlydtl_dateformat_tests.erl diff --git a/src/tests/erlydtl_example_variable_storage.erl b/tests/src/erlydtl_example_variable_storage.erl similarity index 100% rename from src/tests/erlydtl_example_variable_storage.erl rename to tests/src/erlydtl_example_variable_storage.erl diff --git a/src/tests/erlydtl_functional_tests.erl b/tests/src/erlydtl_functional_tests.erl similarity index 98% rename from src/tests/erlydtl_functional_tests.erl rename to tests/src/erlydtl_functional_tests.erl index 7172dd2..3504717 100644 --- a/src/tests/erlydtl_functional_tests.erl +++ b/tests/src/erlydtl_functional_tests.erl @@ -271,7 +271,7 @@ test_render(Name, Module) -> templates_docroot() -> - filename:join([erlydtl_deps:get_base_dir(), "examples", "docroot"]). + filename:join([erlydtl_deps:get_base_dir(), "tests", "input"]). templates_outdir() -> - filename:join([erlydtl_deps:get_base_dir(), "examples", "rendered_output"]). + filename:join([erlydtl_deps:get_base_dir(), "tests", "output"]). diff --git a/src/tests/erlydtl_unittests.erl b/tests/src/erlydtl_unittests.erl similarity index 63% rename from src/tests/erlydtl_unittests.erl rename to tests/src/erlydtl_unittests.erl index 1ff341f..1e63a0e 100644 --- a/src/tests/erlydtl_unittests.erl +++ b/tests/src/erlydtl_unittests.erl @@ -1,7 +1,7 @@ -module(erlydtl_unittests). - + -export([run_tests/0]). - + tests() -> [ {"vars", [ @@ -84,12 +84,12 @@ tests() -> {"now functional", <<"It is the {% now \"jS o\\f F Y\" %}.">>, [{var1, ""}], generate_test_date()} ]}, - {"trans", - [ - {"trans functional default locale", - <<"Hello {% trans \"Hi\" %}">>, [], <<"Hello Hi">> - }, - {"trans functional reverse locale", + {"trans", + [ + {"trans functional default locale", + <<"Hello {% trans \"Hi\" %}">>, [], <<"Hello Hi">> + }, + {"trans functional reverse locale", <<"Hello {% trans \"Hi\" %}">>, [], none, [{locale, "reverse"}], <<"Hello iH">> }, {"trans literal at run-time", @@ -104,7 +104,7 @@ tests() -> {"trans variable at run-time: No-op", <<"Hello {% trans var1 noop %}">>, [{var1, "Hi"}], fun("Hi") -> "Konichiwa" end, [], <<"Hello Hi">>} - ]}, + ]}, {"if", [ {"If/else", <<"{% if var1 %}boo{% else %}yay{% endif %}">>, [{var1, ""}], <<"yay">>}, @@ -190,19 +190,19 @@ tests() -> <<"{% if var1 > 2 %}yay{% endif %}">>, [{var1, 3}], <<"yay">>}, {"If int greater than number literal (false)", <<"{% if var1 > 2 %}yay{% endif %}">>, [{var1, 2}], <<"">>}, - + {"If int greater than or equal to number literal", <<"{% if var1 >= 2 %}yay{% endif %}">>, [{var1, 3}], <<"yay">>}, {"If int greater than or equal to number literal (2)", <<"{% if var1 >= 2 %}yay{% endif %}">>, [{var1, 2}], <<"yay">>}, {"If int greater than or equal to number literal (false)", <<"{% if var1 >= 2 %}yay{% endif %}">>, [{var1, 1}], <<"">>}, - + {"If int less than number literal", <<"{% if var1 < 2 %}yay{% endif %}">>, [{var1, 1}], <<"yay">>}, {"If int less than number literal (false)", <<"{% if var1 < 2 %}yay{% endif %}">>, [{var1, 2}], <<"">>}, - + {"If int less than or equal to number literal", <<"{% if var1 <= 2 %}yay{% endif %}">>, [{var1, 1}], <<"yay">>}, {"If int less than or equal to number literal", @@ -212,10 +212,10 @@ tests() -> ]}, {"if complex bool", [ {"If (true or false) and true", - <<"{% if (var1 or var2) and var3 %}yay{% endif %}">>, + <<"{% if (var1 or var2) and var3 %}yay{% endif %}">>, [{var1, true}, {var2, false}, {var3, true}], <<"yay">>}, {"If true or (false and true)", - <<"{% if var1 or (var2 and var3) %}yay{% endif %}">>, + <<"{% if var1 or (var2 and var3) %}yay{% endif %}">>, [{var1, true}, {var2, false}, {var3, true}], <<"yay">>} ]}, {"for", [ @@ -235,16 +235,16 @@ tests() -> <<"{% for number in person.home.numbers %}{{ number }}\n{% endfor %}">>, [{person, [{home, [{numbers, ["411", "911"]}]}]}], <<"411\n911\n">>}, {"Counter0", - <<"{% for number in numbers %}{{ forloop.counter0 }}. {{ number }}\n{% endfor %}">>, + <<"{% for number in numbers %}{{ forloop.counter0 }}. {{ number }}\n{% endfor %}">>, [{numbers, ["Zero", "One", "Two"]}], <<"0. Zero\n1. One\n2. Two\n">>}, {"Counter", - <<"{% for number in numbers %}{{ forloop.counter }}. {{ number }}\n{% endfor %}">>, + <<"{% for number in numbers %}{{ forloop.counter }}. {{ number }}\n{% endfor %}">>, [{numbers, ["One", "Two", "Three"]}], <<"1. One\n2. Two\n3. Three\n">>}, {"Reverse Counter0", - <<"{% for number in numbers %}{{ forloop.revcounter0 }}. {{ number }}\n{% endfor %}">>, + <<"{% for number in numbers %}{{ forloop.revcounter0 }}. {{ number }}\n{% endfor %}">>, [{numbers, ["Two", "One", "Zero"]}], <<"2. Two\n1. One\n0. Zero\n">>}, {"Reverse Counter", - <<"{% for number in numbers %}{{ forloop.revcounter }}. {{ number }}\n{% endfor %}">>, + <<"{% for number in numbers %}{{ forloop.revcounter }}. {{ number }}\n{% endfor %}">>, [{numbers, ["Three", "Two", "One"]}], <<"3. Three\n2. Two\n1. One\n">>}, {"Counter \"first\"", <<"{% for number in numbers %}{% if forloop.first %}{{ number }}{% endif %}{% endfor %}">>, @@ -356,13 +356,13 @@ tests() -> <<"{{ var1|addslashes }}">>, [{var1, "Jimmy's \"great\" meats'n'things"}], <<"Jimmy\\'s \\\"great\\\" meats\\'n\\'things">>}, {"|capfirst", - <<"{{ var1|capfirst }}">>, [{var1, "dana boyd"}], + <<"{{ var1|capfirst }}">>, [{var1, "dana boyd"}], <<"Dana boyd">>}, {"|center:10", - <<"{{ var1|center:10 }}">>, [{var1, "MB"}], + <<"{{ var1|center:10 }}">>, [{var1, "MB"}], <<" MB ">>}, {"|center:1", - <<"{{ var1|center:1 }}">>, [{var1, "KBR"}], + <<"{{ var1|center:1 }}">>, [{var1, "KBR"}], <<"B">>}, {"|cut:\" \"", <<"{{ var1|cut:\" \" }}">>, [{var1, "String with spaces"}], @@ -401,11 +401,24 @@ tests() -> {"|filesizeformat (GB)", <<"{{ var1|filesizeformat }}">>, [{var1, 1024 * 1024 * 1024}], <<"1.0 GB">>}, {"|first", - <<"{{ var1|first }}">>, [{var1, "James"}], + <<"{{ var1|first }}">>, [{var1, "James"}], <<"J">>}, {"|fix_ampersands", - <<"{{ var1|fix_ampersands }}">>, [{var1, "Ben & Jerry's"}], + <<"{{ var1|fix_ampersands }}">>, [{var1, "Ben & Jerry's"}], <<"Ben & Jerry's">>}, + + {"|floatformat:\"-1\"", + <<"{{ var1|floatformat:\"-1\" }}">>, [{var1, 34.23234}], + <<"34.2">>}, +%% ?assertEqual( "", erlydtl_filters:floatformat(,)), +%% ?assertEqual( "34", erlydtl_filters:floatformat(34.00000,-1)), +%% ?assertEqual( "34.3", erlydtl_filters:floatformat(34.26000,-1)), +%% ?assertEqual( "34.232", erlydtl_filters:floatformat(34.23234,3)), +%% ?assertEqual( "34.000", erlydtl_filters:floatformat(34.00000,3)), +%% ?assertEqual( "34.260", erlydtl_filters:floatformat(34.26000,3)), +%% ?assertEqual( "34.232", erlydtl_filters:floatformat(34.23234,-3)), +%% ?assertEqual( "34", erlydtl_filters:floatformat(34.00000,-3)), +%% ?assertEqual( "34.260", erlydtl_filters:floatformat(34.26000,-3)). {"|force_escape", <<"{{ var1|force_escape }}">>, [{var1, "Ben & Jerry's <=> \"The World's Best Ice Cream\""}], <<"Ben & Jerry's <=> "The World's Best Ice Cream"">>}, @@ -437,33 +450,320 @@ tests() -> {"|length", <<"{{ var1|length }}">>, [{var1, "antidisestablishmentarianism"}], <<"28">>}, + {"|linebreaks", + <<"{{ var1|linebreaks }}">>, [{var1, "Joel\nis a slug"}], + <<"

    Joel
    is a slug

    ">>}, {"|linebreaksbr", <<"{{ var1|linebreaksbr }}">>, [{var1, "One\nTwo\n\nThree\n\n\n"}], <<"One
    Two

    Three


    ">>}, {"|linebreaksbr", <<"{{ \"One\\nTwo\\n\\nThree\\n\\n\\n\"|linebreaksbr }}">>, [], - <<"One
    Two

    Three


    ">>}, + <<"One
    Two

    Three


    ">>}, + {"|linenumbers", + <<"{{ var1|linenumbers }}">>, [{var1, "a\nb\nc"}], + <<"1. a\n2. b\n3. c">>}, + {"|linenumbers", + <<"{{ var1|linenumbers }}">>, [{var1, "a"}], + <<"1. a">>}, + {"|linenumbers", + <<"{{ var1|linenumbers }}">>, [{var1, "a\n"}], + <<"1. a\n2. ">>}, {"|ljust:10", <<"{{ var1|ljust:10 }}">>, [{var1, "Gore"}], <<"Gore ">>}, {"|lower", <<"{{ var1|lower }}">>, [{var1, "E. E. Cummings"}], <<"e. e. cummings">>}, + {"|makelist", + <<"{{ list|make_list }}">>, [{list, "Joel"}], + <<"J","o","e","l">>}, + {"|pluralize", + <<"{{ num|pluralize }}">>, [{num, 1}], + <<"">>}, + {"|pluralize", + <<"{{ num|pluralize }}">>, [{num, 2}], + <<"s">>}, + {"|pluralize:\"s\"", + <<"{{ num|pluralize }}">>, [{num, 1}], + <<"">>}, + {"|pluralize:\"s\"", + <<"{{ num|pluralize }}">>, [{num, 2}], + <<"s">>}, + {"|pluralize:\"y,es\" (list)", + <<"{{ num|pluralize:\"y,es\" }}">>, [{num, 1}], + <<"y">>}, + {"|pluralize:\"y,es\" (list)", + <<"{{ num|pluralize:\"y,es\" }}">>, [{num, 2}], + <<"es">>}, {"|random", <<"{{ var1|random }}">>, [{var1, ["foo", "foo", "foo"]}], <<"foo">>}, + {"|removetags:\"b span\"", + <<"{{ var1|removetags:\"b span\" }}">>, [{var1, "Joel a slug"}], + <<"Joel a slug">>}, {"|rjust:10", <<"{{ var1|rjust:10 }}">>, [{var1, "Bush"}], <<" Bush">>}, - {"|phone2numeric", + %%python/django slice is zero based, erlang lists are 1 based + %%first number included, second number not + %%negative numbers are allowed + %%regex to convert from erlydtl_filters_tests: + % for slice: \?assert.*\( \[(.*)\], erlydtl_filters:(.*)\((.*),"(.*)"\)\), + % {"|slice:\"$4\"", <<"{{ var|$2:\"$4\" }}">>, [{var, $3}],<<$1>>}, + % \t\t{"|slice:\"$4\"",\n\t\t\t\t\t <<"{{ var|$2:\"$4\" }}">>, [{var, $3}],\n\t\t\t\t\t<<$1>>}, + % + % for stringformat: + % \?assert.*\( (.*), erlydtl_filters:(.*)\((.*), "(.*)"\) \) + % \t\t{"|stringformat:\"$4\"",\n\t\t\t\t\t <<"{{ var|$2:\"$4\" }}">>, [{var, $3}],\n\t\t\t\t\t<<$1>>} + + {"|slice:\":\"", + <<"{{ var|slice:\":\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1,2,3,4,5,6,7,8,9>>}, + {"|slice:\"1\"", + <<"{{ var|slice:\"1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<"2">>}, + {"|slice:\"100\"", + <<"{{ var|slice:\"100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<"indexError">>}, + {"|slice:\"-1\"", + <<"{{ var|slice:\"-1\" }}">>, [{var, ["a","b","c","d","e","f","g","h","i"]}], + <<"i">>}, + {"|slice:\"-1\"", + <<"{{ var|slice:\"-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<"9">>}, + {"|slice:\"-100\"", + <<"{{ var|slice:\"-100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<"indexError">>}, + {"|slice:\"1:\"", + <<"{{ var|slice:\"1:\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<2,3,4,5,6,7,8,9>>}, + {"|slice:\"100:\"", + <<"{{ var|slice:\"100:\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"-1:\"", + <<"{{ var|slice:\"-1:\" }}">>, [{var, ["a","b","c","d","e","f","h","i","j"]}], + <<"j">>}, + {"|slice:\"-1:\"", + <<"{{ var|slice:\"-1:\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<9>>}, + {"|slice:\"-100:\"", + <<"{{ var|slice:\"-100:\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1,2,3,4,5,6,7,8,9>>}, + + {"|slice:\":1\"", + <<"{{ var|slice:\":1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1>>}, + {"|slice:\":100\"", + <<"{{ var|slice:\":100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1,2,3,4,5,6,7,8,9>>}, + {"|slice:\":-1\"", + <<"{{ var|slice:\":-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1,2,3,4,5,6,7,8>>}, + {"|slice:\":-100\"", + <<"{{ var|slice:\":-100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + + {"|slice:\"-1:-1\"", + <<"{{ var|slice:\"-1:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"1:1\"", + <<"{{ var|slice:\"1:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"1:-1\"", + <<"{{ var|slice:\"1:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<2,3,4,5,6,7,8>>}, + {"|slice:\"-1:1\"", + <<"{{ var|slice:\"-1:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + + {"|slice:\"-100:-100\"", + <<"{{ var|slice:\"-100:-100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"100:100\"", + <<"{{ var|slice:\"100:100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"100:-100\"", + <<"{{ var|slice:\"100:-100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"-100:100\"", + <<"{{ var|slice:\"-100:100\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1,2,3,4,5,6,7,8,9>>}, + + + {"|slice:\"1:3\"", + <<"{{ var|slice:\"1:3\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<2,3>>}, + + {"|slice:\"::\"", + <<"{{ var|slice:\"::\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1,2,3,4,5,6,7,8,9>>}, + {"|slice:\"1:9:1\"", + <<"{{ var|slice:\"1:9:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<2,3,4,5,6,7,8,9>>}, + {"|slice:\"10:1:-1\"", + <<"{{ var|slice:\"10:1:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<9,8,7,6,5,4,3>>}, + {"|slice:\"-111:-1:1\"", + <<"{{ var|slice:\"-111:-1:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1,2,3,4,5,6,7,8>>}, + + {"|slice:\"-111:-111:1\"", + <<"{{ var|slice:\"-111:-111:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"111:111:1\"", + <<"{{ var|slice:\"111:111:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"-111:111:1\"", + <<"{{ var|slice:\"-111:111:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<1,2,3,4,5,6,7,8,9>>}, + {"|slice:\"111:-111:1\"", + <<"{{ var|slice:\"111:-111:1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + + {"|slice:\"-111:-111:-1\"", + <<"{{ var|slice:\"-111:-111:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"111:111:-1\"", + <<"{{ var|slice:\"111:111:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"-111:111:-1\"", + <<"{{ var|slice:\"-111:111:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<>>}, + {"|slice:\"111:-111:-1\"", + <<"{{ var|slice:\"111:-111:-1\" }}">>, [{var, [1,2,3,4,5,6,7,8,9]}], + <<9,8,7,6,5,4,3,2,1>>}, {"|phone2numeric", <<"{{ var1|phone2numeric }}">>, [{var1, "1-800-COLLECT"}], <<"1-800-2655328">>}, {"|slugify", <<"{{ var1|slugify }}">>, [{var1, "What The $#_! Was He Thinking?"}], <<"what-the-_-was-he-thinking">>}, + {"|slice:\"s\"", + <<"{{ var|stringformat:\"s\" }}">>, [{var, "test"}], + <<"test">>}, + {"|stringformat:\"s\"", + <<"{{ var|stringformat:\"s\" }}">>, [{var, "test"}], + <<"test">>}, + {"|stringformat:\"s\"", + <<"{{ var|stringformat:\"s\" }}">>, [{var, "1"}], + <<"1">>}, + {"|stringformat:\"s\"", + <<"{{ var|stringformat:\"s\" }}">>, [{var, "test"}], + <<"test">>}, + {"|stringformat:\"10s\"", + <<"{{ var|stringformat:\"10s\" }}">>, [{var, "test"}], + <<" test">>}, + {"|stringformat:\"-10s\"", + <<"{{ var|stringformat:\"-10s\" }}">>, [{var, "test"}], + <<"test ">>}, + + {"|stringformat:\"d\"", + <<"{{ var|stringformat:\"d\" }}">>, [{var, "90"}], + <<"90">>}, + {"|stringformat:\"10d\"", + <<"{{ var|stringformat:\"10d\" }}">>, [{var, "90"}], + <<" 90">>}, + {"|stringformat:\"-10d\"", + <<"{{ var|stringformat:\"-10d\" }}">>, [{var, "90"}], + <<"90 ">>}, + {"|stringformat:\"i\"", + <<"{{ var|stringformat:\"i\" }}">>, [{var, "90"}], + <<"90">>}, + {"|stringformat:\"10i\"", + <<"{{ var|stringformat:\"10i\" }}">>, [{var, "90"}], + <<" 90">>}, + {"|stringformat:\"-10i\"", + <<"{{ var|stringformat:\"-10i\" }}">>, [{var, "90"}], + <<"90 ">>}, + {"|stringformat:\"0.2d\"", + <<"{{ var|stringformat:\"0.2d\" }}">>, [{var, "9"}], + <<"09">>}, + {"|stringformat:\"10.4d\"", + <<"{{ var|stringformat:\"10.4d\" }}">>, [{var, "9"}], + <<" 0009">>}, + {"|stringformat:\"-10.4d\"", + <<"{{ var|stringformat:\"-10.4d\" }}">>, [{var, "9"}], + <<"0009 ">>}, + + {"|stringformat:\"f\"", + <<"{{ var|stringformat:\"f\" }}">>, [{var, "1"}], + <<"1.000000">>}, + {"|stringformat:\".2f\"", + <<"{{ var|stringformat:\".2f\" }}">>, [{var, "1"}], + <<"1.00">>}, + {"|stringformat:\"0.2f\"", + <<"{{ var|stringformat:\"0.2f\" }}">>, [{var, "1"}], + <<"1.00">>}, + {"|stringformat:\"-0.2f\"", + <<"{{ var|stringformat:\"-0.2f\" }}">>, [{var, "1"}], + <<"1.00">>}, + {"|stringformat:\"10.2f\"", + <<"{{ var|stringformat:\"10.2f\" }}">>, [{var, "1"}], + <<" 1.00">>}, + {"|stringformat:\"-10.2f\"", + <<"{{ var|stringformat:\"-10.2f\" }}">>, [{var, "1"}], + <<"1.00 ">>}, + {"|stringformat:\".2f\"", + <<"{{ var|stringformat:\".2f\" }}">>, [{var, "1"}], + <<"1.00">>}, + {"|stringformat:\"x\"", + <<"{{ var|stringformat:\"x\" }}">>, [{var, "90"}], + <<"5a">>}, + {"|stringformat:\"X\"", + <<"{{ var|stringformat:\"X\" }}">>, [{var, "90"}], + <<"5A">>}, + + {"|stringformat:\"o\"", + <<"{{ var|stringformat:\"o\" }}">>, [{var, "90"}], + <<"132">>}, + + {"|stringformat:\"e\"", + <<"{{ var|stringformat:\"e\" }}">>, [{var, "90"}], + <<"9.000000e+01">>}, + {"|stringformat:\"e\"", + <<"{{ var|stringformat:\"e\" }}">>, [{var, "90000000000"}], + <<"9.000000e+10">>}, + {"|stringformat:\"E\"", + <<"{{ var|stringformat:\"E\" }}">>, [{var, "90"}], + <<"9.000000E+01">>}, + {"|striptags", + <<"{{ var|striptags }}">>, [{var, "Joel a slug"}], + <<"Joel is a slug">>}, + {"|striptags", + <<"{{ var|striptags }}">>, [{var, "Joel a slug"}], + <<"Joel is a slug">>}, + {"|striptags", + <<"{{ var|striptags }}">>, [{var, "Check out http://www.djangoproject.com"}], + <<"Check out http://www.djangoproject.com">>}, + {"|time:\"H:i\"", + <<"{{ var|time:\"H:i\" }}">>, [{var, {{2010,12,1}, {10,11,12}} }], + <<"10:11">>}, + {"|time", + <<"{{ var|time }}">>, [{var, {{2010,12,1}, {10,11,12}} }], + <<"10:11 a.m.">>}, + {"|timesince:from_date", + <<"{{ from_date|timesince:conference_date }}">>, [{conference_date, {{2006,6,1},{8,0,0}} }, {from_date, {{2006,6,1},{0,0,0}} }], + <<"8 hours">>}, + {"|timesince:from_date", + <<"{{ from_date|timesince:conference_date }}">>, [{conference_date, {{2010,6,1},{8,0,0}} },{from_date, {{2006,6,1},{0,0,0}} }], + <<"4 years, 1 day">>}, % leap year + {"|timesince:from_date", + <<"{{ from_date|timesince:conference_date }}">>, [{conference_date, {{2006,7,15},{8,0,0}} },{from_date, {{2006,6,1},{0,0,0}} }], + <<"1 month, 2 weeks">>}, + {"|timeuntil:from_date", + <<"{{ conference_date|timeuntil:from_date }}">>, [{conference_date, {{2006,6,1},{8,0,0}} }, {from_date, {{2006,6,1},{0,0,0}} }], + <<"8 hours">>}, + {"|timeuntil:from_date", + <<"{{ conference_date|timeuntil:from_date }}">>, [{conference_date, {{2010,6,1},{8,0,0}} },{from_date, {{2006,6,1},{0,0,0}} }], + <<"4 years, 1 day">>}, + {"|timeuntil:from_date", + <<"{{ conference_date|timeuntil:from_date }}">>, [{conference_date, {{2006,7,15},{8,0,0}} },{from_date, {{2006,6,1},{0,0,0}} }], + <<"1 month, 2 weeks">>}, {"|title", <<"{{ \"my title case\"|title }}">>, [], <<"My Title Case">>}, + {"|title (pre-formatted)", + <<"{{ \"My Title Case\"|title }}">>, [], + <<"My Title Case">>}, {"|truncatewords:0", <<"{{ var1|truncatewords:0 }}">>, [{var1, "Empty Me"}], <<"">>}, @@ -479,9 +779,47 @@ tests() -> {"|urlencode", <<"{{ url|urlencode }}">>, [{url, "You #$*@!!"}], <<"You+%23%24%2A%40%21%21">>}, + {"|urlize", + <<"{{ var|urlize }}">>, [{var, "Check out www.djangoproject.com"}], + <<"Check out www.djangoproject.com">>}, + {"|urlize", + <<"{{ var|urlize }}">>, [{var, "Check out http://www.djangoproject.com"}], + <<"Check out http://www.djangoproject.com">>}, + {"|urlize", + <<"{{ var|urlize }}">>, [{var, "Check out \"http://www.djangoproject.com\""}], + <<"Check out \"http://www.djangoproject.com\"">>}, + {"|urlizetrunc:15", + <<"{{ var|urlizetrunc:15 }}">>, [{var, "Check out www.djangoproject.com"}], + <<"Check out www.djangopr...">>}, {"|wordcount", <<"{{ words|wordcount }}">>, [{words, "Why Hello There!"}], - <<"3">>} + <<"3">>}, + {"|wordwrap:2", + <<"{{ words|wordwrap:2 }}">>, [{words, "this is"}], + <<"this \nis">>}, + {"|wordwrap:100", + <<"{{ words|wordwrap:100 }}">>, [{words, "testing testing"}], + <<"testing testing">>}, + {"|wordwrap:10", + <<"{{ words|wordwrap:10 }}">>, [{words, ""}], + <<"">>}, + {"|wordwrap:1", + <<"{{ words|wordwrap:1 }}">>, [{words, "two"}], + <<"two">>}, + % yesno match: \?assert.*\( (.*), erlydtl_filters:(.*)\((.*), "(.*)"\)\) + % yesno replace: \t\t{"|$2:\"$4\"",\n\t\t\t\t\t <<"{{ var|$2:\"$4\" }}">>, [{var, $3}],\n\t\t\t\t\t<<$1>>} + {"|yesno:\"yeah,no,maybe\"", + <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, true}], + <<"yeah">>}, + {"|yesno:\"yeah,no,maybe\"", + <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, false}], + <<"no">>}, + {"|yesno:\"yeah,no\"", + <<"{{ var|yesno:\"yeah,no\" }}">>, [{var, undefined}], + <<"no">>}, + {"|yesno:\"yeah,no,maybe\"", + <<"{{ var|yesno:\"yeah,no,maybe\" }}">>, [{var, undefined}], + <<"maybe">>} ]}, {"filters_if", [ {"Filter if 1.1", @@ -556,35 +894,35 @@ tests() -> <<"baz">>} ]} ]. - + run_tests() -> io:format("Running unit tests...~n"), Failures = lists:foldl( fun({Group, Assertions}, GroupAcc) -> io:format(" Test group ~p...~n", [Group]), lists:foldl(fun - ({Name, DTL, Vars, Output}, Acc) -> + ({Name, DTL, Vars, Output}, Acc) -> process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []), Vars, none, Output, Acc, Group, Name); - ({Name, DTL, Vars, Dictionary, Output}, Acc) -> - process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []), + ({Name, DTL, Vars, Dictionary, Output}, Acc) -> + process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, []), Vars, Dictionary, Output, Acc, Group, Name); - ({Name, DTL, Vars, Dictionary, CompilerOpts, Output}, Acc) -> - process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, CompilerOpts), + ({Name, DTL, Vars, Dictionary, CompilerOpts, Output}, Acc) -> + process_unit_test(erlydtl:compile(DTL, erlydtl_running_test, CompilerOpts), Vars, Dictionary, Output, Acc, Group, Name) end, GroupAcc, Assertions) end, [], tests()), - + io:format("Unit test failures: ~p~n", [lists:reverse(Failures)]). - + process_unit_test(CompiledTemplate, Vars, Dictionary, Output,Acc, Group, Name) -> - case CompiledTemplate of + case CompiledTemplate of {ok, _} -> {ok, IOList} = erlydtl_running_test:render(Vars, Dictionary), {ok, IOListBin} = erlydtl_running_test:render(vars_to_binary(Vars), Dictionary), case {iolist_to_binary(IOList), iolist_to_binary(IOListBin)} of {Output, Output} -> - Acc; + Acc; {Output, Unexpected} -> [{Group, Name, 'binary', Unexpected, Output} | Acc]; {Unexpected, Output} -> @@ -596,8 +934,8 @@ process_unit_test(CompiledTemplate, Vars, Dictionary, Output,Acc, Group, Name) - Err -> [{Group, Name, Err} | Acc] end. - - + + vars_to_binary(Vars) when is_list(Vars) -> lists:map(fun ({Key, [H|_] = Value}) when is_tuple(H) -> @@ -609,7 +947,7 @@ vars_to_binary(Vars) when is_list(Vars) -> end, Vars); vars_to_binary(Vars) -> Vars. - + generate_test_date() -> {{Y,M,D}, _} = erlang:localtime(), MonthName = [ diff --git a/src/tests/gettext.erl b/tests/src/gettext.erl similarity index 100% rename from src/tests/gettext.erl rename to tests/src/gettext.erl diff --git a/src/tests/i18n/sources_parser_unittests.erl b/tests/src/sources_parser_unittests.erl similarity index 100% rename from src/tests/i18n/sources_parser_unittests.erl rename to tests/src/sources_parser_unittests.erl