diff --git a/.gitignore b/.gitignore
index b89b9a284b123a821fea7bdf4b7ad1868d7a99f4..6f7b6735c3dccd859e1a609d3be2063f18bc4458 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .eunit
+ebin
 deps
 *.o
 *.beam
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f6c9e009f02e71cd57db6dafa993d939b841f045
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: erlang
+otp_release:
+   - R16B
+   - R15B03
diff --git a/README.md b/README.md
index d52c33d77ec4f515a08ca56ea3e32c20bc508277..d9aa303f3fb8a2aa01ecc93da230efbb404e615c 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+[![Stories in Ready](https://badge.waffle.io/ehedenst/gurka.png?label=ready)](https://waffle.io/ehedenst/gurka)
+
+[![Build Status](https://travis-ci.org/ehedenst/gurka.png?branch=develop)](https://travis-ci.org/ehedenst/gurka)
+
 gurka
 =====
 
diff --git a/features/parser.feature b/features/parser.feature
new file mode 100644
index 0000000000000000000000000000000000000000..70c7475534c08957cf0038ec9a3660b71c247579
--- /dev/null
+++ b/features/parser.feature
@@ -0,0 +1,17 @@
+Feature: Gurka parser
+  In order to run tests
+  Gurka should be able to
+  parse feature files
+
+  Background:
+    Given feature files are loaded from samples
+
+  Scenario Outline: Parse feature file
+    Given I parse the feature file <File>
+    Then the parsed result should have <Given> given steps
+    And the parsed result should have <When> when steps
+    And the parsed result should have <Then> then steps
+
+  Examples:
+    | File            | Given | When | Then |
+    | sample1.feature | 5     | 3    | 3    |
diff --git a/rebar b/rebar
new file mode 100755
index 0000000000000000000000000000000000000000..9bc9bd14eb3cae28559586bb78bcdd61b78b90ed
Binary files /dev/null and b/rebar differ
diff --git a/rebar.config b/rebar.config
new file mode 100644
index 0000000000000000000000000000000000000000..727a0b01239d86d951513a8451835e4d8729ef7e
--- /dev/null
+++ b/rebar.config
@@ -0,0 +1,3 @@
+{erl_opts, [debug_info, {d, debug}]}.
+{eunit_opts, [{report, {eunit_surefire, [{dir, ".eunit"}]}}]}.
+{cover_enabled, true}.
\ No newline at end of file
diff --git a/samples/sample1.feature b/samples/sample1.feature
new file mode 100644
index 0000000000000000000000000000000000000000..1f142b80ffbc9c8a473aaae6da54b93b1dbf20eb
--- /dev/null
+++ b/samples/sample1.feature
@@ -0,0 +1,35 @@
+Feature: Test 
+  In order to provide shorter URLs
+  As a visitor
+  I want to redirected to the correct index page
+
+  Background:
+    Given flow is started with etc/flow-test.config
+    And I am using chrome
+    And http server is ready
+
+  Scenario: URL without a trailing slash
+    When I open http://localhost:7070
+    And I wait for 1 seconds
+    Then the browser URL should be http://localhost:7070/index.md
+    But the browser URL should be http://localhost:7070/index.md
+
+  Scenario Outline: Email confirmation
+    Given I have a user account with my name "Jojo Binks"
+    And the following users exist:
+      | name       | email           | phone |
+      | Aslak      | aslak@email.com | 123   |
+      | Matt       | matt@email.com  | 234   |
+      | Joe <Role> | joe@email.org   | 456   |
+    When an Admin grants me <Role> rights
+    Then I should receive an email with the body:
+    """
+      Dear Jojo Binks,
+      You have been granted <Role> rights. You are <details>. Please be responsible.
+      -The Admins
+      """
+
+  Examples:
+    | Role    | details                                       |
+    | Manager | now able to manage your employee accounts     |
+    | Admin   | able to manage any user account on the system |
diff --git a/src/gurka.app.src b/src/gurka.app.src
new file mode 100644
index 0000000000000000000000000000000000000000..00994ad045f68eb67b39b3d4e8763dc2a59561a8
--- /dev/null
+++ b/src/gurka.app.src
@@ -0,0 +1,11 @@
+{application, gurka,
+    [
+        {description, "Erlang implementation of Cucumber"},
+        {vsn, "0.0.1"},
+        {applications, [
+            kernel,
+            stdlib
+        ]},
+        {registered, []},
+        {env, []}
+    ]}.
diff --git a/src/gurka.erl b/src/gurka.erl
new file mode 100644
index 0000000000000000000000000000000000000000..8c28323c6e95c130251b977ade37b3ffbae721af
--- /dev/null
+++ b/src/gurka.erl
@@ -0,0 +1,166 @@
+-compile({no_auto_import, [apply/3]}).
+-module(gurka).
+
+-export([run/1, run/2]).
+
+run(File) ->
+    Module = list_to_atom("feature_" ++ filename:basename(File, ".feature")),
+    run(File, Module).
+
+run(File, Module) ->
+    case gurka_parser:parse(File) of
+        {ok, Feature} ->
+            Result = run(Module, undefined, undefined, undefined, Feature),
+            Passed = has_passed(Result),
+            if
+                Passed ->
+                    {ok, Result};
+                true ->
+                    {fail, Result}
+            end;
+        {error, Reason} ->
+            {error, Reason}
+    end.
+
+run(Module, FeatureState, _ScenarioState, _PreviousPhase, []) ->
+    apply(Module, teardown_feature, [FeatureState]),
+    [];
+
+run(DefaultModule, FeatureState, ScenarioState, PreviousPhase, [Step = {feature, start, Pattern, _RowNum} | Steps]) ->
+    Module = resolve_module(DefaultModule, Pattern),
+    case PreviousPhase of
+        feature ->
+            apply(Module, teardown_feature, [FeatureState]);
+        background ->
+            apply(Module, teardown_feature, [FeatureState]);
+        scenario ->
+            apply(Module, teardown_scenario, [ScenarioState]),
+            apply(Module, teardown_feature, [FeatureState]);
+        _ ->
+            ok
+    end,
+    case apply(Module, setup_feature, [Pattern]) of
+        {ok, State} ->
+            [{ok, Step} | run(Module, State, ScenarioState, feature, Steps)];
+        {error, undef} ->
+            [{ok, Step} | run(Module, undefined, ScenarioState, feature, Steps)];
+        Term ->
+            [{Term, Step}]
+    end;
+
+run(Module, FeatureState, ScenarioState, _PreviousPhase, [Step = {background, given, Pattern, _RowNum} | Steps]) ->
+    case apply(Module, given, [Pattern, FeatureState]) of
+        ok ->
+            [{ok, Step} | run(Module, FeatureState, ScenarioState, background, Steps)];
+        {ok, State} ->
+            [{ok, Step} | run(Module, State, ScenarioState, background, Steps)];
+        Term ->
+            [{Term, Step}]
+    end;
+
+run(Module, FeatureState, ScenarioState, PreviousPhase, [Step = {scenario, start, Pattern, _RowNum} | Steps]) ->
+    case PreviousPhase of
+        scenario ->
+            apply(Module, teardown_scenario, [ScenarioState]);
+        _ ->
+            ok
+    end,
+    case apply(Module, setup_scenario, [Pattern, FeatureState]) of
+        {ok, State} ->
+            [{ok, Step} | run(Module, FeatureState, State, scenario, Steps)];
+        {error, undef} ->
+            [{ok, Step} | run(Module, FeatureState, FeatureState, scenario, Steps)];
+        Term ->
+            [{Term, Step}]
+    end;
+
+run(Module, FeatureState, ScenarioState, _PreviousPhase, [Step = {scenario, Action, Pattern, _RowNum} | Steps]) when Action == given; Action == 'when'; Action == then ->
+    case apply(Module, Action, [Pattern, ScenarioState]) of
+        ok ->
+            [{ok, Step} | run(Module, FeatureState, ScenarioState, scenario, Steps)];
+        {ok, State} ->
+            [{ok, Step} | run(Module, FeatureState, State, scenario, Steps)];
+        Term ->
+            [{Term, Step}]
+    end;
+
+run(Module, _FeatureState, ScenarioState, _PreviousPhase, [{scenario_outline, 'end', _Pattern, _RowNum}]) ->
+    apply(Module, teardown_scenario, [ScenarioState]),
+    [];
+
+run(Module, FeatureState, ScenarioState, PreviousPhase, [{scenario_outline, start, Pattern, RowNum} | Steps]) ->
+    case PreviousPhase of
+        scenario ->
+            apply(Module, teardown_scenario, [ScenarioState]);
+        _ ->
+            ok
+    end,
+    run_outline(Module, FeatureState, ScenarioState, PreviousPhase, Steps, [{start, Pattern, RowNum}]);
+
+run(Module, FeatureState, ScenarioState, PreviousPhase, [{scenario_outline, Action, Pattern, RowNum} | Steps]) ->
+    run_outline(Module, FeatureState, ScenarioState, PreviousPhase, Steps, [{Action, Pattern, RowNum}]);
+
+run(Module, FeatureState, ScenarioState, _PreviousPhase, [Step = {Phase, _Action, _Pattern, _RowNum} | Steps]) ->
+    [{ok, Step} | run(Module, FeatureState, ScenarioState, Phase, Steps)].
+
+run_outline(Module, FeatureState, ScenarioState, _PreviousPhase, [{scenario_outline, examples, [Headers | Rows], _RowNum} | Steps], ScenarioOutline) ->
+    TaggedHeaders = [<<"<", Header/binary, ">">> || Header <- Headers],
+    Results = lists:map(fun(Row) ->
+        Example = lists:zip(TaggedHeaders, Row),
+        Scenario = lists:foldl(fun({Action, Pattern, RowNum}, ScenarioAcc) ->
+            ReplacedPattern = lists:map(fun(Token) ->
+                lists:foldr(fun replace_tags/2, Token, Example)
+            end, Pattern),
+            [{scenario, Action, ReplacedPattern, RowNum} | ScenarioAcc]
+        end, [{scenario_outline, 'end', [], 0}], ScenarioOutline),
+        run(Module, FeatureState, ScenarioState, scenario_outline, Scenario)
+    end, Rows),
+    [Results | run(Module, FeatureState, ScenarioState, scenario_outline, Steps)];
+
+run_outline(Module, FeatureState, ScenarioState, PreviousPhase, [{scenario_outline, Action, Pattern, RowNum} | Steps], ScenarioOutline) ->
+    run_outline(Module, FeatureState, ScenarioState, PreviousPhase, Steps, [{Action, Pattern, RowNum} | ScenarioOutline]).
+
+replace_tags({Pattern, Replacement}, {Term, Subject}) ->
+    {Term, replace_tags({Pattern, Replacement}, Subject)};
+
+replace_tags({Pattern, Replacement}, Subject) when is_binary(Subject) ->
+    binary:replace(Subject, Pattern, Replacement, [global]);
+
+replace_tags({Pattern, Replacement}, Subject) when is_list(Subject) ->
+    [replace_tags({Pattern, Replacement}, Token) || Token <- Subject];
+
+replace_tags(_, Subject) ->
+    Subject.
+
+apply(Module, Function, Args) ->
+    case catch erlang:apply(Module, Function, Args) of
+        {'EXIT', {Reason, Stack}} ->
+            {error, Reason, Stack};
+        {'EXIT', Term} ->
+            {error, Term};
+        Term ->
+            Term
+    end.
+
+has_passed([]) ->
+    true;
+
+has_passed([Step | Steps]) when is_list(Step) ->
+    has_passed(Step) and has_passed(Steps);
+
+has_passed([{ok, _} | Steps]) ->
+    has_passed(Steps);
+
+has_passed(_) ->
+    false.
+
+resolve_module(Module, []) ->
+    Module;
+
+resolve_module(_Module, [<<"(module:", Term/binary>> | Pattern]) ->
+    NewModule = hd(binary:split(Term, <<")">>)),
+    resolve_module(binary_to_atom(NewModule, utf8), Pattern);
+
+resolve_module(Module, [_ | Pattern]) ->
+    resolve_module(Module, Pattern).
+
diff --git a/src/gurka_formatter_plain.erl b/src/gurka_formatter_plain.erl
new file mode 100644
index 0000000000000000000000000000000000000000..19094d09c0eaed93c417c9db2372da046c70b453
--- /dev/null
+++ b/src/gurka_formatter_plain.erl
@@ -0,0 +1,82 @@
+-module(gurka_formatter_plain).
+
+-export([format/1, format/2]).
+
+format(Result) ->
+    format(Result, []).
+
+format(Result, _Opts) ->
+    format_steps(Result, undefined).
+
+format_steps([], _LastAction) ->
+    [];
+
+format_steps([Step | Steps], LastAction) when is_list(Step) ->
+    format_steps(Step, LastAction) ++ format_steps(Steps, LastAction);
+
+format_steps([Step | Steps], LastAction) ->
+    {FormattedStep, NewLastAction} = format_step(Step, LastAction),
+    FormattedStep ++ format_steps(Steps, NewLastAction).
+
+format_step({ok, {Phase, Action, Pattern, RowNum}}, LastAction) ->
+    {format_pattern(Pattern, RowNum, format_phase(Phase, Action, LastAction)), Action};
+
+format_step({Result, {Phase, Action, Pattern, RowNum}}, LastAction) ->
+    FormattedPattern = format_pattern(Pattern, RowNum, format_phase(Phase, Action, LastAction)),
+    FormattedFailure = io_lib:format("~s    FAILED: ~60p~n", [FormattedPattern, Result]),
+    {FormattedFailure, Action}.
+
+format_phase(feature, start, _LastAction) ->
+    "Feature: ";
+
+format_phase(feature, desc, _LastAction) ->
+    "  ";
+
+format_phase(background, start, _LastAction) ->
+    "\n  Background: ";
+
+format_phase(scenario, start, _LastAction) ->
+    "\n  Scenario: ";
+
+format_phase(_Phase, Action, Action) ->
+    "    And ";
+
+format_phase(_Phase, given, _LastAction) ->
+    "    Given ";
+
+format_phase(_Phase, 'when', _LastAction) ->
+    "    When ";
+
+format_phase(_Phase, 'then', _LastAction) ->
+    "    Then ";
+
+format_phase(Phase, Action, _LastAction) ->
+    io_lib:format("    ~p:~p ", [Phase, Action]).
+
+format_pattern([], RowNum, [$\n | Acc]) ->
+    io_lib:format("~n~s~4B~n", [unicode:characters_to_binary(string:left(Acc, 76, $\s)), RowNum]);
+
+format_pattern([], RowNum, Acc) ->
+    io_lib:format("~s~4B~n", [unicode:characters_to_binary(string:left(Acc, 76, $\s)), RowNum]);
+
+format_pattern([{table, Table} | Pattern], RowNum, Acc) ->
+    Sizes = [[length(unicode:characters_to_list(Col, utf8)) || Col <- Row] || Row <- Table],
+    Max = lists:foldl(fun(Row, MaxAcc) ->
+        lists:zipwith(fun(X, Y) -> max(X, Y) end, Row, MaxAcc)
+    end, hd(Sizes), tl(Sizes)),
+    FTable = [
+            "      " ++
+            lists:zipwith(fun(V, M) ->
+                "| " ++ string:left(binary_to_list(V), M + 1, $\s)
+            end, Row, Max) ++ "|\n" || Row <- Table
+    ],
+    format_pattern(Pattern, RowNum, Acc) ++ io_lib:format("~s", [FTable]);
+
+format_pattern([{docstring, Docstring} | Pattern], RowNum, Acc) ->
+    IndentedDocString = binary:replace(Docstring, <<"\n">>, <<"\n      ">>, [global]),
+    format_pattern(Pattern, RowNum, Acc) ++ io_lib:format("      \"\"\"~n      ~s~n      \"\"\"~n", [IndentedDocString]);
+
+format_pattern([Term | Pattern], RowNum, Acc) ->
+    format_pattern(Pattern, RowNum, Acc ++ unicode:characters_to_list(Term, utf8) ++ " ").
+
+
diff --git a/src/gurka_parser.erl b/src/gurka_parser.erl
new file mode 100644
index 0000000000000000000000000000000000000000..ceae568fbaa04a8fb3c1bab91f7a2756e1923e5d
--- /dev/null
+++ b/src/gurka_parser.erl
@@ -0,0 +1,144 @@
+-module(gurka_parser).
+
+-export([parse/1, tokens/1]).
+
+-export_type([phase/0, action/0, step/0, feature/0]).
+
+-type phase() :: feature | background | scenario | scenario_outline.
+-type action() :: desc | title | given | 'when' | then | examples | headers | values.
+-type pattern() :: [binary()].
+-type row() :: pos_integer().
+-type step() :: {phase(), action(), pattern(), row()}.
+-type feature() :: [step()].
+-type lines() :: [{row(), binary(), pattern()}].
+
+-spec parse(File) -> {ok, Feature} | {error, Reason} when
+    File :: file:name(),
+    Feature :: feature(),
+    Reason :: file:posix() | badarg | terminated | system_limit.
+parse(File) ->
+    case file:read_file(File) of
+        {ok, Binary} ->
+            {_, Lines} = lists:foldl(fun(Line, {Row, AccIn}) ->
+                {Row + 1, [{Row, Line, tokens(Line)} | AccIn]}
+            end, {1, []}, binary:split(Binary, [<<$\n>>, <<$\r>>], [global, trim])),
+            {ok, compact(process(undefined, undefined, lists:reverse(Lines)))};
+        {error, Reason} ->
+            {error, Reason}
+    end.
+
+-spec process(Phase, Action, Lines) -> Feature when
+    Phase :: phase(),
+    Action :: action(),
+    Lines :: lines(),
+    Feature :: feature().
+process(_Phase, _Action, []) ->
+    [];
+process(Phase, Action, [{_Row, _Line, []} | Lines]) ->
+    process(Phase, Action, Lines);
+process(Phase, Action, [{Row, Line, [<<"|">> | Tokens]} | Lines]) ->
+    {Lines2, Table} = build_table([{Row, Line, [<<"|">> | Tokens]} | Lines], []),
+    [{Phase, table, Table, Row} | process(Phase, Action, Lines2)];
+process(Phase, Action, [{Row, _Line, [<<"\"\"\"">> | _Tokens]} | Lines]) ->
+    {Lines2, Docstring} = build_docstring(Lines, []),
+    [{Phase, docstring, Docstring, Row} | process(Phase, Action, Lines2)];
+process(Phase, Action, [{Row, _Line, [FirstToken | Tokens]} | Lines]) ->
+    case normalize_token(FirstToken) of
+        "and" ->
+            [{Phase, Action, Tokens, Row} | process(Phase, Action, Lines)];
+        "but" ->
+            [{Phase, Action, Tokens, Row} | process(Phase, Action, Lines)];
+        "given" ->
+            [{Phase, given, Tokens, Row} | process(Phase, given, Lines)];
+        "when" ->
+            [{Phase, 'when', Tokens, Row} | process(Phase, 'when', Lines)];
+        "then" ->
+            [{Phase, then, Tokens, Row} | process(Phase, then, Lines)];
+        "examples" ->
+            [{Phase, examples, Tokens, Row} | process(Phase, examples, Lines)];
+        "feature" ->
+            [{feature, start, Tokens, Row} | process(feature, undefined, Lines)];
+        "background" ->
+            [{background, start, Tokens, Row} | process(background, undefined, Lines)];
+        "scenario" ->
+            case normalize_token(hd(Tokens)) of
+                "outline" ->
+                    [{scenario_outline, start, tl(Tokens), Row} | process(scenario_outline, undefined, Lines)];
+                _ ->
+                    [{scenario, start, Tokens, Row} | process(scenario, undefined, Lines)]
+            end;
+        _ ->
+            [{Phase, desc, [FirstToken | Tokens], Row} | process(Phase, Action, Lines)]
+    end.
+
+compact(Lines) ->
+    compact(lists:reverse(Lines), []).
+
+compact([], Acc) ->
+    Acc;
+compact([{_, docstring, Docstring, _} | [{Phase, Action, Tokens, Row} | Lines]], Acc) ->
+    compact(Lines, [{Phase, Action, Tokens ++ [{docstring, Docstring}], Row} | Acc]);
+compact([{_, table, Table, _} | [{Phase, examples, _Tokens, Row} | Lines]], Acc) ->
+    compact(Lines, [{Phase, examples, Table, Row} | Acc]);
+compact([{_, table, Table, _} | [{Phase, Action, Tokens, Row} | Lines]], Acc) ->
+    compact(Lines, [{Phase, Action, Tokens ++ [{table, Table}], Row} | Acc]);
+compact([Line | Lines], Acc) ->
+    compact(Lines, [Line | Acc]).
+
+build_table([], Table) ->
+    {[], lists:reverse(Table)};
+build_table([{_Row, Line, [<<"|">> | _Tokens]} | Lines], Table) ->
+    Cells = [list_to_binary(string:strip(Field)) || Field <- string:tokens(string:strip(binary_to_list(Line)), "|")],
+    build_table(Lines, [Cells | Table]);
+build_table(Lines, Table) ->
+    {Lines, lists:reverse(Table)}.
+
+build_docstring([], Docstring) ->
+    {[], Docstring};
+build_docstring([{_Row, _Line, [<<"\"\"\"">> | _Tokens]} | Lines], Docstring) ->
+    {Lines, list_to_binary(Docstring)};
+build_docstring([{_Row, Line, _Tokens} | Lines], []) ->
+    build_docstring(Lines, string:strip(binary_to_list(Line)));
+build_docstring([{_Row, Line, _Tokens} | Lines], Docstring) ->
+    build_docstring(Lines, Docstring ++ "\n" ++ string:strip(binary_to_list(Line))).
+
+normalize_token(Token) ->
+    hd(string:tokens(string:to_lower(binary_to_list(Token)), ":")).
+
+tokens(Line) ->
+    [strip_quotes(Token) || Token <- tokens(Line, [], <<>>)].
+
+tokens(<<>>, Tokens, <<>>) ->
+    Tokens;
+tokens(<<>>, [<<$", $">>], <<$">>) ->
+    [<<$", $", $">>];
+tokens(<<>>, Tokens, Buffer) ->
+    Tokens ++ [Buffer];
+tokens(<<$\s, Line/binary>>, Tokens, <<>>) ->
+    tokens(Line, Tokens, <<>>);
+tokens(<<$\s, Line/binary>>, Tokens, <<$", Buffer/binary>>) ->
+    tokens(Line, Tokens, <<$", Buffer/binary, $\s>>);
+tokens(<<$\s, Line/binary>>, Tokens, Buffer) ->
+    tokens(<<$\s, Line/binary>>, Tokens ++ [Buffer], <<>>);
+tokens(<<$\", Line/binary>>, Tokens, <<>>) ->
+    tokens(Line, Tokens, <<$">>);
+tokens(<<$\", Line/binary>>, Tokens, <<$", Buffer/binary>>) ->
+    tokens(Line, Tokens ++ [<<$", Buffer/binary, $">>], <<>>);
+tokens(<<$\", Line/binary>>, Tokens, Buffer) ->
+    tokens(Line, Tokens ++ [Buffer], <<$">>);
+tokens(<<Character, Line/binary>>, Tokens, Buffer) ->
+    tokens(Line, Tokens, <<Buffer/binary, Character>>).
+
+strip_quotes(<<>>) ->
+    <<>>;
+strip_quotes(<<$", $", $">>) ->
+    <<$", $", $">>;
+strip_quotes(<<$", Token/binary>>) ->
+    case binary:last(Token) of
+        $" ->
+            binary:part(Token, {0, byte_size(Token) - 1});
+        _ ->
+            Token
+    end;
+strip_quotes(Token) ->
+    Token.
diff --git a/src/gurka_transform.erl b/src/gurka_transform.erl
new file mode 100644
index 0000000000000000000000000000000000000000..32b3aa6aed1e6321fae82dab96d16fa39eae6db6
--- /dev/null
+++ b/src/gurka_transform.erl
@@ -0,0 +1,57 @@
+-module(gurka_transform).
+-export([parse_transform/2]).
+
+parse_transform(Forms, _Options) ->
+    forms(Forms).
+
+forms([]) ->
+    [];
+forms([Form | Forms]) ->
+    [form(Form) | forms(Forms)].
+
+form({function, Line, Name, Arity, Clauses}) when Name =:= setup_feature; Name =:= setup_scenario; Name =:= given; Name =:= 'when'; Name =:= then ->
+    function(Line, Name, Arity, Clauses);
+form(Form) ->
+    Form.
+
+function(Line, Name, Arity, Clauses) ->
+    {function, Line, Name, Arity, clauses(Clauses)}.
+
+clauses([]) ->
+    [];
+clauses([Clause | Clauses]) ->
+    [clause(Clause) | clauses(Clauses)].
+
+clause({clause, ClauseLine, [{bin, _, [{bin_element, _, {string, StringLine, String}, default, default}]} | Head], Guard, Exprs}) ->
+    {clause, ClauseLine, [process_string(StringLine, String) | Head], Guard, Exprs};
+
+clause({clause, ClauseLine, [{string, StringLine, String} | Head], Guard, Exprs}) ->
+    {clause, ClauseLine, [process_string(StringLine, String) | Head], Guard, Exprs};
+
+clause({clause, ClauseLine, [{tuple, _, [{bin, _, [{bin_element, _, {string, StringLine, String}, default, default}]}, {cons, _, _, _}]} | Head], Guard, Exprs}) ->
+    {clause, ClauseLine, [process_string(StringLine, String) | Head], Guard, Exprs};
+
+clause({clause, ClauseLine, [{tuple, _, [{string, StringLine, String}, {cons, _, _, _}]} | Head], Guard, Exprs}) ->
+    {clause, ClauseLine, [process_string(StringLine, String) | Head], Guard, Exprs};
+
+clause({clause, Line, Head, Guard, Exprs}) ->
+    {clause, Line, Head, Guard, Exprs};
+
+clause(Clause) ->
+    Clause.
+
+process_string(Row, String) ->
+    Tokens = gurka_parser:tokens(list_to_binary(String)),
+    Pattern = build_pattern(Row, Tokens),
+    Pattern.
+
+build_pattern(Row, []) ->
+    {nil, Row};
+
+build_pattern(Row, [<<$\$, Token/binary>> | Tokens]) ->
+    Var = {var, Row, binary_to_atom(Token, utf8)},
+    {cons, Row, Var, build_pattern(Row, Tokens)};
+
+build_pattern(Row, [Token | Tokens]) ->
+    Bin = {bin, Row, [{bin_element, Row, {string, Row, binary_to_list(Token)}, default, default}]},
+    {cons, Row, Bin, build_pattern(Row, Tokens)}.
diff --git a/test/feature_parser.erl b/test/feature_parser.erl
new file mode 100644
index 0000000000000000000000000000000000000000..457735d1ceaeb57fe9d8adbfc3e7b1912ad7f846
--- /dev/null
+++ b/test/feature_parser.erl
@@ -0,0 +1,58 @@
+-compile({parse_transform, gurka_transform}).
+-module(feature_parser).
+
+-export([setup_feature/1, setup_scenario/2, teardown_feature/1, teardown_scenario/1, given/2, 'when'/2, then/2]).
+
+-record(state, {feature_dir, feature}).
+
+setup_feature(_Tokens) ->
+    {ok, #state{}}.
+
+teardown_feature(_State) ->
+    ok.
+
+setup_scenario(_Tokens, State) ->
+    {ok, State#state{}}.
+
+teardown_scenario(_State) ->
+    ok.
+
+given("feature files are loaded from $Directory", State) ->
+    case filelib:is_dir(Directory) of
+        true -> {ok, State#state{feature_dir = Directory}};
+        _ -> {error, <<Directory/binary, " is a not a directory">>}
+    end;
+
+given("I parse the feature file $File", State) ->
+    case gurka_parser:parse(filename:join(State#state.feature_dir, File)) of
+        {ok, Feature} ->
+            {ok, State#state{feature = Feature}};
+        Error ->
+            Error
+    end;
+
+given(Tokens, State) ->
+    io:format("given ~p~n", [Tokens]),
+    {ok, State}.
+
+'when'(Tokens, State) ->
+    io:format("when ~p~n", [Tokens]),
+    {ok, State}.
+
+then("the parsed result should have $StepCount $StepType steps", State) ->
+    ExpectedCount = binary_to_integer(StepCount),
+    Type = binary_to_atom(StepType, utf8),
+    RealCount = lists:foldl(
+        fun({_, Action, _, _}, Sum) when Action == Type -> Sum + 1;
+            (_, Sum) -> Sum
+        end, 0, State#state.feature),
+    if
+        ExpectedCount == RealCount ->
+            {ok, State};
+        true ->
+            {error, lists:flatten(io_lib:format("Expected ~p ~p steps, but got ~p", [ExpectedCount, Type, RealCount]))}
+    end;
+
+then(Tokens, State) ->
+    io:format("then ~p~n", [Tokens]),
+    {ok, State}.
diff --git a/test/features.erl b/test/features.erl
new file mode 100644
index 0000000000000000000000000000000000000000..689cbe2c469568700813491324d57314eb390d14
--- /dev/null
+++ b/test/features.erl
@@ -0,0 +1,31 @@
+-module(features).
+
+-include_lib("eunit/include/eunit.hrl").
+
+gurka_test_() ->
+    {setup,
+        spawn,
+        fun setup/0,
+        fun teardown/1,
+        fun features/1}.
+
+setup() ->
+    file:set_cwd(".."),
+    filelib:fold_files("features", ".*[.]feature", true, fun(File, Files) -> [File | Files] end, []).
+
+teardown(_Files) ->
+    ok.
+
+features(Files) ->
+    {inorder, [{timeout, 60, ?_test(feature(File))} || File <- Files]}.
+
+feature(File) ->
+    erlang:group_leader(erlang:whereis(user), self()),
+    case gurka:run(File) of
+        {ok, Result} ->
+            io:format("File: ~sOK~n~s~n", [string:left(File, 72, $\s), gurka_formatter_plain:format(Result)]);
+        {fail, Result} ->
+            io:format("File: ~sFAIL~n~s~n", [string:left(File, 70, $\s), gurka_formatter_plain:format(Result)]),
+            erlang:error({failed, File})
+    end.
+