How to read config files on elixir mix project
Asked Answered
M

2

6

I am creating an elixir project to search for patterns in files. I want to store those patterns a config files to allow for easy changes in the app. My first idea is storing those files as exs files in the config folder in the mix project. So, the questions are:

  1. Is there any easy way to store the config in the files a a keyword list?
  2. How would I load it in the app?

I see there are modules like File to read the file, but is there no standard way to parse keyword lists in elixir? I was thinking something similar as the yml files in Rails.

Moniquemonism answered 27/5, 2014 at 19:12 Comment(6)
Why not simply use the configuration provided by Elixir? It is meant to play well with OTP and existing Elixir applications. Why provide something new?Stith
You mean the mix.exs? I thought about it, but for a complex app this can get very crowded, so I thought about splitting it in different files. Is there any resource on the web about how is the correct way of configuring Elixir apps? I am very interested in not reinventing the wheel if there is already a solution in place.Moniquemonism
No, he's talking about the new config/config.exs file introduced in 0.13.2. You can provide the config key in mix.exs, and use interpolation to emulate config by environment.Intake
Yeah, I meant the new config/config.exs. If you get latest Elixir and create a new project, you will see the new file with comments on how to use it.Stith
The docs dont actually say how to access these config values inside of an Elixir program.Dingle
See groups.google.com/forum/#!topic/elixir-lang-talk/kixyuzLFbbwDingle
I
8

You can read keyword lists stored in a *.exs file, using Mix.Config.read(path). For writing Elixir terms to a *.exs file, you can use Inspect.Algebra.to_doc(%Inspect.Opts{pretty: true}) and write the resulting string content to a file using File.write. It's not as well formatted as if you did it by hand, but it's definitely still readable.

If you don't mind using Erlang terms, you can read and write those easily using :file.consult(path) and :file.write_file(:io_lib.fwrite('~p.\n', [config]), path) respectively.

Intake answered 27/5, 2014 at 21:39 Comment(5)
This is nice, but Keyword Lists are a bit too simple for configuration, is there not anything similar that supports Maps?Moniquemonism
The file is just eval'ed. You can have literally anything you want there. Just check how Mix.Config.read/1 is implemented (it is pretty straight-forward).Stith
You mean Mix.Config.read!/1, right? Or have things changed since this answer was posted?Manzo
Be wary that :file.write_file's default encoding is latin1Laroy
Yeah, you can pass [encoding: :utf8] as a third parameter to change that.Intake
C
1

Using Code.eval_file

Adding another option, is to evaluate the file as a code file, using Code.eval_file and get in return the result as an elixir construct.

Config file config1.ex:

%{configKey1: "configValue1", configKey2: "configValue2"} 

Reading the file:

{content, _} = Code.eval_file("config1.ex")

*evaluating a code file has security consideration needs to take in mind.

Regarding using Mix.Config.read! in @bitwalker correct answer

the config file needs to be in a specific format of:

[
   appName: [key1: "val1", key2: "val2"]
]

In the Mix.Config.read code, it try to validate the contents and expect a main keyword list [ {}, {}.. ] which includes keys that has value of type keyword list also.

The code is not long:

 def validate!(config) do
    if is_list(config) do
      Enum.all?(config, fn
        {app, value} when is_atom(app) ->
          if Keyword.keyword?(value) do
            true
          else
            raise ArgumentError,
              "expected config for app #{inspect app} to return keyword list, got: #{inspect value}"
          end
        _ ->
          false
      end)
    else
      raise ArgumentError,
        "expected config file to return keyword list, got: #{inspect config}"
    end
  end

We can circumvent and use a first key which is not atom, and then the validate stops but does not throw:

[
  {"mockFirstKey", "mockValue"},
  myKey1: "myValue1",
  myKey2: "myValue2"
]
Carburetor answered 10/5, 2017 at 10:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.