Browse Source

active v1.9 init

221V 3 years ago
parent
commit
0c82d62f3f
9 changed files with 291 additions and 1 deletions
  1. 1 0
      .gitignore
  2. 15 0
      LICENSE
  3. 45 1
      README.md
  4. 9 0
      ebin/active.app
  5. 10 0
      include/active.hrl
  6. 169 0
      src/active.erl
  7. 6 0
      src/active_app.erl
  8. 20 0
      src/active_events.erl
  9. 16 0
      src/active_sup.erl

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+ebin/*.beam

+ 15 - 0
LICENSE

@@ -0,0 +1,15 @@
+Copyright (c) 2013 Vladimir Kirillov <proger@hackndev.com>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and following permission notice appear in all copies:
+
+Software may only be used for the great good and the true happiness of all sentient beings.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

+ 45 - 1
README.md

@@ -1,2 +1,46 @@
-# active
+ACTIVE - https://github.com/synrc/active v1.9 fork
+======
 
 
+Active is a [sync](https://github.com/rustyio/sync) replacement
+that uses native file-system OS async listeners to compile and
+reload Erlang files, DTL templates and other files. It acts as
+FS subscriber under supervision and uses
+[mad](https://github.com/synrc/mad) under the hood.
+
+Listen Folders
+--------------
+
+### One-level
+
+```erlang
+app(App,["ebin",Module|_]) -> load_ebin(App,Module);
+app(App,["priv"|_]) -> compile(App);
+app(App,["src"|_]) -> compile(App);
+app(_,_)-> ok.
+```
+
+### Two-level
+
+```erlang
+otp(["deps",App|Rest]) -> app(App,Rest);
+otp(["apps",App|Rest]) -> app(App,Rest);
+otp([Some|Path]) -> app(top(),[Some|Path]);
+otp(_) -> ok.
+```
+
+Usage
+-----
+
+On Mac/Linux/Windows just include into your rebar.config:
+
+    {active, ".*", {git, "git://github.com/synrc/active", {tag,"0.5"}}}
+
+NOTE: on Linux please install inotify-tools.
+
+Credits
+-------
+
+* Maxim Sokhatsky
+* Vladimir Kirillov
+
+OM A HUM

+ 9 - 0
ebin/active.app

@@ -0,0 +1,9 @@
+{application, active,
+ [{description, "ACT VXZ Continuous Compilation"},
+  {vsn, "1.9"},
+  {registered, [active_sup]},
+  {applications, [kernel, stdlib, fs]},
+  {modules, [active_app, active_events, active_sup, active]},
+  {mod, { active_app, []}},
+  {env, []}
+ ]}.

+ 10 - 0
include/active.hrl

@@ -0,0 +1,10 @@
+
+-type mf() :: {M :: module(), F :: atom()}.
+-spec subscribe_onload(Function :: mf()) -> ok.
+-spec subscribe_onnew(Function :: mf()) -> ok.
+-spec subscribe(Event :: reloaded | loaded_new, Function :: mf()) -> ok.
+-record(event_state, {
+    event :: reloaded | loaded_new,
+    function :: mf()
+}).
+

+ 169 - 0
src/active.erl

@@ -0,0 +1,169 @@
+-module(active).
+-behaviour(gen_server).
+-define(SERVER, ?MODULE).
+-compile([export_all, nowarn_export_all]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+-record(state, {last, root}).
+
+start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+init([]) ->
+    fs:subscribe(),
+    erlang:process_flag(priority, low),
+    gen_server:cast(self(), recompile_all),
+    {ok, #state{last=fresh, root=fs:path()}}.
+handle_call(_Request, _From, State) -> {reply, ok, State}.
+handle_cast(recompile_all, State) ->
+    compile(top(), ["all"]),
+    {noreply, State}.
+handle_info({_Pid, {fs,file_event}, {Path, Flags}}, #state{root=Root} = State) ->
+    Cur = path_shorten(filename:split(Root)),
+    P = filename:split(Path),
+
+    Result = case lists:prefix(Cur, P) of
+        true ->
+            Components = P -- Cur,
+            %mad:info("event: ~p ~p", [Components, Flags]),
+            path_event(Components, Flags, State);
+        false ->
+            ok
+    end,
+
+    {noreply, State#state{last={event, Path, Flags, Result}}};
+handle_info({load_ebin, Atom}, State) -> do_load_ebin(Atom), {noreply, State#state{last={do_load_ebin, Atom}}};
+handle_info(Info, State) -> {noreply, State#state{last={unk, Info}}}.
+terminate(_Reason, _State) -> ok.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+path_event(C, [E|_Events], _State) when E =:= created; E =:= modified; E =:= renamed ->
+    case path_filter(C) of true -> maybe_otp(C); false -> ignore end;
+path_event(C, [_E|Events], State) -> path_event(C, Events, State);
+path_event(_, [], _State) -> done.
+
+maybe_otp(C) ->
+    case application:get_env(active, handler) of
+         {ok,{M,F}} -> M:F(C);
+                  _ -> otp(C) end.
+
+otp(["deps",App|Rest]) -> maybe_app(App,Rest);
+otp(["apps",App|Rest]) -> maybe_app(App,Rest);
+otp([Some|Path]) -> maybe_app(top(),[Some|Path]);
+otp(_) -> ok.
+
+maybe_app(App, Path) ->
+    EnabledApps = application:get_env(active, apps, undefined),
+    case EnabledApps of
+        undefined ->
+%            mad:info("App ~p Path ~p~n",[App,Path]),
+            app(App, Path);
+        {ok,L} when is_list(L) ->
+            AppAtom = list_to_atom(App),
+            case lists:member(AppAtom, L) of
+                true ->
+%                    mad:info("App ~p Path ~p~n",[App,Path]),
+                    app(App, Path);
+                false ->
+                    skip
+            end
+    end.
+
+app( App,["ebin",Module|_])     -> load_ebin(App,Module);
+app(_App,["priv","fdlink"++_])  -> skip;
+app(_App,["priv","mac"++_])     -> skip;
+app(_App,["priv","windows"++_]) -> skip;
+app(_App,["priv","linux"++_])   -> skip;
+app(_App,["priv","static"|_])   -> skip;
+app( App,["priv"|Rest])         -> compile(App,Rest);
+app( App,["include"|Rest])      -> compile(App,Rest);
+app( App,["src"|Rest])          -> compile(App,Rest);
+app(_,_)-> ok.
+
+top() -> lists:last(filename:split(fs:path())).
+
+compile(App,Rest) ->
+    case lists:last(Rest) of
+         ".#" ++ _ -> skip;
+             _ -> try put(App,updated),
+                      mad_compile:compile(App)
+                catch E:R ->
+                      mad:info("~p", [erlang:get_stacktrace()]),
+                      mad:info("Catch: ~p:~p",[E,R]) end end.
+
+load_ebin(_App, EName) ->
+    case lists:reverse(EName) of
+        "maeb." ++ Tail -> Name = lists:reverse(Tail),
+            LoadRes = do_load_ebin(list_to_atom(lists:flatten(Name))),
+            mad:info("Active: module loaded: ~p~n\n\r", [LoadRes]),
+            active_events:notify_reload(LoadRes);
+        "#aeb." ++ _ -> ok;
+        _ -> mad:info("Active: unknown BEAM file: ~p", [EName]), ok
+    end.
+
+do_load_ebin(Module) ->
+    IsLoaded = case code:is_loaded(Module) of
+                   {file, _} ->
+                       true;
+                   false ->
+                       false
+               end,
+    {Module, Binary, Filename} = code:get_object_code(Module),
+    case code:load_binary(Module, Filename, Binary) of
+        {module, Module} when IsLoaded->
+            {reloaded, Module};
+        {module, Module} when not IsLoaded ->
+            {loaded_new, Module};
+        {error, Reason} ->
+            {load_error, Module, Reason}
+    end.
+
+monitor_handles_renames([renamed|_]) -> true;
+monitor_handles_renames([_|Events]) -> monitor_handles_renames(Events);
+monitor_handles_renames([]) -> false.
+
+monitor_handles_renames() ->
+    case get(monitor_handles_renames) of
+        undefined ->
+            R = monitor_handles_renames(fs:known_events()),
+            put(monitor_handles_renames, R),
+            R;
+        V -> V
+    end.
+
+% ["a", "b", ".."] -> ["a"]
+path_shorten(Coms) -> path_shorten_r(lists:reverse(Coms), [], 0).
+
+path_shorten_r([".."|Rest], Acc, Count) -> path_shorten_r(Rest, Acc, Count + 1);
+path_shorten_r(["."|Rest], Acc, Count) -> path_shorten_r(Rest, Acc, Count);
+path_shorten_r([_C|Rest], Acc, Count) when Count > 0 -> path_shorten_r(Rest, Acc, Count - 1);
+path_shorten_r([C|Rest], Acc, 0) -> path_shorten_r(Rest, [C|Acc], 0);
+path_shorten_r([], Acc, _) -> Acc.
+
+%
+% Filters
+%
+
+path_filter(L) ->
+    not lists:any(fun(E) -> not path_filter_dir(E) end, L)
+        andalso path_filter_file(lists:last(L))
+        andalso path_filter_ext(ext(L)).
+
+ext(L) -> filename:extension(lists:last(L)).
+
+path_filter_dir(".git") -> false;
+path_filter_dir(".hg")  -> false;
+path_filter_dir(".svn") -> false;
+path_filter_dir("CVS")  -> false;
+path_filter_dir("log")  -> false;
+path_filter_dir(_)      -> true.
+
+path_filter_file(".rebarinfo")     -> false;   % new rebars
+path_filter_file("LICENSE")        -> false;
+path_filter_file("4913 (deleted)") -> false;   % vim magical file
+path_filter_file("4913")           -> false;
+path_filter_file(_)                -> true.
+
+path_filter_ext(".app")            -> false;
+path_filter_ext(".jpg")            -> false;
+path_filter_ext(".png")            -> false;
+path_filter_ext(".gif")            -> false;
+path_filter_ext(_)                 -> true.

+ 6 - 0
src/active_app.erl

@@ -0,0 +1,6 @@
+-module(active_app).
+-behaviour(application).
+-export([start/2, stop/1]).
+
+start(_StartType, _StartArgs) -> active_sup:start_link().
+stop(_State) -> ok.

+ 20 - 0
src/active_events.erl

@@ -0,0 +1,20 @@
+-module(active_events).
+-include("active.hrl").
+-behaviour(gen_event).
+-export([start_link/0, subscribe_onload/1, subscribe_onnew/1, notify_reload/1, subscribe/2]).
+-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]).
+
+start_link() -> gen_event:start_link({local, ?MODULE}).
+
+subscribe_onload(Function) -> subscribe(reloaded, Function).
+subscribe_onnew(Function) -> subscribe(loaded_new, Function).
+subscribe(Event, Function) -> ok = gen_event:add_sup_handler(?MODULE, {?MODULE, Event}, [Event, Function]).
+notify_reload(Event) -> gen_event:notify(?MODULE, Event).
+
+init([Event, Function]) -> {ok, #event_state{event = Event, function = Function}}.
+handle_event({Event, Module}, State = #event_state{event = Event, function = {Mod, Fun}}) -> erlang:apply(Mod, Fun, [[Module]]), {ok, State};
+handle_event(_, State) -> {ok, State}.
+handle_call(_Request, _State) -> erlang:error(not_implemented).
+handle_info(_Info, _State) -> erlang:error(not_implemented).
+terminate(_Args, _State) -> ok.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.

+ 16 - 0
src/active_sup.erl

@@ -0,0 +1,16 @@
+-module(active_sup).
+-behaviour(supervisor).
+-export([start_link/0]).
+-export([init/1]).
+-define(CHILD(I, Type, Args), {I, {I, start_link, Args}, permanent, 5000, Type, [I]}).
+
+start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+init([]) -> 
+    {ok, { 
+        {one_for_one, 5, 10}, 
+        [
+            ?CHILD(active, worker, []), 
+            ?CHILD(active_events, worker, [])
+        ]
+    }}.
+