Просмотр исходного кода

Integrate new filters and tests from dgulino

Restructured the file layout somewhat.
Evan Miller 14 лет назад
Родитель
Сommit
ae6bf4b036
66 измененных файлов с 921 добавлено и 396 удалено
  1. 4 6
      Emakefile
  2. 3 3
      Makefile
  3. 1 1
      README.markdown
  4. 1 5
      ebin/erlydtl.app
  5. 0 0
      src/erlydtl.erl
  6. 17 20
      src/erlydtl_compiler.erl
  7. 0 0
      src/erlydtl_deps.erl
  8. 409 303
      src/erlydtl_filters.erl
  9. 0 0
      src/erlydtl_i18n.erl
  10. 1 0
      src/erlydtl_parser.yrl
  11. 0 0
      src/erlydtl_runtime.erl
  12. 0 0
      src/erlydtl_scanner.erl
  13. 0 0
      src/filter_lib/erlydtl_dateformat.erl
  14. 13 17
      src/filter_lib/erlydtl_slice.erl
  15. 0 0
      src/i18n/Makefile
  16. 0 0
      src/i18n/i18n_manager.erl
  17. 0 0
      src/i18n/po_generator.erl
  18. 0 0
      src/i18n/po_scanner.erl
  19. 0 0
      src/i18n/sources_parser.erl
  20. 0 0
      tests/input/autoescape
  21. 0 0
      tests/input/base
  22. 0 0
      tests/input/comment
  23. 0 0
      tests/input/custom_call
  24. 0 0
      tests/input/custom_tag
  25. 0 0
      tests/input/cycle
  26. 0 0
      tests/input/extends
  27. 0 0
      tests/input/extends_path
  28. 0 0
      tests/input/extends_path2
  29. 0 0
      tests/input/filters
  30. 0 0
      tests/input/for
  31. 0 0
      tests/input/for_list
  32. 0 0
      tests/input/for_list_preset
  33. 0 0
      tests/input/for_preset
  34. 0 0
      tests/input/for_records
  35. 0 0
      tests/input/for_records_preset
  36. 0 0
      tests/input/for_tuple
  37. 0 0
      tests/input/if
  38. 0 0
      tests/input/if_preset
  39. 0 0
      tests/input/ifequal
  40. 0 0
      tests/input/ifequal_preset
  41. 0 0
      tests/input/ifnotequal
  42. 0 0
      tests/input/ifnotequal_preset
  43. 0 0
      tests/input/include
  44. 0 0
      tests/input/include.html
  45. 0 0
      tests/input/include_path
  46. 0 0
      tests/input/include_template
  47. 0 0
      tests/input/now
  48. 0 0
      tests/input/path1/base1
  49. 0 0
      tests/input/path1/base2
  50. 0 0
      tests/input/path1/include1
  51. 0 0
      tests/input/path1/template1
  52. 0 0
      tests/input/path2/base2
  53. 0 0
      tests/input/path2/template2
  54. 0 0
      tests/input/trans
  55. 0 0
      tests/input/var
  56. 0 0
      tests/input/var_preset
  57. 49 0
      tests/py/erlydtl_python_test.py
  58. BIN
      tests/py/erlydtl_python_test.pyc
  59. 29 0
      tests/py/python_dtl_setup.txt
  60. 15 0
      tests/py/run_erl_node.sh
  61. 0 0
      tests/src/erlydtl_dateformat_tests.erl
  62. 0 0
      tests/src/erlydtl_example_variable_storage.erl
  63. 2 2
      tests/src/erlydtl_functional_tests.erl
  64. 377 39
      tests/src/erlydtl_unittests.erl
  65. 0 0
      tests/src/gettext.erl
  66. 0 0
      tests/src/sources_parser_unittests.erl

+ 4 - 6
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"}]}.

+ 3 - 3
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/*

+ 1 - 1
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.

+ 1 - 5
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,

+ 0 - 0
src/erlydtl/erlydtl.erl → src/erlydtl.erl


+ 17 - 20
src/erlydtl/erlydtl_compiler.erl → 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]).
 

+ 0 - 0
src/erlydtl/erlydtl_deps.erl → src/erlydtl_deps.erl


+ 409 - 303
src/erlydtl/erlydtl_filters.erl → src/erlydtl_filters.erl

@@ -3,9 +3,9 @@
 %%% @author    Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
 %%% @author    Evan Miller <emmiller@gmail.com>
 %%% @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(<<Byte:8/integer, Binary/binary>>) 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("</?(~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));
@@ -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 <ul> 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],
+%%                 "<ul>" ++ unordered_list(Rest,Return) + "</ul>";
+%%             false ->
+%%                 Return = [First | Acc],
+%%                 "<li>" ++ unordered_list(Rest,Return) + "</li>"
+%%         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 <BR/> 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) ->
     <<Hi:4, Lo:4>> = <<C>>,
     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("<a href=\"~s\" rel=\"nofollow\">~s</a>",[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;

+ 0 - 0
src/erlydtl/erlydtl_i18n.erl → src/erlydtl_i18n.erl


+ 1 - 0
src/erlydtl/erlydtl_parser.yrl → 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'.

+ 0 - 0
src/erlydtl/erlydtl_runtime.erl → src/erlydtl_runtime.erl


+ 0 - 0
src/erlydtl/erlydtl_scanner.erl → src/erlydtl_scanner.erl


+ 0 - 0
src/erlydtl/filter_lib/erlydtl_dateformat.erl → src/filter_lib/erlydtl_dateformat.erl


+ 13 - 17
src/erlydtl/filter_lib/erlydtl_slice.erl → 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 ->

+ 0 - 0
src/erlydtl/i18n/Makefile → src/i18n/Makefile


+ 0 - 0
src/erlydtl/i18n/i18n_manager.erl → src/i18n/i18n_manager.erl


+ 0 - 0
src/erlydtl/i18n/po_generator.erl → src/i18n/po_generator.erl


+ 0 - 0
src/erlydtl/i18n/po_scanner.erl → src/i18n/po_scanner.erl


+ 0 - 0
src/erlydtl/i18n/sources_parser.erl → src/i18n/sources_parser.erl


+ 0 - 0
examples/docroot/autoescape → tests/input/autoescape


+ 0 - 0
examples/docroot/base → tests/input/base


+ 0 - 0
examples/docroot/comment → tests/input/comment


+ 0 - 0
examples/docroot/custom_call → tests/input/custom_call


+ 0 - 0
examples/docroot/custom_tag → tests/input/custom_tag


+ 0 - 0
examples/docroot/cycle → tests/input/cycle


+ 0 - 0
examples/docroot/extends → tests/input/extends


+ 0 - 0
examples/docroot/extends_path → tests/input/extends_path


+ 0 - 0
examples/docroot/extends_path2 → tests/input/extends_path2


+ 0 - 0
examples/docroot/filters → tests/input/filters


+ 0 - 0
examples/docroot/for → tests/input/for


+ 0 - 0
examples/docroot/for_list → tests/input/for_list


+ 0 - 0
examples/docroot/for_list_preset → tests/input/for_list_preset


+ 0 - 0
examples/docroot/for_preset → tests/input/for_preset


+ 0 - 0
examples/docroot/for_records → tests/input/for_records


+ 0 - 0
examples/docroot/for_records_preset → tests/input/for_records_preset


+ 0 - 0
examples/docroot/for_tuple → tests/input/for_tuple


+ 0 - 0
examples/docroot/if → tests/input/if


+ 0 - 0
examples/docroot/if_preset → tests/input/if_preset


+ 0 - 0
examples/docroot/ifequal → tests/input/ifequal


+ 0 - 0
examples/docroot/ifequal_preset → tests/input/ifequal_preset


+ 0 - 0
examples/docroot/ifnotequal → tests/input/ifnotequal


+ 0 - 0
examples/docroot/ifnotequal_preset → tests/input/ifnotequal_preset


+ 0 - 0
examples/docroot/include → tests/input/include


+ 0 - 0
examples/docroot/include.html → tests/input/include.html


+ 0 - 0
examples/docroot/include_path → tests/input/include_path


+ 0 - 0
examples/docroot/include_template → tests/input/include_template


+ 0 - 0
examples/docroot/now → tests/input/now


+ 0 - 0
examples/docroot/path1/base1 → tests/input/path1/base1


+ 0 - 0
examples/docroot/path1/base2 → tests/input/path1/base2


+ 0 - 0
examples/docroot/path1/include1 → tests/input/path1/include1


+ 0 - 0
examples/docroot/path1/template1 → tests/input/path1/template1


+ 0 - 0
examples/docroot/path2/base2 → tests/input/path2/base2


+ 0 - 0
examples/docroot/path2/template2 → tests/input/path2/template2


+ 0 - 0
examples/docroot/trans → tests/input/trans


+ 0 - 0
examples/docroot/var → tests/input/var


+ 0 - 0
examples/docroot/var_preset → tests/input/var_preset


+ 49 - 0
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))
+    

BIN
tests/py/erlydtl_python_test.pyc


+ 29 - 0
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)

+ 15 - 0
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/

+ 0 - 0
src/tests/erlydtl_dateformat_tests.erl → tests/src/erlydtl_dateformat_tests.erl


+ 0 - 0
src/tests/erlydtl_example_variable_storage.erl → tests/src/erlydtl_example_variable_storage.erl


+ 2 - 2
src/tests/erlydtl_functional_tests.erl → 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"]).

+ 377 - 39
src/tests/erlydtl_unittests.erl → 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 &amp; 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 &amp; Jerry&#039;s &lt;=&gt; &quot;The World&#039;s Best Ice Cream&quot;">>},
@@ -437,33 +450,320 @@ tests() ->
                 {"|length",
                     <<"{{ var1|length }}">>, [{var1, "antidisestablishmentarianism"}],
                     <<"28">>},
+                {"|linebreaks",
+                    <<"{{ var1|linebreaks }}">>, [{var1, "Joel\nis a slug"}],
+                    <<"<p>Joel<br />is a slug</p>">>},               
                 {"|linebreaksbr",
                     <<"{{ var1|linebreaksbr }}">>, [{var1, "One\nTwo\n\nThree\n\n\n"}],
                     <<"One<br />Two<br /><br />Three<br /><br /><br />">>},
                 {"|linebreaksbr",
                     <<"{{ \"One\\nTwo\\n\\nThree\\n\\n\\n\"|linebreaksbr }}">>, [],
-                    <<"One<br />Two<br /><br />Three<br /><br /><br />">>},
+                    <<"One<br />Two<br /><br />Three<br /><br /><br />">>},             
+                {"|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, "<B>Joel</B> <button>is</button> a <span>slug</span>"}],
+                    <<"<B>Joel</B> <button>is</button> 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, "<b>Joel</b> <button>is</button> a <span>slug</span>"}],
+                    <<"Joel is a slug">>},
+                {"|striptags",
+                     <<"{{ var|striptags }}">>, [{var, "<B>Joel</B> <button>is</button> a <span>slug</Span>"}],
+                    <<"Joel is a slug">>},
+                {"|striptags",
+                     <<"{{ var|striptags }}">>, [{var, "Check out <a href=\"http://www.djangoproject.com\" rel=\"nofollow\">http://www.djangoproject.com</a>"}],
+                    <<"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 <a href=\"http://www.djangoproject.com\" rel=\"nofollow\">www.djangoproject.com</a>">>},
+                {"|urlize",    
+                    <<"{{ var|urlize }}">>, [{var, "Check out http://www.djangoproject.com"}],
+                    <<"Check out <a href=\"http://www.djangoproject.com\" rel=\"nofollow\">http://www.djangoproject.com</a>">>},
+                {"|urlize",    
+                    <<"{{ var|urlize }}">>, [{var, "Check out \"http://www.djangoproject.com\""}],
+                    <<"Check out \"<a href=\"http://www.djangoproject.com\" rel=\"nofollow\">http://www.djangoproject.com</a>\"">>},
+                {"|urlizetrunc:15",    
+                    <<"{{ var|urlizetrunc:15 }}">>, [{var, "Check out www.djangoproject.com"}],
+                    <<"Check out <a href=\"http://www.djangoproject.com\" rel=\"nofollow\">www.djangopr...</a>">>},    
                 {"|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 = [

+ 0 - 0
src/tests/gettext.erl → tests/src/gettext.erl


+ 0 - 0
src/tests/i18n/sources_parser_unittests.erl → tests/src/sources_parser_unittests.erl