Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Move to fsm implementation

  • Loading branch information...
commit 4e7c8a53e1bda8893556ddf69c991900bff7bea7 1 parent 24a0451
@hyperthunk authored
View
4 .travis.yml
@@ -1,5 +1,5 @@
language: erlang
otp_release:
- R14B01
-before_script: "./rebar get-deps compile -v"
-script: "./rebar skip_deps=true eunit qc -v"
+before_script: "./rebar get-deps compile"
+script: "./rebar skip_deps=true eunit qc -v"
View
56 include/xml_writer.hrl
@@ -0,0 +1,56 @@
+%% -----------------------------------------------------------------------------
+%% Copyright (c) 2002-2011 Tim Watson (watson.timothy@gmail.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -----------------------------------------------------------------------------
+
+-type name() :: atom().
+-type write_function() :: fun((iodata()) -> 'ok' | {'error', term()}).
+-opaque writer() :: pid() | atom().
+
+%% TODO: it seems there is no way to state that element_name must be non-empty!
+-type element_name() :: iodata().
+-type ns_name() :: iodata().
+
+-record(stack_frame, {
+ ns_name :: ns_name(),
+ local_name :: element_name(),
+ has_attributes :: boolean(),
+ has_children :: boolean()
+}).
+
+-record(writer, {
+ ns = [] :: list(tuple(string(), string())),
+ ns_stack = [] :: list(string()),
+ stack = [] :: [#stack_frame{}],
+ quote = <<"\"">> :: binary(),
+ prettyprint = false :: boolean(),
+ indent = <<"\t">> :: binary(),
+ newline = <<"\n">> :: binary(),
+ encoding :: atom(),
+ last_error :: term(),
+ %escapes = [
+ % {<<"\"">>,
+ % {binary:compile_pattern(<<"\"">>),
+ % <<"\\\"">>}}
+ %]
+ write :: write_function(),
+ format,
+ close
+}).
View
2  rebar.config
@@ -10,7 +10,7 @@
{cover_enabled, true}.
{cover_print_enabled, true}.
-{erl_opts, [warnings_as_errors]}.
+% {erl_opts, [warnings_as_errors]}.
{plugins, [rebar_dist_plugin]}.
View
276 src/xml_writer.erl
@@ -1,8 +1,5 @@
%% -----------------------------------------------------------------------------
-%%
-%% xml_writer
-%%
-%% Copyright (c) 2011 Tim Watson (watson.timothy@gmail.com)
+%% Copyright (c) 2002-2011 Tim Watson (watson.timothy@gmail.com)
%%
%% Permission is hereby granted, free of charge, to any person obtaining a copy
%% of this software and associated documentation files (the "Software"), to deal
@@ -22,194 +19,95 @@
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.
%% -----------------------------------------------------------------------------
-%% @author Tim Watson [http://hyperthunk.wordpress.com]
-%% @copyright (c) Tim Watson, 2011
-%% @since: August 2011
+-module(xml_writer).
+-behaviour(gen_fsm).
+
+%% API exports
+-export([new/1, new/2, close/1]).
+-export([write_value/2, start_element/2, end_element/1]).
+
+%% gen_fsm state-name exports
+-export([waiting_for_input/3, element_started/3]).
+
+%% gen_fsm behaviour exports
+-export([init/1,
+ handle_sync_event/4,
+ terminate/3,
+ handle_event/3,
+ handle_info/3,
+ code_change/4]).
+
+-include("xml_writer.hrl").
+
%%
-%% @doc XML Serialiser
+%% API
%%
-%% This plugin allows you to generate an XML document using a simple,
-%% dom-like API.
-%% -----------------------------------------------------------------------------
--module(xml_writer).
--compile(export_all).
-
--record(ctx, {
- ns = [] :: list(tuple(string(), string())),
- ns_stack = [] :: list(string()),
- stack = [] :: [tuple(boolean(), tuple(string(), string()))],
- quote = <<"\"">> :: binary(),
- prettyprint = false :: boolean(),
- indent = <<"\t">> :: binary(),
- newline = <<"\n">> :: binary(),
- encoding :: atom(),
- %escapes = [
- % {<<"\"">>,
- % {binary:compile_pattern(<<"\"">>),
- % <<"\\\"">>}}
- %]
- write,
- format,
- close
-}).
-
--define(OPEN_BRACE, <<"<">>).
--define(FWD_SLASH, <<"/">>).
--define(CLOSE_BRACE, <<">">>).
--define(SPACE, <<" ">>).
-
--define(OPEN_ELEM(N), [?OPEN_BRACE, N, ?CLOSE_BRACE]).
--define(CLOSE_ELEM(N), [?OPEN_BRACE, ?FWD_SLASH, N, ?CLOSE_BRACE]).
-
-file_writer(Path) ->
- file_writer(Path, [binary, append]).
-
-file_writer(Path, Modes) ->
- {ok, IoDevice} = file:open(Path, Modes),
- #ctx{ write = fun(X) -> file:write(IoDevice, X) end,
- format = fun(M, A, W) -> io:format(IoDevice, M, A), W end,
- close = fun(_) -> file:close(IoDevice) end }.
+-spec new(name(), write_function()) -> writer().
+new(Name, WriteFun) ->
+ {ok, _Pid} = gen_fsm:start({local, Name}, ?MODULE, [WriteFun], []),
+ Name.
+
+-spec new(write_function()) -> writer().
new(WriteFun) ->
- #ctx{ write=WriteFun }.
-
-new(RootElement, WriteFun) ->
- start(RootElement, new(WriteFun)).
-
-set_option(prettyprint, OnOff, Writer) ->
- Writer#ctx{ prettyprint=OnOff }.
-
-add_namespace(NS, NSUri, Writer=#ctx{ stack=[], ns_stack=NSStack, ns=NSMap }) ->
- Writer#ctx{
- ns=lists:keystore(NS, 1, NSMap, {NS, NSUri}),
- ns_stack=[NS|NSStack] }.
-
-start(ElementName, Writer) ->
- start_element(ElementName, Writer).
-
-close(Writer=#ctx{ close=Close, stack=[] }) ->
- case Close of
- undefined -> ok;
- CloseFun when is_function(CloseFun) ->
- CloseFun(Writer)
- end;
-close(Writer=#ctx{ stack=[_|_]}) ->
- close(end_element(Writer)).
-
-with_element(Name, Writer, Fun) ->
- with_element(none, Name, Writer, Fun).
-
-with_element(NS, Name, Writer, Fun) ->
- Writer2 = start_element(NS, Name, Writer),
- Writer3 = Fun(Writer2),
- close(Writer3).
-
-write_node(Name, Writer) ->
- write_node(none, Name, Writer).
-
-write_node(NS, Name, Writer) ->
- end_element(start_element(NS, Name, Writer)).
-
-write_attributes(AttributeList, Writer) ->
- lists:foldl(fun({Name, Value}, XMLWriter) ->
- write_attribute(Name, Value, XMLWriter)
- end, Writer, AttributeList).
-
-write_attribute(Name, Value, Writer) ->
- write_attribute(none, Name, Value, Writer).
-
-write_attribute(NS, Name, Value, Writer=#ctx{ quote=Quot }) ->
- write([?SPACE, qname(NS, Name), <<"=">>, Quot, Value, Quot], Writer).
-
-write_value(_, #ctx{ stack=[] }) ->
- throw({error, no_root_element});
-write_value(Value, Writer) ->
- write(Value, close_current_node(Writer)).
-
-write_child(Name, Writer) ->
- write_child(none, Name, Writer).
-
-write_child(NS, Name, Writer) ->
- start_element(NS, Name, Writer).
-
-write_sibling(Name, Writer) ->
- write_sibling(none, Name, Writer).
-
-write_sibling(NS, Name, Writer) ->
- Writer2 = end_element(Writer),
- start_element(NS, Name, Writer2).
-
-start_element(Name, Writer) ->
- start_element(none, Name, Writer).
-
-start_element(NS, Name, Writer=#ctx{ ns=NSMap, ns_stack=NSStack }) ->
- {Writer2, NodeName} =
- push({NS, Name}, close_current_node(Writer)),
- WithElem = write(NodeName, fun start_elem/2, Writer2),
- case NSStack of
- [] -> WithElem;
- [_|_] ->
- XmlnsAtt =
- [ setelement(1, lists:keyfind(NSEntry, 1, NSMap),
- "xmlns:" ++ NSEntry) || NSEntry <- NSStack ],
- write_attributes(XmlnsAtt, WithElem)
- end.
+ {ok, Pid} = gen_fsm:start(?MODULE, [WriteFun], []),
+ Pid.
+
+- spec close(writer()) -> term().
+close(Writer) ->
+ gen_fsm:sync_send_all_state_event(Writer, stop).
+-spec write_value(writer(), iodata()) -> 'ok' | {'error', term()}.
+write_value(Writer, Value) ->
+ gen_fsm:sync_send_event(Writer, {write_value, Value}).
+
+-spec start_element(writer(), element_name()) -> ok.
+start_element(Writer, ElementName) ->
+ gen_fsm:sync_send_event(Writer, {start_element, ElementName}).
+
+-spec end_element(writer()) -> 'ok'.
end_element(Writer) ->
- {Writer2, NodeName} = pop(Writer),
- write(NodeName, fun end_elem/2, Writer2).
-
-push(Scope={NS, N}, W=#ctx{ stack=Stack }) ->
- W2 = W#ctx{ stack=[{false, Scope}|Stack] },
- %% io:format("Pushing ~p~n", [N]),
- {W2, qname(NS, N)}.
-
-pop(#ctx{ stack=[] }) ->
- throw({error, empty_stack});
-pop(W=#ctx{ stack=[{false, _}|_] }) ->
- pop(close_current_node(W));
-pop(W=#ctx{ stack=[{true, {NS, N}}|T] }) ->
- {W#ctx{ stack=T }, qname(NS, N)}.
-
-close_current_node(W=#ctx{ stack=[] }) ->
- W;
-close_current_node(W=#ctx{ stack=[{true, _}|_] }) ->
- W;
-close_current_node(W=#ctx{ write=WF, stack=[{false, Node}|Stack] }) ->
- WF(?CLOSE_BRACE),
- W#ctx{ stack=[{true, Node}|Stack] }.
-
-qname(none, Name) ->
- Name;
-qname(NS, Name) ->
- [NS, <<":">>, Name].
-
-format(Msg, Args, Writer=#ctx{ format=undefined }) ->
- write(io_lib:format(Msg, Args), Writer);
-format(Msg, Args, Writer=#ctx{ format=Format }) when is_function(Format, 2) ->
- write(Format(Msg, Args), Writer);
-format(Msg, Args, Writer=#ctx{ format=Format }) when is_function(Format, 3) ->
- Format(Msg, Args),
- Writer.
-
-write(Data, Writer=#ctx{ write=WF }) ->
- WF(encode(Data, Writer)), Writer.
-
-write(Data, Producer, Writer=#ctx{ write=WF }) ->
- WF(Producer(encode(Data, Writer), Writer)), Writer.
-
-encode(Data, #ctx{ encoding=undefined }) when is_atom(Data) ->
- atom_to_binary(Data, utf8);
-encode(Data, #ctx{ encoding=Encoding }) when is_atom(Data) ->
- atom_to_binary(Data, Encoding);
-encode(Data, #ctx{ encoding=_ }) -> %% when is_binary(Data) ->
- Data. %% TODO: deal with in/out encoding requirements
- %% HINT: maybe get this from the Content Type and Disposition headers
-
-start_elem(NodeName, #ctx{ prettyprint=false }) ->
- [?OPEN_BRACE, NodeName];
-start_elem(NodeName, #ctx{ stack=S, prettyprint=true, indent=I, newline=NL }) ->
- [NL, lists:duplicate(length(S), I), ?OPEN_BRACE, NodeName].
-
-end_elem(NodeName, _) ->
- [?OPEN_BRACE, ?FWD_SLASH, NodeName, ?CLOSE_BRACE].
+ gen_fsm:sync_send_event(Writer, end_element).
+
+%%
+%% gen_fsm callbacks
+%%
+
+init([Writer]) ->
+ {ok, waiting_for_input, #writer{ write=Writer }}.
+
+waiting_for_input({write_value, _}, From, W=#writer{ stack=[] }) ->
+ {reply, {error, no_root_node}, waiting_for_input, W};
+waiting_for_input({start_element, ElementName},
+ From, W=#writer{ stack=Stack }) ->
+ gen_fsm:reply(From, ok),
+ {next_state, element_started,
+ W#writer{ stack=[#stack_frame{ local_name=ElementName }|Stack] }}.
+
+element_started(end_element, _From, Writer) ->
+ {reply, ok, waiting_for_input, pop(Writer)}.
+
+handle_sync_event(stop, _From, _StateName, _StateData) ->
+ {stop, normal, ok, []}.
+
+terminate(_Reason, _StateName, _StateData) ->
+ ok.
+
+handle_event(_Event, StateName, StateData) ->
+ {next_state, StateName, StateData}.
+
+handle_info(_Info, _StateName, _StateData) ->
+ ok.
+
+code_change(_OldVsn, StateName, StateData, _Extra) ->
+ {ok, StateName, StateData}.
+
+%%
+%% Internal API
+%%
+
+pop(Writer=#writer{ stack=[] }) ->
+ Writer;
+pop(Writer=#writer{ stack=[_|Rest] }) ->
+ Writer#writer{ stack=Rest }.
+
View
218 src/xml_writer_old.bak
@@ -0,0 +1,218 @@
+%% -----------------------------------------------------------------------------
+%%
+%% xml_writer
+%%
+%% Copyright (c) 2011 Tim Watson (watson.timothy@gmail.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -----------------------------------------------------------------------------
+%% @author Tim Watson [http://hyperthunk.wordpress.com]
+%% @copyright (c) Tim Watson, 2011
+%% @since: August 2011
+%%
+%% @doc XML Serialiser
+%%
+%% This plugin allows you to generate an XML document using a simple,
+%% dom-like API.
+%% -----------------------------------------------------------------------------
+-module(xml_writer).
+-compile(export_all).
+
+-record(ctx, {
+ ns = [] :: list(tuple(string(), string())),
+ ns_stack = [] :: list(string()),
+ stack = [] :: [tuple(boolean(), tuple(string(), string()))],
+ quote = <<"\"">> :: binary(),
+ prettyprint = false :: boolean(),
+ indent = <<"\t">> :: binary(),
+ newline = <<"\n">> :: binary(),
+ encoding :: atom(),
+ %escapes = [
+ % {<<"\"">>,
+ % {binary:compile_pattern(<<"\"">>),
+ % <<"\\\"">>}}
+ %]
+ write,
+ format,
+ close
+}).
+
+-define(OPEN_BRACE, <<"<">>).
+-define(FWD_SLASH, <<"/">>).
+-define(CLOSE_BRACE, <<">">>).
+-define(SPACE, <<" ">>).
+
+-define(OPEN_ELEM(N), [?OPEN_BRACE, N, ?CLOSE_BRACE]).
+-define(CLOSE_ELEM(N), [?OPEN_BRACE, ?FWD_SLASH, N, ?CLOSE_BRACE]).
+
+file_writer(Path) ->
+ file_writer(Path, [binary, append]).
+
+file_writer(Path, Modes) ->
+ {ok, IoDevice} = file:open(Path, Modes),
+ #ctx{ write = fun(X) -> file:write(IoDevice, X) end,
+ format = fun(M, A, W) -> io:format(IoDevice, M, A), W end,
+ close = fun(_) -> file:close(IoDevice) end }.
+
+new(WriteFun) ->
+ #ctx{ write=WriteFun }.
+
+new(RootElement, WriteFun) ->
+ start(RootElement, new(WriteFun)).
+
+set_option(prettyprint, OnOff, Writer) ->
+ Writer#ctx{ prettyprint=OnOff }.
+
+add_namespace(NS, NSUri, Writer=#ctx{ stack=[], ns_stack=NSStack, ns=NSMap }) ->
+ Writer#ctx{
+ ns=lists:keystore(NS, 1, NSMap, {NS, NSUri}),
+ ns_stack=[NS|NSStack] }.
+
+start(ElementName, Writer) ->
+ start_element(ElementName, Writer).
+
+close(Writer=#ctx{ close=Close, stack=[] }) ->
+ case Close of
+ undefined -> ok;
+ CloseFunArity1 when is_function(CloseFunArity1, 1) ->
+ CloseFunArity1(Writer),
+ Writer;
+ CloseFunArity2 when is_function(CloseFunArity2, 2) ->
+ CloseFunArity2(Writer)
+ end;
+close(Writer=#ctx{ stack=[_|_]}) ->
+ close(end_element(Writer)).
+
+with_element(Name, Writer, Fun) ->
+ with_element(none, Name, Writer, Fun).
+
+with_element(NS, Name, Writer, Fun) ->
+ close(Fun(start_element(NS, Name, Writer))).
+
+write_node(Name, Writer) ->
+ write_node(none, Name, Writer).
+
+write_node(NS, Name, Writer) ->
+ end_element(start_element(NS, Name, Writer)).
+
+write_attributes(AttributeList, Writer) ->
+ lists:foldl(fun({Name, Value}, XMLWriter) ->
+ write_attribute(Name, Value, XMLWriter)
+ end, Writer, AttributeList).
+
+write_attribute(Name, Value, Writer) ->
+ write_attribute(none, Name, Value, Writer).
+
+write_attribute(NS, Name, Value, Writer=#ctx{ quote=Quot }) ->
+ write([?SPACE, qname(NS, Name), <<"=">>, Quot, Value, Quot], Writer).
+
+write_value(_, #ctx{ stack=[] }) ->
+ throw({error, no_root_element});
+write_value(Value, Writer) ->
+ write(Value, close_current_node(Writer)).
+
+write_child(Name, Writer) ->
+ write_child(none, Name, Writer).
+
+write_child(NS, Name, Writer) ->
+ start_element(NS, Name, Writer).
+
+write_sibling(Name, Writer) ->
+ write_sibling(none, Name, Writer).
+
+write_sibling(NS, Name, Writer) ->
+ Writer2 = end_element(Writer),
+ start_element(NS, Name, Writer2).
+
+start_element(Name, Writer) ->
+ start_element(none, Name, Writer).
+
+start_element(NS, Name, Writer=#ctx{ ns=NSMap, ns_stack=NSStack }) ->
+ {Writer2, NodeName} =
+ push({NS, Name}, close_current_node(Writer)),
+ WithElem = write(NodeName, fun start_elem/2, Writer2),
+ case NSStack of
+ [] -> WithElem;
+ [_|_] ->
+ XmlnsAtt =
+ [ setelement(1, lists:keyfind(NSEntry, 1, NSMap),
+ "xmlns:" ++ NSEntry) || NSEntry <- NSStack ],
+ write_attributes(XmlnsAtt, WithElem)
+ end.
+
+end_element(Writer) ->
+ {Writer2, NodeName} = pop(Writer),
+ write(NodeName, fun end_elem/2, Writer2).
+
+push(Scope={NS, N}, W=#ctx{ stack=Stack }) ->
+ W2 = W#ctx{ stack=[{false, Scope}|Stack] },
+ {W2, qname(NS, N)}.
+
+pop(#ctx{ stack=[] }) ->
+ throw({error, empty_stack});
+pop(W=#ctx{ stack=[{false, _}|_] }) ->
+ pop(close_current_node(W));
+pop(W=#ctx{ stack=[{true, {NS, N}}|T] }) ->
+ {W#ctx{ stack=T }, qname(NS, N)}.
+
+close_current_node(W=#ctx{ stack=[] }) ->
+ W;
+close_current_node(W=#ctx{ stack=[{true, _}|_] }) ->
+ W;
+close_current_node(W=#ctx{ write=WF, stack=[{false, Node}|Stack] }) ->
+ WF(?CLOSE_BRACE),
+ W#ctx{ stack=[{true, Node}|Stack] }.
+
+qname(none, Name) ->
+ Name;
+qname(NS, Name) ->
+ [NS, <<":">>, Name].
+
+format(_Msg, _Args, #ctx{ stack=[] }) ->
+ throw({error, no_root_element});
+format(Msg, Args, Writer=#ctx{ format=undefined }) ->
+ write(io_lib:format(Msg, Args), close_current_node(Writer));
+format(Msg, Args, Writer=#ctx{ format=Format }) when is_function(Format, 2) ->
+ write(Format(Msg, Args), close_current_node(Writer));
+format(Msg, Args, Writer=#ctx{ format=Format }) when is_function(Format, 3) ->
+ Closed = close_current_node(Writer),
+ Format(Msg, Args, Closed).
+
+write(Data, Writer=#ctx{ write=WF }) ->
+ WF(encode(Data, Writer)), Writer.
+
+write(Data, Producer, Writer=#ctx{ write=WF }) ->
+ WF(Producer(encode(Data, Writer), Writer)), Writer.
+
+encode(Data, #ctx{ encoding=undefined }) when is_atom(Data) ->
+ atom_to_binary(Data, utf8);
+encode(Data, #ctx{ encoding=Encoding }) when is_atom(Data) ->
+ atom_to_binary(Data, Encoding);
+encode(Data, #ctx{ encoding=_ }) -> %% when is_binary(Data) ->
+ Data. %% TODO: deal with in/out encoding requirements
+ %% HINT: maybe get this from the Content Type and Disposition headers
+
+start_elem(NodeName, #ctx{ prettyprint=false }) ->
+ [?OPEN_BRACE, NodeName];
+start_elem(NodeName, #ctx{ stack=S, prettyprint=true, indent=I, newline=NL }) ->
+ %% TODO: consider a format string for this...
+ [NL, lists:duplicate(length(S), I), ?OPEN_BRACE, NodeName].
+
+end_elem(NodeName, _) ->
+ [?OPEN_BRACE, ?FWD_SLASH, NodeName, ?CLOSE_BRACE].
View
109 test/xml_writer_fsm_props.erl
@@ -0,0 +1,109 @@
+%% -----------------------------------------------------------------------------
+%% Copyright (c) 2002-2011 Tim Watson (watson.timothy@gmail.com)
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -----------------------------------------------------------------------------
+-module(xml_writer_fsm_props).
+
+-include_lib("proper/include/proper.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("hamcrest/include/hamcrest.hrl").
+-include_lib("xmerl/include/xmerl.hrl").
+
+-include("xml_writer.hrl").
+
+-compile(export_all).
+
+%%
+%% fsm transitions
+%%
+
+waiting_for_input(#writer{}=W) ->
+ [{history,
+ {call, xml_writer, write_value, [?MODULE, binary()]}},
+ {element_started,
+ {call, xml_writer, start_element, [?MODULE, binary()]}}].
+
+element_started(W) ->
+ [{waiting_for_input, {call, xml_writer, end_element, [?MODULE]}}].
+
+initial_state() -> waiting_for_input.
+
+initial_state_data() -> #writer{}.
+
+next_state_data(waiting_for_input, element_started, S,
+ Result, {call, xml_writer, start_element, [_, Elem]}=Call) ->
+ S#writer{ stack=[#stack_frame{ local_name=Elem }|S#writer.stack] };
+next_state_data(From, Target, StateData, Result, {call, _, _, _}=Call) ->
+ ct:pal("From = ~p, Target = ~p, StateData = ~p, Result = ~p, Call = ~p~n",
+ [From, Target, StateData, Result, Call]),
+ StateData.
+
+%precondition(waiting_for_input, _, W, {call, _, write_value, _}) ->
+% true; % length(W#writer.stack) > 0.
+%precondition(waiting_for_input, stopping, W, {_, _, _, _}) ->
+% false;
+%precondition(stopping, stopping, _, _) ->
+% true.
+
+%precondition(Today, _, S, {call,_,hungry,[]}) ->
+% case Today of
+% cheese_day ->
+% S#storage.cheese > 0;
+% lettuce_day ->
+% S#storage.lettuce > 0;
+% grapes_day ->
+% S#storage.grapes > 0
+% end;
+
+%precondition(waiting_for_input, element_started,
+% S, {call, _, start_element, _}) ->
+% length(S#writer.stack) > 0;
+precondition(_From, _Target, _StateData, {call ,_,_,_}) ->
+ true.
+
+postcondition(waiting_for_input, _, #writer{stack=[]},
+ {call, _, write_value, _}, Result) ->
+ Result == {error, no_root_node};
+postcondition(element_started, waiting_for_input,
+ W, {call, _, _, _}=Call, Result) ->
+% ct:pal("W = ~p, Call = ~p, Result = ~p~n",
+% [W,Call, Result]),
+ length(W#writer.stack) > 0;
+postcondition(_, _, _, _, _) ->
+ true.
+
+%weight(_Today, _Tomorrow, {call,_,new_day,_}) -> 1;
+%weight(_Today, _Today, {call,_,hungry,_}) -> 3;
+%weight(_Today, _Today, {call,_,buy,_}) -> 2.
+
+prop_all_state_transitions_are_valid() ->
+ ?FORALL(Cmds, proper_fsm:commands(?MODULE),
+ begin
+ Writer = xml_writer:new(?MODULE, fun io:format/2),
+ {History, State, Result} =
+ proper_fsm:run_commands(?MODULE, Cmds),
+ xml_writer:close(Writer),
+ ?WHENFAIL(
+ io:format("History: ~w\nState: ~w\nResult: ~w\n",
+ [History, State, Result]),
+ aggregate(zip(proper_fsm:state_names(History),
+ command_names(Cmds)),
+ Result =:= ok))
+ end).
View
31 test/xml_writer_props.erl
@@ -25,24 +25,49 @@
-include_lib("hamcrest/include/hamcrest.hrl").
-include_lib("xmerl/include/xmerl.hrl").
-prop_iolist_serialisation() ->
+-compile(export_all).
+
+%%
+%% Properties
+%%
+
+x_prop_basic_iolist_serialisation() ->
?FORALL(IoData, iodata(),
?IMPLIES(length(IoData) > 0,
enforce(fun has_iodata_content/2,
test_helper:tmp_file_id(), IoData))).
-prop_binary_serialisation() ->
+x_prop_basic_binary_serialisation() ->
?FORALL(Value, a_to_z(),
?IMPLIES(length(Value) > 1,
enforce(fun inner_text_value/2,
test_helper:tmp_file_id(), Value))).
-prop_atom_serialisation() ->
+x_prop_basic_atom_serialisation() ->
?FORALL(Value, a_to_z(),
?IMPLIES(length(Value) > 1,
enforce(fun has_named_value_foo/2, "foo",
test_helper:tmp_file_id(), Value))).
+x_prop_format_floating_point_numbers() ->
+ ?FORALL(F, float(),
+ begin
+ File = filename:join(".test", test_helper:tmp_file_id()),
+ io:format("Writing to ~s~n", [filename:join(rebar_utils:get_cwd(), File)]),
+ Writer = xml_writer:file_writer(File, [write]),
+ Writer2 = xml_writer:with_element("price", Writer,
+ fun(W) ->
+ W = xml_writer:format("~f", [F], W),
+ io:format("W = ~p~n", [W]),
+ W
+ end
+ ),
+ io:format("Writer2 = ~p~n", [Writer2]),
+ xml_writer:close(Writer2),
+ inner_text_value(File, F)
+ end
+ ).
+
%%
%% Custom Hamcrest Matchers
%%
View
2  test/xml_writer_tests.erl
@@ -29,7 +29,7 @@
-include_lib("hamcrest/include/hamcrest.hrl").
-include_lib("xmerl/include/xmerl.hrl").
-namespace_handling_test_() ->
+namespace_handling_test_xx() ->
{"Namespace handling",
[{"tags should be put into their proper namespace",
fun() ->
Please sign in to comment.
Something went wrong with that request. Please try again.