From dc9187e8a4ee385bb94f6081309a9eee3aecd53c Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Fri, 6 Feb 2015 10:25:08 +0100 Subject: [PATCH 1/7] Build stanzas from XML snippets (just started) --- src/escalus_stanza.erl | 22 ++++++++++++++++++++++ test/escalus_stanza_SUITE.erl | 25 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 test/escalus_stanza_SUITE.erl diff --git a/src/escalus_stanza.erl b/src/escalus_stanza.erl index 0d185366..693a32a8 100644 --- a/src/escalus_stanza.erl +++ b/src/escalus_stanza.erl @@ -115,11 +115,15 @@ -export([remove_account/0]). +%% Stanzas from inline XML +-export([from_xml/1]). + -import(escalus_compat, [bin/1]). -include("escalus.hrl"). -include("escalus_xmlns.hrl"). -include("no_binary_to_integer.hrl"). +-include_lib("exml/include/exml.hrl"). -include_lib("exml/include/exml_stream.hrl"). %%-------------------------------------------------------------------- @@ -689,7 +693,25 @@ enable_carbons_el() -> #xmlel{name = <<"enable">>, attrs = [{<<"xmlns">>, ?NS_CARBONS_2}]}. +%%-------------------------------------------------------------------- +%% Stanzas from inline XML +%%-------------------------------------------------------------------- + +-spec from_xml(Snippet) -> Term when + Snippet :: string(), + Term :: xmlterm(). +from_xml(Snippet) -> + to_element(Snippet). + +%%-------------------------------------------------------------------- +%% Helpers for stanzas from XML +%%-------------------------------------------------------------------- +to_element(XMLSnippet) when is_binary(XMLSnippet) -> + {ok, El} = exml:parse(XMLSnippet), + El; +to_element(XMLSnippet) -> + to_element(iolist_to_binary(XMLSnippet)). %%-------------------------------------------------------------------- %% Helpers diff --git a/test/escalus_stanza_SUITE.erl b/test/escalus_stanza_SUITE.erl new file mode 100644 index 00000000..150a559d --- /dev/null +++ b/test/escalus_stanza_SUITE.erl @@ -0,0 +1,25 @@ +-module(escalus_stanza_SUITE). +-compile(export_all). + +-include_lib("exml/include/exml.hrl"). +-include_lib("escalus/include/escalus.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-define(eq(E, A), ?assertEqual(E, A)). + +all() -> + [sanity_check, + nullary_snippet_to_xmlel]. + +%% +%% Tests +%% + +sanity_check(_) -> + ok. + +nullary_snippet_to_xmlel(_) -> + M = escalus_stanza, + ?eq(#xmlel{name = <<"el">>}, M:from_xml("")), + ?eq(#xmlel{name = <<"el">>}, M:from_xml(<<"">>)). From 2ec45f58b1c0d6038b409379c81d4334515fc0e9 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Fri, 6 Feb 2015 10:40:39 +0100 Subject: [PATCH 2/7] Build stanza from parameterized snippet --- rebar.config | 3 +- src/escalus_stanza.erl | 68 ++++++++++++++++++++++++++++++----- test/escalus_stanza_SUITE.erl | 10 +++++- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/rebar.config b/rebar.config index 2a4ff013..3a12eab2 100644 --- a/rebar.config +++ b/rebar.config @@ -9,5 +9,6 @@ {base16, ".*", {git, "git://github.com/goj/base16.git", "ec420aa"}}, {fusco, ".*", {git, "git://github.com/esl/fusco.git", "0a428471"}}, {wsecli, ".*", {git, "git://github.com/esl/wsecli.git", "bf575f1d04"}}, - {meck, ".*", {git, "git://github.com/eproxus/meck.git", {tag, "0.8.2"}}} + {meck, ".*", {git, "git://github.com/eproxus/meck.git", {tag, "0.8.2"}}}, + {mustache, ".*", {git, "git://github.com/mojombo/mustache.erl.git", {branch, "master"}}} ]}. diff --git a/src/escalus_stanza.erl b/src/escalus_stanza.erl index 693a32a8..888ded18 100644 --- a/src/escalus_stanza.erl +++ b/src/escalus_stanza.erl @@ -116,7 +116,8 @@ -export([remove_account/0]). %% Stanzas from inline XML --export([from_xml/1]). +-export([from_template/2, + from_xml/1]). -import(escalus_compat, [bin/1]). @@ -126,6 +127,8 @@ -include_lib("exml/include/exml.hrl"). -include_lib("exml/include/exml_stream.hrl"). +-define(b2l(B), erlang:binary_to_list(B)). + %%-------------------------------------------------------------------- %% Stream - related functions %%-------------------------------------------------------------------- @@ -697,21 +700,70 @@ enable_carbons_el() -> %% Stanzas from inline XML %%-------------------------------------------------------------------- +%% @doc An xml_snippet() is a textual representation of XML, +%% possibly with formatting parameters (places where to insert substitutions). +%% It may be a string() or a binary(). +%% A parameterless snippet might look like: +%% +%% +%% +%% Snippet with formatting parameters will look like: +%% +%% +%% +%% Parameter names must be valid atoms, so if you want to use punctuation +%% use single quotes: +%% +%% +%% +-type xml_snippet() :: string() | binary(). + -spec from_xml(Snippet) -> Term when - Snippet :: string(), + Snippet :: xml_snippet(), Term :: xmlterm(). from_xml(Snippet) -> - to_element(Snippet). + from_template(Snippet, []). + +-type context() :: [{atom(), binary() | list() | xmlterm()}]. + +-spec from_template(Snippet, Ctx) -> Term when + Snippet :: xml_snippet(), + Ctx :: context(), + Term :: xmlterm(). +from_template(Snippet, Ctx) -> + xml_to_xmlterm(iolist_to_binary(render(Snippet, Ctx))). %%-------------------------------------------------------------------- %% Helpers for stanzas from XML %%-------------------------------------------------------------------- -to_element(XMLSnippet) when is_binary(XMLSnippet) -> - {ok, El} = exml:parse(XMLSnippet), - El; -to_element(XMLSnippet) -> - to_element(iolist_to_binary(XMLSnippet)). +%% @doc An xml() is a well-formed XML document. +%% No multiple top-level elements are allowed. +-type xml() :: binary(). + +-spec xml_to_xmlterm(XML) -> Term when + XML :: xml(), + Term :: xmlterm(). +xml_to_xmlterm(XML) when is_binary(XML) -> + {ok, Term} = exml:parse(XML), + Term. + +-spec render(Snippet, Ctx) -> Text when + Snippet :: xml_snippet(), + Ctx :: context(), + Text :: string(). +render(Snippet, Ctx) -> + mustache:render(xml_snippet_to_string(Snippet), + validate_context(Ctx)). + +xml_snippet_to_string(Snippet) when is_binary(Snippet) -> ?b2l(Snippet); +xml_snippet_to_string(Snippet) -> Snippet. + +validate_context(Ctx) -> + [ {Key, to_string(Value)} || {Key, Value} <- Ctx ]. + +to_string(E) when is_binary(E) -> ?b2l(E); +to_string(E) when is_list(E) -> E. %%-------------------------------------------------------------------- %% Helpers diff --git a/test/escalus_stanza_SUITE.erl b/test/escalus_stanza_SUITE.erl index 150a559d..c3a0b12b 100644 --- a/test/escalus_stanza_SUITE.erl +++ b/test/escalus_stanza_SUITE.erl @@ -10,7 +10,8 @@ all() -> [sanity_check, - nullary_snippet_to_xmlel]. + nullary_snippet_to_xmlel, + unary_snippet_to_xmlel]. %% %% Tests @@ -23,3 +24,10 @@ nullary_snippet_to_xmlel(_) -> M = escalus_stanza, ?eq(#xmlel{name = <<"el">>}, M:from_xml("")), ?eq(#xmlel{name = <<"el">>}, M:from_xml(<<"">>)). + +unary_snippet_to_xmlel(_) -> + M = escalus_stanza, + ?eq(#xmlel{name = <<"el">>, + attrs = [{<<"attr">>, <<"value">>}]}, + M:from_template("", + [{val, "value"}])). From 83b42bb5b08c86e65eeccd2f3ae05fd2c6786bbc Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Fri, 6 Feb 2015 11:32:33 +0100 Subject: [PATCH 3/7] Assert strings and binaries are interchangeable --- test/escalus_stanza_SUITE.erl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/escalus_stanza_SUITE.erl b/test/escalus_stanza_SUITE.erl index c3a0b12b..4e6a592f 100644 --- a/test/escalus_stanza_SUITE.erl +++ b/test/escalus_stanza_SUITE.erl @@ -11,7 +11,8 @@ all() -> [sanity_check, nullary_snippet_to_xmlel, - unary_snippet_to_xmlel]. + unary_snippet_to_xmlel, + type_matrix_accepted]. %% %% Tests @@ -31,3 +32,12 @@ unary_snippet_to_xmlel(_) -> attrs = [{<<"attr">>, <<"value">>}]}, M:from_template("", [{val, "value"}])). + +type_matrix_accepted(_) -> + M = escalus_stanza, + Example = #xmlel{name = <<"el">>, + attrs = [{<<"attr">>, <<"value">>}]}, + ?eq(Example, M:from_template("", [{val, "value"}])), + ?eq(Example, M:from_template(<<"">>, [{val, "value"}])), + ?eq(Example, M:from_template(<<"">>, [{val, <<"value">>}])), + ?eq(Example, M:from_template("", [{val, <<"value">>}])). From e2de7541a6b7f80d32c619268040288d886bc1d1 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Fri, 6 Feb 2015 11:58:50 +0100 Subject: [PATCH 4/7] Accept concrete terms (not only text) as template arguments --- src/escalus_stanza.erl | 12 ++++++++++++ test/escalus_stanza_SUITE.erl | 10 +++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/escalus_stanza.erl b/src/escalus_stanza.erl index 888ded18..ab56193c 100644 --- a/src/escalus_stanza.erl +++ b/src/escalus_stanza.erl @@ -128,6 +128,7 @@ -include_lib("exml/include/exml_stream.hrl"). -define(b2l(B), erlang:binary_to_list(B)). +-define(io2b(IOList), erlang:iolist_to_binary(IOList)). %%-------------------------------------------------------------------- %% Stream - related functions @@ -716,6 +717,16 @@ enable_carbons_el() -> %% %% %% +%% If the argument you pass as the parameter value is an xmlterm() +%% then use triple brackets at the parameter expansion site. +%% Otherwise, the argument term will end up HTML-encoded +%% after expansion. +%% +%% +%% {{{argument_will_be_xmlterm}}} +%% +%% +%% Refer to escalus_stanza_SUITE for usage examples. -type xml_snippet() :: string() | binary(). -spec from_xml(Snippet) -> Term when @@ -762,6 +773,7 @@ xml_snippet_to_string(Snippet) -> Snippet. validate_context(Ctx) -> [ {Key, to_string(Value)} || {Key, Value} <- Ctx ]. +to_string(E = #xmlel{}) -> ?b2l(?io2b(exml:to_iolist(E))); to_string(E) when is_binary(E) -> ?b2l(E); to_string(E) when is_list(E) -> E. diff --git a/test/escalus_stanza_SUITE.erl b/test/escalus_stanza_SUITE.erl index 4e6a592f..b1a29dd0 100644 --- a/test/escalus_stanza_SUITE.erl +++ b/test/escalus_stanza_SUITE.erl @@ -12,7 +12,8 @@ all() -> [sanity_check, nullary_snippet_to_xmlel, unary_snippet_to_xmlel, - type_matrix_accepted]. + type_matrix_accepted, + term_as_argument]. %% %% Tests @@ -41,3 +42,10 @@ type_matrix_accepted(_) -> ?eq(Example, M:from_template(<<"">>, [{val, "value"}])), ?eq(Example, M:from_template(<<"">>, [{val, <<"value">>}])), ?eq(Example, M:from_template("", [{val, <<"value">>}])). + +term_as_argument(_) -> + M = escalus_stanza, + Inner = #xmlel{name = <<"inner">>}, + Example = #xmlel{name = <<"outer">>, + children = [Inner]}, + ?eq(Example, M:from_template("{{{inner}}}", [{inner, Inner}])). From 504c6e3679f33fdd50c47c02dcdcae3be308294a Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Fri, 6 Feb 2015 14:08:03 +0100 Subject: [PATCH 5/7] Accept xmlattr()s as template arguments --- src/escalus_stanza.erl | 17 ++++++++++++----- test/escalus_stanza_SUITE.erl | 10 +++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/escalus_stanza.erl b/src/escalus_stanza.erl index ab56193c..31230ae8 100644 --- a/src/escalus_stanza.erl +++ b/src/escalus_stanza.erl @@ -726,6 +726,10 @@ enable_carbons_el() -> %% {{{argument_will_be_xmlterm}}} %% %% +%% It's also possible to substitute whole attributes, not just their values: +%% +%% +%% %% Refer to escalus_stanza_SUITE for usage examples. -type xml_snippet() :: string() | binary(). @@ -771,11 +775,14 @@ xml_snippet_to_string(Snippet) when is_binary(Snippet) -> ?b2l(Snippet); xml_snippet_to_string(Snippet) -> Snippet. validate_context(Ctx) -> - [ {Key, to_string(Value)} || {Key, Value} <- Ctx ]. - -to_string(E = #xmlel{}) -> ?b2l(?io2b(exml:to_iolist(E))); -to_string(E) when is_binary(E) -> ?b2l(E); -to_string(E) when is_list(E) -> E. + [ {Key, argument_to_string(Value)} || {Key, Value} <- Ctx ]. + +argument_to_string({Name, Value}) -> + ?b2l(?io2b([Name, "='", exml:escape_attr(Value), "'"])); +argument_to_string(E = #xmlel{}) -> + ?b2l(?io2b(exml:to_iolist(E))); +argument_to_string(E) when is_binary(E) -> ?b2l(E); +argument_to_string(E) when is_list(E) -> E. %%-------------------------------------------------------------------- %% Helpers diff --git a/test/escalus_stanza_SUITE.erl b/test/escalus_stanza_SUITE.erl index b1a29dd0..f1b1f702 100644 --- a/test/escalus_stanza_SUITE.erl +++ b/test/escalus_stanza_SUITE.erl @@ -13,7 +13,8 @@ all() -> nullary_snippet_to_xmlel, unary_snippet_to_xmlel, type_matrix_accepted, - term_as_argument]. + term_as_argument, + attribute_as_argument]. %% %% Tests @@ -49,3 +50,10 @@ term_as_argument(_) -> Example = #xmlel{name = <<"outer">>, children = [Inner]}, ?eq(Example, M:from_template("{{{inner}}}", [{inner, Inner}])). + +attribute_as_argument(_) -> + M = escalus_stanza, + Attr = {<<"name">>, <<"value">>}, + Example = #xmlel{name = <<"el">>, + attrs = [Attr]}, + ?eq(Example, M:from_template("", [{attr, Attr}])). From acf0e176f73ac93d4bde06e78eb32dcfbba84c14 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Fri, 6 Feb 2015 14:25:06 +0100 Subject: [PATCH 6/7] Drop empty sanity_check testcase [skip ci] --- test/escalus_stanza_SUITE.erl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/escalus_stanza_SUITE.erl b/test/escalus_stanza_SUITE.erl index f1b1f702..05510322 100644 --- a/test/escalus_stanza_SUITE.erl +++ b/test/escalus_stanza_SUITE.erl @@ -9,8 +9,7 @@ -define(eq(E, A), ?assertEqual(E, A)). all() -> - [sanity_check, - nullary_snippet_to_xmlel, + [nullary_snippet_to_xmlel, unary_snippet_to_xmlel, type_matrix_accepted, term_as_argument, @@ -20,9 +19,6 @@ all() -> %% Tests %% -sanity_check(_) -> - ok. - nullary_snippet_to_xmlel(_) -> M = escalus_stanza, ?eq(#xmlel{name = <<"el">>}, M:from_xml("")), From 6ab5b45f9225c05d4a98ee01c0ea4f7b885ac71b Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Fri, 6 Feb 2015 14:26:46 +0100 Subject: [PATCH 7/7] Freeze mustache [skip ci] --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 3a12eab2..a9f09c14 100644 --- a/rebar.config +++ b/rebar.config @@ -10,5 +10,5 @@ {fusco, ".*", {git, "git://github.com/esl/fusco.git", "0a428471"}}, {wsecli, ".*", {git, "git://github.com/esl/wsecli.git", "bf575f1d04"}}, {meck, ".*", {git, "git://github.com/eproxus/meck.git", {tag, "0.8.2"}}}, - {mustache, ".*", {git, "git://github.com/mojombo/mustache.erl.git", {branch, "master"}}} + {mustache, ".*", {git, "git://github.com/mojombo/mustache.erl.git", "d0246fe"}} ]}.