Hi,

I guess Yariv is right; I actually am having borderline unhealthy
amounts of fun playing with erlyweb and erlang!

This patch allows erlyweb:compile to honor compile option macro
definitions, just like erlang's own compile:file.  As a result,
smerl:for_file, smerl:for_module, and erlydb:code_gen may also take
macros now.

Motivation:
- the doc said erlyweb:compile handles Options like compile:file does,
  as one would expect
- include directories are handled, macros are conceptually similar
- I needed it for my project

The patch code was test-driven.  Backward compatibility is kept for
all
enhanced overloaded functions, adding new exports in the process.
I'm
pretty sure all the erlyweb doc has correspondingly updated.

Tests which depend on a database use the same connection parameters
as erlydb_test.  They assume the 'person' table is present.

Hopefully this will be as useful to some folks as it is to me.  Yariv,
you're
welcome to include this patch as you see fit.

Cheers,

Chris Bernard
LogicLeaf


======================================================================
%% @author Chris Bernard [http://logicleaf.com]
%% @hidden
%%
%% Test for patch to erlyweb and smerl which allows compile option
macros
%% -- erlyweb:compile/2 now honors macro definitions in its Options.

-module(erlyweb_test).
-author("Chris Bernard").
-compile(export_all).

-define(File,
    ["-module(mymod).\n",
     "-export([myfunc/0]).\n",
     "-ifdef(Macro).\n",
     "-myattr(myvalue).\n",
     "-endif.\n",
     "myfunc() -> ok.\n"]).

-define(Controller,
    ["-module(foo_controller).\n",
     "-erlyweb_magic(erlyweb_controller).\n",
     "-export([myfunc/0]).\n",
     "-ifdef(Macro).\n",
     "-myattr(myvalue).\n",
     "-endif.\n",
     "myfunc() -> ok.\n"]).

-define(Model,
    ["-module(person).\n",
     "-export([fields/0, type_field/0]).\n",
     "-ifdef(Macro).\n",
     "-myattr(myvalue).\n",
     "-endif.\n",
     "fields() -> [name, age, country].\n",
     "type_field() -> type.\n"]).

test() ->
    run([
         control_compile_test,
         predef_macro_fail_test,
         for_file_with_macros_test,
         for_module_with_macros_test,
         compile_test,
         compile_controller_test,
         compile_model_test
        ]),
    ok.

%% Ensure that our test strategy is sound -- that we can actually test
%% whether a compile option macro was honored -- by bypassing smerl
%% and erlyweb entirely at first.
control_compile_test() ->
    mymod_setup(),
    {ok, Module} = compile:file("mymod", [{outdir, "./"},
{d,'Macro'}]),
    code:purge(Module),
    {module, Module} = code:load_file(Module),
    assert_mod_has_attr(myattr, mymod, ?LINE).

%% Test that smerl doesn't honor predefined macros via compile().
%% Upon first glance at smerl's api, it seemes appropriate to use
%% smerl:compile() since it takes an options list, but after reading
the
%% implementation, it's clear that for_file() and for_module() should
be
%% used to support macros.
predef_macro_fail_test() ->
    mymod_setup(),
    {ok, MetaMod} = smerl:for_file("./mymod.erl"),
    smerl:compile(MetaMod, [{outdir, "./"},{d, 'Macro'}]),
    assert_fail(fun() ->
        assert_mod_has_attr(myattr, mymod) end, ?LINE).

%% Test that smerl honors predefined macros via for_file().
for_file_with_macros_test() ->
    mymod_setup(),
    {ok, MetaMod} = smerl:for_file("./mymod.erl", [], [{'Macro',
true}]),
    smerl:compile(MetaMod, [{outdir, "./"}]),
    assert_mod_has_attr(myattr, mymod, ?LINE).

%% Test that smerl honors predefined macros via for_module().
for_module_with_macros_test() ->
    mymod_setup(),
    {ok, MetaMod} = smerl:for_module("./mymod.erl", [], [{'Macro',
true}]),
    smerl:compile(MetaMod, [{outdir, "./"}]),
    assert_mod_has_attr(myattr, mymod, ?LINE).

%% Now that smerl can handle compile option macro definitions, test
%% that erlyweb_compile() can use this new feature for non-components.
compile_test() ->
    myapp_setup(),
    file:write_file("./myapp/src/mymod.erl", ?File),
    {ok, _} = erlyweb:compile("myapp", [{d,'Macro'}]),
    assert_mod_has_attr(myattr, mymod, ?LINE).

%% Ensure erlyweb controllers can get compiled with predefined macros.
compile_controller_test() ->
    myapp_setup(),
    file:write_file("./myapp/src/components/foo_controller.erl", ?
Controller),
    {ok, _} = erlyweb:compile("myapp", [{d, 'Macro'}]),
    assert_mod_has_attr(myattr, foo_controller, ?LINE).

%% Ensure erlyweb models can get compiled with predefined macros.
%% This uses the same database connection configuration as
erlydb_test,
%% and depends on erlydb_test's person table.
compile_model_test() ->
    myapp_setup(),
    file:write_file("./myapp/src/components/person.erl", ?Model),
    ok = erlydb:start(mysql, [{hostname, "localhost"}, {username,
"root"},
                              {password, "password"}, {database, "test"}]),
    {ok, _} = erlyweb:compile("myapp", [{d, 'Macro'}, {erlydb_driver,
mysql}]),
    assert_mod_has_attr(myattr, person, ?LINE).

%% Note:  ErlTL was not enhanced to allow compile option macro
definitions
%% because ErlTL does not currently handle macros at all.


%% Setup and Teardown

mymod_setup() ->
    file:write_file("mymod.erl", ?File).

myapp_setup() ->
    erlyweb:create_app("myapp", ".").

teardown() ->
    case os:type() of
        {unix, _} ->
            os:cmd("rm mymod* 2>/dev/null; rm -rf myapp 2>/dev/null");
        {win32, _} ->
            os:cmd("del mymod* 2>NUL && rmdir /S /Q myapp 2>NUL")
    end.


%% Testing Helpers

assert_mod_has_attr(Attr, Mod) ->
    assert_mod_has_attr(Attr, Mod, "").

assert_mod_has_attr(Attr, Mod, Line) ->
    case lists:any(fun(InfoList) ->
        case InfoList of
            {attributes, Attrs} -> proplists:is_defined(Attr, Attrs);
            _ -> false end
        end, Mod:module_info()) of
        false -> throw({error, "Assertion failure at line: " ++
integer_to_list(Line)});
         _ -> ok end.

assert_equal(Expected, Received, Line) ->
    case Expected =:= Received of
        false -> throw({error, lists:flatten(io_lib:format(
            "Expected: (~p), Received: (~p).  Line: ~p", [Expected,
Received, Line]))});
        true -> ok end.

assert_fail(Failer, Line) ->
    case begin
            try Failer()
            catch
                _:_ -> ok
            end
        end of
    ok -> ok;
    _ -> throw({error, Line}) end.

run(Tests) when is_atom(Tests) ->
    run([Tests]);
run(Tests) ->
    lists:foreach(fun(Test) ->
                          try apply(?MODULE, Test, [])
                          after
                              teardown()
                          end
                  end, Tests).


======================================================================
diff -aur erlyweb-0.7.2/src/smerl/smerl.erl patched_erlyweb-0.7.2/src/
smerl/smerl.erl
--- erlyweb-0.7.2/src/smerl/smerl.erl   2007-08-21 02:10:45.000000000
-0400
+++ patched_erlyweb-0.7.2/src/smerl/smerl.erl   2008-09-06
15:39:20.000000000 -0400
@@ -69,8 +69,10 @@
 -export([new/1,
         for_module/1,
         for_module/2,
+        for_module/3,
         for_file/1,
-        for_file/2,
+        for_file/2,
+        for_file/3,
          get_module/1,
         set_module/2,
          get_forms/1,
@@ -136,6 +138,10 @@
 for_module(ModuleName) ->
     for_module(ModuleName, []).

+%% @equiv for_module(ModuleName, IncludePaths, [])
+for_module(ModuleName, IncludePaths) ->
+    for_module(ModuleName, IncludePaths, []).
+
 %% @doc Create a meta_mod tuple for an existing module. If ModuleName
is a
 %% string, it is interpreted as a file name (this is the same as
calling
 %% @{link smerl:for_file}). If ModuleName is an atom, Smerl attempts
to
@@ -146,15 +152,16 @@
 %%
 %% The IncludePaths parameter is used when 'ModuleName' is a file
name.
 %%
-%% @spec for_module(ModuleName::atom() | string(), IncludePaths::
[string()]) ->
+%% @spec for_module(ModuleName::atom() | string(), IncludePaths::
[string()],
+%%    Macros::[{atom(), term()}]) ->
 %%   {ok, meta_mod()} | {error, Error}
-for_module(ModuleName, IncludePaths) when is_list(ModuleName) ->
-    for_file(ModuleName, IncludePaths);
-for_module(ModuleName, IncludePaths) when is_atom(ModuleName) ->
+for_module(ModuleName, IncludePaths, Macros) when is_list(ModuleName)
->
+    for_file(ModuleName, IncludePaths, Macros);
+for_module(ModuleName, IncludePaths, Macros) when is_atom(ModuleName)
->
     [_Exports, _Imports, _Attributes,
      {compile, [_Options, _Version, _Time, {source, Path}]}] =
        ModuleName:module_info(),
-    case for_file(Path, IncludePaths) of
+    case for_file(Path, IncludePaths, Macros) of
        {ok, _Mod} = Res->
            Res;
        _Err ->
@@ -175,13 +182,18 @@
 for_file(SrcFilePath) ->
     for_file(SrcFilePath, []).

+%% @equiv for_file(SrcFilePath, IncludePaths, [])
+for_file(SrcFilePath, IncludePaths) ->
+    for_file(SrcFilePath, IncludePaths, []).
+
 %% @doc Create a meta_mod for a module from its source file.
 %%
-%% @spec for_file(SrcFilePath::string(), IncludePaths::[string()]) ->
+%% @spec for_file(SrcFilePath::string(), IncludePaths::[string()],
+%%   Macros::[{atom(), term()}]) ->
 %%  {ok, meta_mod()} | {error, invalid_module}
-for_file(SrcFilePath, IncludePaths) ->
+for_file(SrcFilePath, IncludePaths, Macros) ->
     case epp:parse_file(SrcFilePath, [filename:dirname(SrcFilePath) |
-                                     IncludePaths], []) of
+                                     IncludePaths], Macros) of
        {ok, Forms} ->
            mod_for_forms(Forms);
        _err ->


======================================================================
diff -aur erlyweb-0.7.2/src/erlyweb/erlyweb_compile.erl
patched_erlyweb-0.7.2/src/erlyweb/erlyweb_compile.erl
--- erlyweb-0.7.2/src/erlyweb/erlyweb_compile.erl       2008-05-31
21:34:16.000000000 -0400
+++ patched_erlyweb-0.7.2/src/erlyweb/erlyweb_compile.erl       2008-09-06
15:49:59.000000000 -0400
@@ -5,7 +5,7 @@

 %% For license information see LICENSE.txt
 -module(erlyweb_compile).
--export([compile/2, get_app_data_module/1, compile_file/5]).
+-export([compile/2, get_app_data_module/1, compile_file/5,
compile_file/6]).

 -include_lib("kernel/include/file.hrl").

@@ -24,6 +24,13 @@
                  Other -> lists:reverse([$/ | Other])
              end,

+    Macros =
+       lists:foldl(
+         fun({d, M}, Acc) -> [{M, true} | Acc];
+            ({d, M, V}, Acc) -> [{M, V} | Acc];
+            (_, Acc) -> Acc
+         end, [], Options),
+
     IncludePaths =
        lists:foldl(
          fun({i, [$/ | _] = Path}, Acc) ->
@@ -82,7 +89,7 @@
     AppControllerFilePath = AppDir1 ++ "src/" ++ AppControllerFile,
     case compile_file(AppControllerFilePath,
                      AppControllerStr, ".erl", undefined,
-                     LastCompileTimeInSeconds, Options3, IncludePaths) of
+                     LastCompileTimeInSeconds, Options3, IncludePaths, Macros) 
of
        {ok, _} -> ok;
        {ok, _, _, _} -> ok;
        ok -> ok;
@@ -101,7 +108,7 @@
                  if FileName =/= AppControllerFilePath ->
                          compile_component_file(
                            ComponentsDir, http_util:to_lower(FileName),
-                           LastCompileTimeInSeconds, Options3, IncludePaths,
+                           LastCompileTimeInSeconds, Options3, IncludePaths, 
Macros,
                            Acc);
                     true ->
                          Acc
@@ -119,7 +126,7 @@
                     {value, {erlydb_driver, Drivers}} ->
                         erlydb:code_gen(lists:reverse(Models),
                                         Drivers,
-                                        Options3, IncludePaths);
+                                        Options3, IncludePaths, Macros);
                     false -> {error, missing_erlydb_driver_option}
                 end
        end,
@@ -315,7 +322,7 @@


 compile_component_file(ComponentsDir, FileName,
LastCompileTimeInSeconds,
-                      Options, IncludePaths, {ComponentTree, Models} = Acc) ->
+                      Options, IncludePaths, Macros, {ComponentTree, Models} =
Acc) ->
     BaseName = filename:rootname(filename:basename(FileName)),
     Extension = filename:extension(FileName),
     BaseNameTokens = string:tokens(BaseName, "_"),
@@ -332,7 +339,7 @@
                   other
           end,
     case {compile_file(FileName, BaseName, Extension, Type,
-                      LastCompileTimeInSeconds, Options, IncludePaths),
+                      LastCompileTimeInSeconds, Options, IncludePaths, Macros),
          Type} of
        {{ok, Module}, controller} ->
            [{exports, Exports} | _] =
@@ -362,11 +369,11 @@
     end.

 compile_file(_FileName, [$. | _] = BaseName, _Extension, _Type,
-_LastCompileTimeInSeconds, _Options, _IncludePaths) ->
+_LastCompileTimeInSeconds, _Options, _IncludePaths, _Macros) ->
     ?Debug("Ignoring file ~p", [BaseName]),
     {ok, ignore};
 compile_file(FileName, BaseName, Extension, Type,
-            LastCompileTimeInSeconds, Options, IncludePaths) ->
+            LastCompileTimeInSeconds, Options, IncludePaths, Macros) ->
     case should_compile(FileName,BaseName,LastCompileTimeInSeconds)
of
         true ->
             case Extension of
@@ -378,7 +385,7 @@
                 ".erl" ->
                     ?Debug("Compiling Erlang file ~p", [BaseName]),
                     compile_file(FileName, BaseName, Type, Options,
-                                 IncludePaths)
+                                 IncludePaths, Macros)
             end;
         false ->
             ok;
@@ -387,7 +394,10 @@
     end.

 compile_file(FileName, BaseName, Type, Options, IncludePaths) ->
-    case smerl:for_file(FileName, IncludePaths) of
+    compile_file(FileName, BaseName, Type, Options, IncludePaths,
[]).
+
+compile_file(FileName, BaseName, Type, Options, IncludePaths, Macros)
->
+    case smerl:for_file(FileName, IncludePaths, Macros) of
        {ok, M1} ->
            M2 = add_forms(Type, BaseName, M1),
            case smerl:compile(M2, Options) of


======================================================================
diff -aur erlyweb-0.7.2/src/erlydb/erlydb.erl patched_erlyweb-0.7.2/
src/erlydb/erlydb.erl
--- erlyweb-0.7.2/src/erlydb/erlydb.erl 2007-12-25 04:34:26.000000000
-0500
+++ patched_erlyweb-0.7.2/src/erlydb/erlydb.erl 2008-09-06
15:54:39.000000000 -0400
@@ -1,7 +1,7 @@
 %% @author Yariv Sadan <[EMAIL PROTECTED]> [http://yarivsblog.com]
 %% @copyright Yariv Sadan 2006-2007
 %% @doc ErlyDB: The Erlang Twist on Database Abstraction.
-%%
+%%
 %% == Contents ==
 %%
 %% [EMAIL PROTECTED] Introduction}<br/>
@@ -95,7 +95,8 @@
     start/2,
     code_gen/2,
     code_gen/3,
-    code_gen/4]).
+    code_gen/4,
+    code_gen/5]).

 -import(erlyweb_util, [log/5]).
 -define(Debug(Msg, Params), log(?MODULE, ?LINE, debug, Msg, Params)).
@@ -149,6 +150,10 @@
 code_gen(Modules, Drivers, Options) ->
     code_gen(Modules, Drivers, Options, []).

+%% @equiv code_gen(Modules, Drivers, Options, IncludePaths, [])
+code_gen(Modules, Drivers, Options, IncludePaths) ->
+    code_gen(Modules, Drivers, Options, IncludePaths, []).
+
 %% @doc Generate code for the list of modules using the provided
drivers.
 %%
 %% If you're using ErlyWeb, you shouldn't need to call this function
directly.
@@ -214,6 +219,11 @@
 %% Additional include paths that will be used to search for header
files
 %% when compiling the modules.
 %%
+%% ==== Macros ====
+%%
+%% Macro definitions that will be used for conditional compilation.
These are
+%% represented in the same way as 'PredefMacros' in epp:parse_file/
2.
+%%
 %% === Examples ===
 %%
 %% Generate code for "musician.erl" using the MySQL driver. Only the
default
@@ -276,17 +286,17 @@
 %% a 'driver' option or only a 'pool_id' option.
 %%
 %% @spec code_gen([Module::atom() | string()], [driver()] | driver(),
-%%   Options::[term()], [IncludePath::string()]) ->
+%%   Options::[term()], [IncludePath::string()], [Macro::{atom(),
term()}]) ->
 %%    ok | {error, Err}
 %% @type driver() = Driver::atom() |
 %%    {Driver::atom(),  DriverOptions::proplist()} |
 %%   {Driver::atom(), DriverOptions::proplist(), [pool()]}
 %% @type pool() = PoolId::atom() | {default, PoolId::atom()}
-code_gen(_Modules, [], _Options, _IncludePaths) ->
+code_gen(_Modules, [], _Options, _IncludePaths, _Macros) ->
     exit(no_drivers_specified);
-code_gen(Modules, Driver, Options, IncludePaths) when not
is_list(Driver) ->
-    code_gen(Modules, [Driver], Options, IncludePaths);
-code_gen(Modules, Drivers, Options, IncludePaths) ->
+code_gen(Modules, Driver, Options, IncludePaths, Macros) when not
is_list(Driver) ->
+    code_gen(Modules, [Driver], Options, IncludePaths, Macros);
+code_gen(Modules, Drivers, Options, IncludePaths, Macros) ->

     %% Normalize the driver tuples.
     DriverTuples =
@@ -360,12 +370,12 @@
       fun(Module) ->
              DefaultDriverMod = element(1, hd(DriverTuples)),
              gen_module_code(Module, DefaultDriverMod, DriversData,
-                                 Options, IncludePaths)
+                                 Options, IncludePaths, Macros)
       end, Modules).

 gen_module_code(ModulePath, DefaultDriverMod,
-              DriversData, Options, IncludePaths) ->
-    case smerl:for_module(ModulePath, IncludePaths) of
+              DriversData, Options, IncludePaths, Macros) ->
+    case smerl:for_module(ModulePath, IncludePaths, Macros) of
        {ok, C1} ->
            C2 = preprocess_and_compile(C1),
            Module = smerl:get_module(C2),




--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"erlyweb" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/erlyweb?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to