| % Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| % use this file except in compliance with the License. You may obtain a copy of |
| % the License at |
| % |
| % http://www.apache.org/licenses/LICENSE-2.0 |
| % |
| % Unless required by applicable law or agreed to in writing, software |
| % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| % License for the specific language governing permissions and limitations under |
| % the License. |
| |
| %% @doc Saves a Key/Value pair to a ini file. The Key consists of a Section |
| %% and Option combination. If that combination is found in the ini file |
| %% the new value replaces the old value. If only the Section is found the |
| %% Option and value combination is appended to the Section. If the Section |
| %% does not yet exist in the ini file, it is added and the Option/Value |
| %% pair is appended. |
| %% @see couch_config |
| |
| -module(couch_config_writer). |
| -include("couch_db.hrl"). |
| |
| -export([save_to_file/2]). |
| |
| %% @spec save_to_file( |
| %% Config::{{Section::string(), Option::string()}, Value::string()}, |
| %% File::filename()) -> ok |
| %% @doc Saves a Section/Key/Value triple to the ini file File::filename() |
| save_to_file({{Section, Option}, Value}, File) -> |
| |
| ?LOG_DEBUG("saving to file '~s', Config: '~p'", [File, {{Section, Option}, Value}]), |
| |
| % open file and create a list of lines |
| {ok, Stream} = file:read_file(File), |
| OldFileContents = binary_to_list(Stream), |
| Lines = re:split(OldFileContents, "\r\n|\n|\r|\032", [{return, list}]), |
| |
| % prepare input variables |
| SectionName = "[" ++ Section ++ "]", |
| OptionList = Option, |
| |
| % produce the contents for the config file |
| NewFileContents = |
| case {NewFileContents2, DoneOptions} = save_loop({{SectionName, OptionList}, Value}, Lines, "", "", []) of |
| % we didn't change anything, that means we couldn't find a matching |
| % [ini section] in which case we just append a new one. |
| {OldFileContents, DoneOptions} -> |
| % but only if we haven't actually written anything. |
| case lists:member(OptionList, DoneOptions) of |
| true -> OldFileContents; |
| _ -> append_new_ini_section({{SectionName, OptionList}, Value}, OldFileContents) |
| end; |
| _ -> |
| NewFileContents2 |
| end, |
| |
| ok = file:write_file(File, list_to_binary(NewFileContents)), |
| ok. |
| |
| %% @doc Iterates over the lines of an ini file and replaces or adds a new |
| %% configuration directive. |
| save_loop({{Section, Option}, Value}, [Line|Rest], OldCurrentSection, Contents, DoneOptions) -> |
| |
| % if we find a new [ini section] (Section), save that for reference |
| NewCurrentSection = parse_module(Line, OldCurrentSection), |
| % if the current Section is the one we want to change, try to match |
| % each line with the Option |
| NewContents = |
| case NewCurrentSection of |
| Section -> |
| case OldCurrentSection of |
| NewCurrentSection -> % we already were in [Section] |
| case lists:member(Option, DoneOptions) of |
| true -> % we already replaced Option, do nothing |
| DoneOptions2 = DoneOptions, |
| Line; |
| _ -> % we haven't written our Option yet |
| case parse_variable(Line, Option, Value) of |
| nomatch -> |
| DoneOptions2 = DoneOptions, |
| Line; |
| NewLine -> |
| DoneOptions2 = [Option|DoneOptions], |
| NewLine |
| end |
| end; |
| _ -> % we got into a new [section] |
| {NewLine, DoneOptions2} = append_var_to_section( |
| {{Section, Option}, Value}, |
| Line, |
| OldCurrentSection, |
| DoneOptions), |
| NewLine |
| end; |
| _ -> % we are reading [NewCurrentSection] |
| {NewLine, DoneOptions2} = append_var_to_section( |
| {{Section, Option}, Value}, |
| Line, |
| OldCurrentSection, |
| DoneOptions), |
| NewLine |
| end, |
| % clumsy way to only append a newline character if the line is not empty. We need this to |
| % avoid having a newline inserted at the top of the target file each time we save it. |
| Contents2 = case Contents of "" -> ""; _ -> Contents ++ "\n" end, |
| % go to next line |
| save_loop({{Section, Option}, Value}, Rest, NewCurrentSection, Contents2 ++ NewContents, DoneOptions2); |
| |
| save_loop({{Section, Option}, Value}, [], OldSection, NewFileContents, DoneOptions) -> |
| case lists:member(Option, DoneOptions) of |
| % append Deferred Option |
| false when Section == OldSection -> |
| {NewFileContents ++ "\n" ++ Option ++ " = " ++ Value ++ "\n", DoneOptions}; |
| % we're out of new lines, just return the new file's contents |
| _ -> {NewFileContents, DoneOptions} |
| end. |
| |
| append_new_ini_section({{SectionName, Option}, Value}, OldFileContents) -> |
| OldFileContents ++ "\n" ++ SectionName ++ "\n" ++ Option ++ " = " ++ Value ++ "\n". |
| |
| append_var_to_section({{Section, Option}, Value}, Line, OldCurrentSection, DoneOptions) -> |
| case OldCurrentSection of |
| Section -> % append Option to Section |
| case lists:member(Option, DoneOptions) of |
| false -> |
| {Option ++ " = " ++ Value ++ "\n\n" ++ Line, [Option|DoneOptions]}; |
| _ -> |
| {Line, DoneOptions} |
| end; |
| _ -> |
| {Line, DoneOptions} |
| end. |
| |
| %% @spec parse_module(Line::string(), OldSection::string()) -> string() |
| %% @doc Tries to match a line against a pattern specifying a ini module or |
| %% section ("[Section]"). Returns OldSection if no match is found. |
| parse_module(Line, OldSection) -> |
| case re:run(Line, "^\\[([a-zA-Z0-9\_-]*)\\]$", [{capture, first}]) of |
| nomatch -> |
| OldSection; |
| {match, [{Start, Length}]} -> |
| string:substr(Line, Start+1, Length) |
| end. |
| |
| %% @spec parse_variable(Line::string(), Option::string(), Value::string()) -> |
| %% string() | nomatch |
| %% @doc Tries to match a variable assignment in Line. Returns nomatch if the |
| %% Option is not found. Returns a new line composed of the Option and |
| %% Value otherwise. |
| parse_variable(Line, Option, Value) -> |
| case re:run(Line, "^" ++ Option ++ "\s?=", [{capture, none}]) of |
| nomatch -> |
| nomatch; |
| match -> |
| Option ++ " = " ++ Value |
| end. |