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 -~----------~----~----~----~------~----~------~--~---
