C0 code coverage information

Generated on 2009-08-09 21:22:57 with etap 0.3.4.

Name Total lines Lines of code Total coverage Code coverage
couch_config ?? ?? ??
87% 
....1 % Licensed under the Apache License, Version 2.0 (the "License"); you may not
....2 % use this file except in compliance with the License. You may obtain a copy of
....3 % the License at
....4 %
....5 %   http://www.apache.org/licenses/LICENSE-2.0
....6 %
....7 % Unless required by applicable law or agreed to in writing, software
....8 % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
....9 % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
...10 % License for the specific language governing permissions and limitations under
...11 % the License.
...12 
...13 % Reads CouchDB's ini file and gets queried for configuration parameters.
...14 % This module is initialized with a list of ini files that it consecutively
...15 % reads Key/Value pairs from and saves them in an ets table. If more an one
...16 % ini file is specified, the last one is used to write changes that are made
...17 % with store/2 back to that ini file.
...18 
...19 -module(couch_config).
...20 -behaviour(gen_server).
...21 
...22 -include("couch_db.hrl").
...23 
...24 
...25 -export([start_link/1, stop/0]).
...26 -export([all/0, get/1, get/2, get/3, set/3, set/4, delete/2, delete/3]).
...27 -export([register/1, register/2]).
...28 -export([parse_ini_file/1]).
...29 
...30 -export([init/1, terminate/2, code_change/3]).
...31 -export([handle_call/3, handle_cast/2, handle_info/2]).
...32 
...33 -record(config, {
...34     notify_funs=[],
...35     write_filename=undefined
...36 }).
...37 
...38 
...39 start_link(IniFiles) ->
...40     gen_server:start_link({local, ?MODULE}, ?MODULE, IniFiles, []).
...41 
...42 stop() ->
...43     gen_server:cast(?MODULE, stop).
...44 
...45 
...46 all() ->
...47     lists:sort(gen_server:call(?MODULE, all)).
...48 
...49 
...50 get(Section) when is_binary(Section) ->
...51     ?MODULE:get(?b2l(Section));
...52 get(Section) ->
...53     gen_server:call(?MODULE, {get, Section}).
...54 
...55 get(Section, Key) ->
...56     ?MODULE:get(Section, Key, undefined).
...57 
...58 get(Section, Key, Default) when is_binary(Section) and is_binary(Key) ->
...59     ?MODULE:get(?b2l(Section), ?b2l(Key), Default);
...60 get(Section, Key, Default) ->
...61     gen_server:call(?MODULE, {get, Section, Key, Default}).
...62 
...63 
...64 set(Section, Key, Value) ->
...65     ?MODULE:set(Section, Key, Value, true).
...66 
...67 set(Section, Key, Value, Persist) when is_binary(Section) and is_binary(Key)  ->
...68     ?MODULE:set(?b2l(Section), ?b2l(Key), Value, Persist);
...69 set(Section, Key, Value, Persist) ->
...70     gen_server:call(?MODULE, {set, Section, Key, Value, Persist}).
...71 
...72 
...73 delete(Section, Key) when is_binary(Section) and is_binary(Key) ->
...74     delete(?b2l(Section), ?b2l(Key));
...75 delete(Section, Key) ->
...76     delete(Section, Key, true).
...77 
...78 delete(Section, Key, Persist) when is_binary(Section) and is_binary(Key) ->
...79     delete(?b2l(Section), ?b2l(Key), Persist);
...80 delete(Section, Key, Persist) ->
...81     gen_server:call(?MODULE, {delete, Section, Key, Persist}).
...82 
...83 
...84 register(Fun) ->
...85     ?MODULE:register(Fun, self()).
...86 
...87 register(Fun, Pid) ->
...88     gen_server:call(?MODULE, {register, Fun, Pid}).
...89 
...90 
...91 init(IniFiles) ->
...92     ets:new(?MODULE, [named_table, set, private]),
...93     lists:map(fun(IniFile) ->
...94         {ok, ParsedIniValues} = parse_ini_file(IniFile),
...95         ets:insert(?MODULE, ParsedIniValues)
...96     end, IniFiles),
...97     WriteFile = case length(IniFiles) > 0 of
...98         true -> lists:last(IniFiles);
...99         _ -> undefined
..100     end,
..101     {ok, #config{write_filename=WriteFile}}.
..102 
..103 
..104 terminate(_Reason, _State) ->
..105     ok.
..106 
..107 
..108 handle_call(all, _From, Config) ->
..109     Resp = lists:sort((ets:tab2list(?MODULE))),
..110     {reply, Resp, Config};
..111 handle_call({get, Section}, _From, Config) ->
..112     Matches = ets:match(?MODULE, {{Section, '$1'}, '$2'}),
..113     Resp = [{Key, Value} || [Key, Value] <- Matches],
..114     {reply, Resp, Config};
..115 handle_call({get, Section, Key, Default}, _From, Config) ->
..116     Resp = case ets:lookup(?MODULE, {Section, Key}) of
..117         [] -> Default;
..118         [{_, Match}] -> Match
..119     end,
..120     {reply, Resp, Config};
..121 handle_call({set, Sec, Key, Val, Persist}, _From, Config) ->
..122     true = ets:insert(?MODULE, {{Sec, Key}, Val}),
..123     case {Persist, Config#config.write_filename} of
..124         {true, undefined} ->
..125             ok;
..126         {true, FileName} ->
..127             couch_config_writer:save_to_file({{Sec, Key}, Val}, FileName);
..128         _ ->
..129             ok
..130     end,
..131     [catch F(Sec, Key, Val) || {_Pid, F} <- Config#config.notify_funs],
..132     {reply, ok, Config};
..133 handle_call({delete, Sec, Key, Persist}, _From, Config) ->
..134     true = ets:delete(?MODULE, {Sec,Key}),
..135     case {Persist, Config#config.write_filename} of
..136         {true, undefined} ->
..137             ok;
..138         {true, FileName} ->
..139             couch_config_writer:save_to_file({{Sec, Key}, ""}, FileName);
..140         _ ->
..141             ok
..142     end,
..143     [catch F(Sec, Key, deleted) || {_Pid, F} <- Config#config.notify_funs],
..144     {reply, ok, Config};
..145 handle_call({register, Fun, Pid}, _From, #config{notify_funs=PidFuns}=Config) ->
..146     erlang:monitor(process, Pid),
..147     % convert 1 and 2 arity to 3 arity
..148     Fun2 =
..149     case Fun of
..150         _ when is_function(Fun, 1) ->
..151             fun(Section, _Key, _Value) -> Fun(Section) end;
..152         _ when is_function(Fun, 2) ->
..153             fun(Section, Key, _Value) -> Fun(Section, Key) end;
..154         _ when is_function(Fun, 3) ->
..155             Fun
..156     end,
..157     {reply, ok, Config#config{notify_funs=[{Pid, Fun2} | PidFuns]}}.
..158 
..159 
..160 handle_cast(stop, State) ->
..161     {stop, normal, State};
..162 handle_cast(_Msg, State) ->
..163     {noreply, State}.
..164 
..165 handle_info({'DOWN', _, _, DownPid, _}, #config{notify_funs=PidFuns}=Config) ->
..166     % remove any funs registered by the downed process
..167     FilteredPidFuns = [{Pid,Fun} || {Pid,Fun} <- PidFuns, Pid /= DownPid],
..168     {noreply, Config#config{notify_funs=FilteredPidFuns}}.
..169 
..170 code_change(_OldVsn, State, _Extra) ->
..171     {ok, State}.
..172 
..173 
..174 parse_ini_file(IniFile) ->
..175     IniFilename = couch_util:abs_pathname(IniFile),
..176     IniBin =
..177     case file:read_file(IniFilename) of
..178         {ok, IniBin0} ->
..179             IniBin0;
..180         {error, enoent} ->
..181             Fmt = "Couldn't find server configuration file ~s.",
..182             Msg = ?l2b(io_lib:format(Fmt, [IniFilename])),
..183             ?LOG_ERROR("~s~n", [Msg]),
..184             throw({startup_error, Msg})
..185     end,
..186 
..187     {ok, Lines} = regexp:split(binary_to_list(IniBin), "\r\n|\n|\r|\032"),
..188     {_, ParsedIniValues} =
..189     lists:foldl(fun(Line, {AccSectionName, AccValues}) ->
..190             case string:strip(Line) of
..191             "[" ++ Rest ->
..192                 case regexp:split(Rest, "\\]") of
..193                 {ok, [NewSectionName, ""]} ->
..194                     {NewSectionName, AccValues};
..195                 _Else -> % end bracket not at end, ignore this line
..196                     {AccSectionName, AccValues}
..197                 end;
..198             ";" ++ _Comment ->
..199                 {AccSectionName, AccValues};
..200             Line2 ->
..201                 case regexp:split(Line2, "\s?=\s?") of
..202                 {ok, [_SingleElement]} -> % no "=" found, ignore this line
..203                     {AccSectionName, AccValues};
..204                 {ok, [""|_LineValues]} -> % line begins with "=", ignore
..205                     {AccSectionName, AccValues};
..206                 {ok, [ValueName|LineValues]} -> % yeehaw, got a line!
..207                     RemainingLine = couch_util:implode(LineValues, "="),
..208                     % removes comments
..209                     case regexp:split(RemainingLine, " ;|\t;") of
..210                     {ok, [[]]} ->
..211                         % empty line means delete this key
..212                         ets:delete(?MODULE, {AccSectionName, ValueName}),
..213                         {AccSectionName, AccValues};                        
..214                     {ok, [LineValue | _Rest]} ->
..215                         {AccSectionName,
..216                             [{{AccSectionName, ValueName}, LineValue} | AccValues]}
..217                     end
..218                 end
..219             end
..220         end, {"", []}, Lines),
..221     {ok, ParsedIniValues}.
..222 

Generated using etap 0.3.4.