%%%------------------------------------------------------------------- %%% File : dtl_server.erl %%% Author : Liu Yubao %%% Description : A server to compile Django template files %%% Created : 28 Jan 2009 by Liu Yubao %%% %%% Copyright (c) 2009, Liu Yubao. All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% * Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% %%% * Redistributions in binary form must reproduce the above %%% copyright notice, this list of conditions and the following %%% disclaimer in the documentation and/or other materials %%% provided with the distribution. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND %%% CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, %%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE %%% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, %%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT %%% LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF %%% USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED %%% AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT %%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN %%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE %%% POSSIBILITY OF SUCH DAMAGE. %%%------------------------------------------------------------------- -module(dtl_server). -author('yubao.liu@gmail.com'). -behaviour(gen_server). %% API -export([start_link/0, start/0, stop/0, lookup/1, make/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -record(state, {id}). -record(modinfo, {filepath, modname, mtime, source, dependencies}). %%==================================================================== %% API %%==================================================================== lookup(FilePath) -> case ets:lookup(?MODULE, FilePath) of [ModInfo] -> ModInfo#modinfo.modname; [] -> {ok, Module} = gen_server:call(?MODULE, {compile, FilePath}), Module end. make() -> gen_server:call(?MODULE, make). start() -> start_link(). stop() -> gen_server:call(?MODULE, stop). %%-------------------------------------------------------------------- %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the server %%-------------------------------------------------------------------- start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- init([]) -> ets:new(?MODULE, [set, protected, named_table, {keypos,2}]), {ok, #state{id=1}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call({compile, FilePath}, _From, State) -> #state{id=Id} = State, Time = filelib:last_modified(FilePath), case ets:lookup(?MODULE, FilePath) of [ModInfo] -> Module = ModInfo#modinfo.modname, Id2 = Id; [] -> Name = lists:flatten(io_lib:format("dtl_m_~b", [State#state.id])), Module = list_to_atom(Name), Id2 = Id + 1 end, case erlydtl:compile(FilePath, Module) of ok -> ets:insert(?MODULE, #modinfo{filepath=FilePath, modname=Module, mtime=Time, source=Module:source(), dependencies=Module:dependencies()}), {reply, {ok, Module}, State#state{id=Id2}}; Err -> {reply, Err, State#state{id=Id2}} end; handle_call(make, _From, State) -> Result = lists:foldl(fun make_one_template/2, [], ets:match(?MODULE, '$1')), {reply, {ok, Result}, State}. make_one_template([ModInfo], Acc) -> #modinfo{filepath=FilePath, modname=Module, source=Source, dependencies=Dependencies} = ModInfo, Time = filelib:last_modified(FilePath), case erlydtl:compile(FilePath, Module) of ok -> Source2 = Module:source(), Dependencies2 = Module:dependencies(), case {Source2, Dependencies2} of {Source, Dependencies} -> Msg = "up to date"; _ -> Msg = "recompiled" end, ets:insert(?MODULE, ModInfo#modinfo{ mtime=Time, source=Source2, dependencies=Dependencies2}); {error, Msg} -> void end, [{FilePath, Module, Time, Msg} | Acc]. %%-------------------------------------------------------------------- %% Function: handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() %% Description: This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any necessary %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ets:delete(?MODULE), ok. %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%--------------------------------------------------------------------