diff --git a/sync.sample.config b/erlSync.sample.config similarity index 62% rename from sync.sample.config rename to erlSync.sample.config index bcf4bb9..8652596 100644 --- a/sync.sample.config +++ b/erlSync.sample.config @@ -1,11 +1,25 @@ -%% vim: ts=4 sw=4 et ft=erlang [ - {sync, [ + {erlSync, [ + {moduleTime, 10000}, - %% growl: Desktop notifications - %% valid values: all | none | [success | warning | error] - %% default: all - {growl, all}, + {srcDirTime, 10000}, + + {srcFileTime, 5000}, + + {compareBeamTime, 2000}, + + {compareSrcFileTime, 1000}, + + {file_variables, "-*- mode: compilation; mode: auto-revert; buffer-read-only: true; auto-revert-interval: 0.5 -*-\n\n"}, + + %% Temp file to write output messages. + {out_file, "/tmp/erlSync.out"}, + + %% List of modules to be excluded from scanning. While using rebar + %% it's not very useful to specify the excludes here as every + %% get/update-deps will override the settings. Instead specify + %% excluded stuff in the node's config file. + {excluded_modules, []}, %% log: Console notifications %% valid values: all | none | [success | warnings | errors] @@ -24,7 +38,7 @@ %% default: fix {non_descendants, fix}, - %% whitelisted_modules: Sync only these modules + %% whitelisted_modules: erlSync only these modules %% default: [] {whitelisted_modules, []}, @@ -34,7 +48,7 @@ %% executable: Identify the program that you want run by the "growl" notifications %% valid values: auto | notifu | 'notify-send' | growlnotify | emacsclient | notification_center - %% * auto = allow sync to autodetect which program to run + %% * auto = allow erlSync to autodetect which program to run %% * growlnotify = Use Growl for Mac %% * notification_center = Use OSX Notification Center %% * 'notify-send' = Use libnotify for Linux diff --git a/src/erlSync.app.src b/src/erlSync.app.src index bdbf89f..f7dc350 100644 --- a/src/erlSync.app.src +++ b/src/erlSync.app.src @@ -13,7 +13,7 @@ %% http://www.gnu.org/software/emacs/manual/html_node/emacs/File-Variables.html#File-Variables {file_variables, "-*- mode: compilation; mode: auto-revert; buffer-read-only: true; auto-revert-interval: 0.5 -*-\n\n"}, %% Temp file to write output messages. - {out_file, "/tmp/sync.out"}, + {out_file, "/tmp/erlSync.out"}, %% List of modules to be excluded from scanning. While using rebar %% it's not very useful to specify the excludes here as every %% get/update-deps will override the settings. Instead specify diff --git a/src/erlSync.erl b/src/erlSync.erl index 1ea389f..7842317 100644 --- a/src/erlSync.erl +++ b/src/erlSync.erl @@ -5,18 +5,14 @@ stop/0, run/0, pause/0, - unpause/0, - patch/0, info/0, log/1, log/0, onsync/0, - onsync/1 + onsync/1, + swSyncNode/1 ]). --include_lib("kernel/include/file.hrl"). --define(SERVER, ?MODULE). --define(PRINT(Var), io:format("DEBUG: ~p:~p - ~p~n~n ~p~n~n", [?MODULE, ?LINE, ??Var, Var])). -define(VALID_GROWL_OR_LOG(X), is_boolean(X); is_list(X); X == all; X == none; X == skip_success). start() -> @@ -28,37 +24,35 @@ stop() -> run() -> case start() of {ok, _Started} -> + esScanner:unpause(), + esScanner:rescan(), ok; {error, _Reason} -> - esScanner:unpause(), - esScanner:rescan() + io:format("Err") end. -patch() -> - run(), - esScanner:enable_patching(), - ok. - pause() -> esScanner:pause(). -unpause() -> - esScanner:unpause(). +swSyncNode(IsSync) -> + run(), + esScanner:swSyncNode(IsSync), + ok. info() -> esScanner:info(). log(Val) when ?VALID_GROWL_OR_LOG(Val) -> - esScanner:set_log(Val). + esScanner:setLog(Val). log() -> - esScanner:get_log(). + esScanner:getLog(). onsync(Fun) -> - esOptions:set_onsync(Fun). + esScanner:setOnsync(Fun). onsync() -> - esOptions:get_onsync(). + esScanner:getOnsync(). diff --git a/src/sync/esScanner.erl b/src/sync/esScanner.erl index 6b9e322..770526b 100644 --- a/src/sync/esScanner.erl +++ b/src/sync/esScanner.erl @@ -1,18 +1,21 @@ -module(esScanner). -behaviour(gen_ipc). --compile([export_all, nowarn_export_all]). +-compile(inline). +-compile({inline_size, 128}). %% API -export([ start_link/0, rescan/0, info/0, - enable_patching/0, + swSyncNode/1, pause/0, unpause/0, - set_log/1, - get_log/0 + getOnsync/0, + setOnsync/1, + setLog/1, + getLog/0 ]). %% gen_ipc callbacks @@ -30,11 +33,17 @@ -define(LOG_OR_GROWL_ON(Val), Val == true;Val == all;Val == skip_success;is_list(Val), Val =/= []). -define(LOG_OR_GROWL_OFF(Val), Val == false;F == none;F == []). +-define(gTimeout(Type, Time), {{gTimeout, Type}, Time, Type}). + +-define(esCfgSync, esCfgSync). + +-define(Log, log). -define(moduleTime, moduleTime). -define(srcDirTime, srcDirTime). -define(srcFileTime, srcFileTime). -define(compareBeamTime, compareBeamTime). -define(compareSrcFileTime, compareSrcFileTime). +-define(CfgList, [{?Log, all}, {?moduleTime, 30000}, {?srcDirTime, 5000}, {?srcFileTime, 5000}, {?compareBeamTime, 3000}, {?compareSrcFileTime, 3000}]). -type timestamp() :: file:date_time() | 0. @@ -66,35 +75,37 @@ unpause() -> ok. pause() -> - esUtils:log_success("Pausing Sync. Call sync:go() to restart~n"), + esUtils:log_success("Pausing erlSync. Call sync:go() to restart~n"), gen_ipc:cast(?SERVER, miPause), ok. info() -> - io:format("Sync Info...~n"), - gen_ipc:cast(?SERVER, info), + io:format("erlSync Info...~n"), + gen_ipc:call(?SERVER, miInfo), ok. -set_log(T) when ?LOG_OR_GROWL_ON(T) -> +setLog(T) when ?LOG_OR_GROWL_ON(T) -> esUtils:setEnv(log, T), + loadCfg(), esUtils:log_success("Console Notifications Enabled~n"), ok; -set_log(F) when ?LOG_OR_GROWL_OFF(F) -> - esUtils:log_success("Console Notifications Disabled~n"), +setLog(F) when ?LOG_OR_GROWL_OFF(F) -> esUtils:setEnv(log, none), + loadCfg(), + esUtils:log_success("Console Notifications Disabled~n"), ok. -get_log() -> - esUtils:getEnv(log, all). +getLog() -> + ?esCfgSync:getv(log). -enable_patching() -> - gen_ipc:cast(?SERVER, enable_patching), +swSyncNode(IsSync) -> + gen_ipc:cast(?SERVER, {miSyncNode, IsSync}), ok. -get_onsync() -> +getOnsync() -> gen_ipc:call(?SERVER, miGetOnsync). -set_onsync(Fun) -> +setOnsync(Fun) -> gen_ipc:call(?SERVER, {miSetOnsync, Fun}). @@ -107,6 +118,7 @@ init([]) -> erlang:process_flag(trap_exit, true), rescan(), startup(), + loadCfg(), {ok, running, #state{}}. handleCall(miGetOnsync, _, State, _From) -> @@ -115,13 +127,16 @@ handleCall(miGetOnsync, _, State, _From) -> handleCall({miSetOnsync, Fun}, _, State, _From) -> State2 = State#state{onsyncFun = Fun}, {reply, ok, State2}; + +handleCall(miInfo, _, State, _Form) -> + {reply, {erlang:get(), State}, State}; handleCall(_Request, _, _State, _From) -> keepStatusState. handleCast(miPause, _, State) -> {nextStatus, pause, State}; handleCast(miUnpause, _, State) -> - %% TODO restart + rescan(), {nextStatus, running, State}; handleCast(miCollMods, running, State) -> % Get a list of all loaded non-system modules. @@ -131,9 +146,8 @@ handleCast(miCollMods, running, State) -> CollModules = collMods(AllModules), %% Schedule the next interval... - Time = esUtils:getEnv(?moduleTime, 30000), - - {keepStatus, State#state{modules = CollModules}, [{{gTimeout, miCollMods}, Time, miCollMods}]}; + Time = ?esCfgSync:getv(?moduleTime), + {keepStatus, State#state{modules = CollModules}, [?gTimeout(miCollMods, Time)]}; handleCast(miCollSrcDirs, running, State) -> {USortedSrcDirs, USortedHrlDirs} = @@ -145,8 +159,9 @@ handleCast(miCollSrcDirs, running, State) -> {ok, {replace, DirsAndOpts}} -> collSrcDirs(State, [], dirs(DirsAndOpts)) end, - Time = esUtils:getEnv(?srcDirTime, 30000), - {keepStatus, State#state{srcDirs = USortedSrcDirs, hrlDirs = USortedHrlDirs}, [{{gTimeout, miCollSrcDirs}, Time, miCollSrcDirs}]}; + + Time = ?esCfgSync:getv(?srcDirTime), + {keepStatus, State#state{srcDirs = USortedSrcDirs, hrlDirs = USortedHrlDirs}, [?gTimeout(miCollSrcDirs, Time)]}; handleCast(miCollSrcFiles, running, State) -> %% For each source dir, get a list of source files... @@ -164,9 +179,9 @@ handleCast(miCollSrcFiles, running, State) -> HrlFiles = lists:usort(lists:foldl(FHrl, [], State#state.hrlDirs)), %% Schedule the next interval... - Time = esUtils:getEnv(?srcFileTime, 5000), + Time = ?esCfgSync:getv(?srcFileTime), %% Return with updated files... - {keepStatus, State#state{srcFiles = ErlFiles, hrlFiles = HrlFiles}, [{{gTimeout, miCollSrcFiles}, Time, miCollSrcFiles}]}; + {keepStatus, State#state{srcFiles = ErlFiles, hrlFiles = HrlFiles}, [?gTimeout(miCollSrcFiles, Time)]}; handleCast(miCompareBeams, running, #state{onsyncFun = OnsyncFun} = State) -> %% Create a list of beam file lastmod times, but filter out modules not having @@ -190,8 +205,8 @@ handleCast(miCompareBeams, running, #state{onsyncFun = OnsyncFun} = State) -> %% the beam... reloadChangedMod(State#state.beamTimes, NewBeamLastMod, State#state.patching, OnsyncFun, []), - Time = esUtils:getEnv(?compareBeamTime, 2000), - {keepStatus, State#state{beamTimes = NewBeamLastMod}, [{{gTimeout, miCompareBeams}, Time, miCompareBeams}]}; + Time = ?esCfgSync:getv(?compareBeamTime), + {keepStatus, State#state{beamTimes = NewBeamLastMod}, [?gTimeout(miCompareBeams, Time)]}; handleCast(miCompareSrcFiles, running, State) -> %% Create a list of file lastmod times... @@ -207,8 +222,8 @@ handleCast(miCompareSrcFiles, running, State) -> %% Schedule the next interval... - Time = esUtils:getEnv(?compareSrcFileTime, 2000), - {keepStatus, State#state{srcFileTimes = NewSrcFileLastMod}, [{{gTimeout, miCompareSrcFiles}, Time, miCompareSrcFiles}]}; + Time = ?esCfgSync:getv(?compareSrcFileTime), + {keepStatus, State#state{srcFileTimes = NewSrcFileLastMod}, [?gTimeout(miCompareSrcFiles, Time)]}; handleCast(miCompareHrlFiles, running, State) -> %% Create a list of file lastmod times... @@ -223,18 +238,16 @@ handleCast(miCompareHrlFiles, running, State) -> recompileChangeHrlFile(State#state.hrlFileTimes, NewHrlFileLastMod, State#state.srcFiles, State#state.patching), %% Schedule the next interval... - Time = esUtils:getEnv(?compareSrcFileTime, 2000), - {keepStatus, State#state{hrlFileTimes = NewHrlFileLastMod}, [{{gTimeout, miCompareHrlFiles}, Time, miCompareHrlFiles}]}; - -handleCast(info, _, State) -> - io:format("Modules: ~p~n", [State#state.modules]), - io:format("Source Dirs: ~p~n", [State#state.srcDirs]), - io:format("Source Files: ~p~n", [State#state.srcFiles]), - {noreply, State}; + Time = ?esCfgSync:getv(?compareSrcFileTime), + {keepStatus, State#state{hrlFileTimes = NewHrlFileLastMod}, [?gTimeout(miCompareHrlFiles, Time)]}; -handleCast(enable_patching, _, State) -> - NewState = State#state{patching = true}, - {noreply, NewState}; +handleCast({miSyncNode, IsSync}, _, State) -> + case IsSync of + true -> + {keepStatus, State#state{patching = true}}; + _ -> + {keepStatus, State#state{patching = false}} + end; handleCast(_Msg, _, _State) -> keepStatusState. @@ -263,29 +276,11 @@ dirs(DirsAndOpts) -> Dir end || {Dir, Opts} <- DirsAndOpts]. -handle_info(_Info, State) -> - {noreply, State}. terminate(_Reason, _Status, _State) -> ok. %%% PRIVATE FUNCTIONS %%% - -schedule_cast(Msg, Default, Timers) -> - %% Cancel the old timer... - TRef = proplists:get_value(Msg, Timers), - timer:cancel(TRef), - - %% Lookup the interval... - IntervalKey = list_to_atom(atom_to_list(Msg) ++ "_interval"), - Interval = esUtils:getEnv(IntervalKey, Default), - - %% Schedule the call... - {ok, NewTRef} = timer:apply_after(Interval, gen_ipc, cast, [?SERVER, Msg]), - - %% Return the new timers structure... - lists:keystore(Msg, 1, Timers, {Msg, NewTRef}). - reloadChangedMod([{Module, LastMod} | T1], [{Module, LastMod} | T2], EnablePatching, OnsyncFun, Acc) -> %% Beam hasn't changed, do nothing... reloadChangedMod(T1, T2, EnablePatching, OnsyncFun, Acc); @@ -756,5 +751,9 @@ setOptions(SrcDir, Options) -> startup() -> io:format("Growl notifications disabled~n"). + +loadCfg() -> + KVs = [{Key, esUtils:getEnv(Key, DefVal)} || {Key, DefVal} <- ?CfgList], + esUtils:load(?esCfgSync, KVs). %% ***********************************misc fun end ********************************************* diff --git a/src/sync/esUtils.erl b/src/sync/esUtils.erl index de30e64..1d47996 100644 --- a/src/sync/esUtils.erl +++ b/src/sync/esUtils.erl @@ -1,5 +1,8 @@ -module(esUtils). +-compile(inline). +-compile({inline_size, 128}). + -export([ getSrcDirFromMod/1, getOptionsFromMod/1, @@ -8,6 +11,7 @@ wildcard/2, getEnv/2, setEnv/2, + load/2, getSystemModules/0 ]). @@ -181,7 +185,6 @@ normalizeCaseWindowsDir(Dir) -> {unix, _} -> Dir end. - %% @private This is an attempt to intelligently fix paths in modules when a %% release is moved. Essentially, it takes a module name and its original path %% from Module:module_info(compile), say @@ -250,7 +253,7 @@ getEnv(Var, Default) -> Default end. -%% @private Set a sync environment variable. +%% @private Set a erlSync environment variable. setEnv(Var, Val) -> ok = application:set_env(erlSync, Var, Val). @@ -269,8 +272,8 @@ log_warnings(Message) -> can_we_log(MsgType) -> can_we_notify(log, MsgType). -can_we_notify(GrowlOrLog, MsgType) -> - case esUtils:getEnv(GrowlOrLog, all) of +can_we_notify(_GrowlOrLog, MsgType) -> + case esScanner:getLog() of true -> true; all -> true; none -> false; @@ -335,3 +338,41 @@ getSystemModules() -> end end, lists:flatten([F(X) || X <- Apps]). + +%% 注意 map类型的数据不能当做key +-type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple(). +-type value() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple() | map(). + +-spec load(term(), [{key(), value()}]) -> ok. +load(Module, KVs) -> + Forms = forms(Module, KVs), + {ok, Module, Bin} = compile:forms(Forms), + code:soft_purge(Module), + {module, Module} = code:load_binary(Module, atom_to_list(Module), Bin), + ok. + +forms(Module, KVs) -> + %% -module(Module). + Mod = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]), + %% -export([getv/0]). + ExportList = [erl_syntax:arity_qualifier(erl_syntax:atom(getv), erl_syntax:integer(1))], + Export = erl_syntax:attribute(erl_syntax:atom(export), [erl_syntax:list(ExportList)]), + %% getv(K) -> V + Function = erl_syntax:function(erl_syntax:atom(getv), lookup_clauses(KVs, [])), + [erl_syntax:revert(X) || X <- [Mod, Export, Function]]. + +lookup_clause(Key, Value) -> + Var = erl_syntax:abstract(Key), + Body = erl_syntax:abstract(Value), + erl_syntax:clause([Var], [], [Body]). + +lookup_clause_anon() -> + Var = erl_syntax:variable("_"), + Body = erl_syntax:atom(undefined), + erl_syntax:clause([Var], [], [Body]). + +lookup_clauses([], Acc) -> + lists:reverse(lists:flatten([lookup_clause_anon() | Acc])); +lookup_clauses([{Key, Value} | T], Acc) -> + lookup_clauses(T, [lookup_clause(Key, Value) | Acc]). +