Skip to content

Commit 860b3cc

Browse files
committed
Introduce IEx.Config to store internal iex state
* All config get/put moved to IEx.Config * after_spawn stored in ETS to prevent config_change deletion * started? checks for IEx.Config process * Return full config on IEx.configuration/1
1 parent bb5e6f6 commit 860b3cc

File tree

9 files changed

+290
-134
lines changed

9 files changed

+290
-134
lines changed

lib/iex/lib/iex.ex

Lines changed: 12 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -273,54 +273,35 @@ defmodule IEx do
273273
274274
"""
275275
def configure(options) do
276-
Enum.each options, fn {k, v} ->
277-
Application.put_env(:iex, k, configure(k, v))
278-
end
279-
end
280-
281-
defp configure(k, v) when k in [:colors, :inspect] and is_list(v) do
282-
Keyword.merge(Application.get_env(:iex, k), v)
283-
end
284-
285-
defp configure(:history_size, v) when is_integer(v) do
286-
v
287-
end
288-
289-
defp configure(k, v) when k in [:default_prompt, :alive_prompt] and is_binary(v) do
290-
v
291-
end
292-
293-
defp configure(k, v) do
294-
raise ArgumentError, "invalid configuration or value for pair #{inspect k} - #{inspect v}"
276+
IEx.Config.configure(options)
295277
end
296278

297279
@doc """
298280
Returns IEx configuration.
299281
"""
300282
def configuration do
301-
Application.get_all_env(:iex)
283+
IEx.Config.configuration()
302284
end
303285

304286
@doc """
305287
Registers a function to be invoked after the IEx process is spawned.
306288
"""
307289
def after_spawn(fun) when is_function(fun) do
308-
Application.put_env(:iex, :after_spawn, [fun|after_spawn])
290+
IEx.Config.after_spawn(fun)
309291
end
310292

311293
@doc """
312294
Returns registered `after_spawn` callbacks.
313295
"""
314296
def after_spawn do
315-
{:ok, list} = Application.fetch_env(:iex, :after_spawn)
316-
list
297+
IEx.Config.after_spawn()
317298
end
318299

319300
@doc """
320301
Returns `true` if IEx was started.
321302
"""
322303
def started? do
323-
Application.get_env(:iex, :started, false)
304+
IEx.Config.started?()
324305
end
325306

326307
@doc """
@@ -329,37 +310,28 @@ defmodule IEx do
329310
ANSI escapes in `string` are not processed in any way.
330311
"""
331312
def color(color, string) do
332-
colors = Application.get_env(:iex, :colors)
333-
334-
if color_enabled?(colors[:enabled]) do
335-
ansi = Keyword.get(colors, color, default_color(color))
336-
IO.iodata_to_binary(IO.ANSI.format_fragment(ansi, true)) <> string <> IO.ANSI.reset
337-
else
338-
string
313+
case IEx.Config.color(color) do
314+
nil ->
315+
string
316+
ansi ->
317+
IO.iodata_to_binary([IO.ANSI.format_fragment(ansi, true), string | IO.ANSI.reset])
339318
end
340319
end
341320

342-
defp color_enabled?(nil), do: IO.ANSI.enabled?
343-
defp color_enabled?(bool) when is_boolean(bool), do: bool
344-
345321
@doc """
346322
Gets the IEx width for printing.
347323
348324
Used by helpers and it has a maximum cap of 80 chars.
349325
"""
350326
def width do
351-
case :io.columns() do
352-
{:ok, width} -> min(width, 80)
353-
{:error, _} -> 80
354-
end
327+
IEx.Config.width()
355328
end
356329

357330
@doc """
358331
Gets the options used for inspecting.
359332
"""
360333
def inspect_opts do
361-
Application.get_env(:iex, :inspect) ++
362-
[width: width(), pretty: true]
334+
IEx.Config.inspect_opts()
363335
end
364336

365337
@doc """
@@ -467,7 +439,6 @@ defmodule IEx do
467439
defp start_iex() do
468440
unless started? do
469441
{:ok, _} = Application.ensure_all_started(:iex)
470-
Application.put_env(:iex, :started, true)
471442
end
472443

473444
:ok
@@ -503,24 +474,4 @@ defmodule IEx do
503474
_ = for fun <- Enum.reverse(after_spawn), do: fun.()
504475
:ok
505476
end
506-
507-
# Used by default on evaluation cycle
508-
defp default_color(:eval_interrupt), do: [:yellow]
509-
defp default_color(:eval_result), do: [:yellow]
510-
defp default_color(:eval_error), do: [:red]
511-
defp default_color(:eval_info), do: [:normal]
512-
defp default_color(:stack_app), do: [:red, :bright]
513-
defp default_color(:stack_info), do: [:red]
514-
515-
# Used by ls
516-
defp default_color(:ls_directory), do: [:blue]
517-
defp default_color(:ls_device), do: [:green]
518-
519-
# Used by ansi docs
520-
defp default_color(:doc_bold), do: [:bright]
521-
defp default_color(:doc_code), do: [:cyan, :bright]
522-
defp default_color(:doc_headings), do: [:yellow, :bright]
523-
defp default_color(:doc_inline_code), do: [:cyan]
524-
defp default_color(:doc_underline), do: [:underline]
525-
defp default_color(:doc_title), do: [:reverse, :yellow, :bright]
526477
end

lib/iex/lib/iex/app.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule IEx.App do
2+
@moduledoc false
3+
4+
use Application
5+
6+
def start(_type, _args) do
7+
import Supervisor.Spec
8+
9+
children = [worker(IEx.Config, [])]
10+
options = [strategy: :one_for_one, name: IEx.Supervisor]
11+
12+
tab = IEx.Config.new()
13+
case Supervisor.start_link(children, options) do
14+
{:ok, pid} ->
15+
{:ok, pid, tab}
16+
{:error, _} = error ->
17+
IEx.Config.delete(tab)
18+
error
19+
end
20+
end
21+
22+
def stop(tab) do
23+
IEx.Config.delete(tab)
24+
end
25+
end

lib/iex/lib/iex/cli.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ defmodule IEx.CLI do
9696
end
9797

9898
def local_start do
99-
IEx.start(config(), {:elixir, :start_cli, []})
99+
IEx.start(options(), {:elixir, :start_cli, []})
100100
end
101101

102102
def remote_start(parent, ref) do
@@ -110,7 +110,7 @@ defmodule IEx.CLI do
110110

111111
defp remote_start_function do
112112
ref = make_ref
113-
config = config()
113+
opts = options()
114114

115115
parent = spawn_link fn ->
116116
receive do
@@ -121,11 +121,11 @@ defmodule IEx.CLI do
121121
end
122122

123123
fn ->
124-
IEx.start(config, {__MODULE__, :remote_start, [parent, ref]})
124+
IEx.start(opts, {__MODULE__, :remote_start, [parent, ref]})
125125
end
126126
end
127127

128-
defp config do
128+
defp options do
129129
[dot_iex_path: find_dot_iex(:init.get_plain_arguments)]
130130
end
131131

lib/iex/lib/iex/config.ex

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
defmodule IEx.Config do
2+
@moduledoc false
3+
4+
@table __MODULE__
5+
@agent __MODULE__
6+
@keys [:colors, :inspect, :history_size, :default_prompt, :alive_prompt]
7+
@colors [:eval_interrupt, :eval_result, :eval_error, :eval_info, :stack_app,
8+
:stack_info, :ls_directory, :ls_device]
9+
10+
def new() do
11+
tab = :ets.new(@table, [:named_table, :public])
12+
true = :ets.insert_new(tab, [after_spawn: []])
13+
tab
14+
end
15+
16+
def delete(__MODULE__) do
17+
:ets.delete(__MODULE__)
18+
end
19+
20+
def configure(options) do
21+
Agent.update(@agent, __MODULE__, :handle_configure, [options])
22+
end
23+
24+
def handle_configure(tab, options) do
25+
options = :lists.ukeysort(1, options)
26+
get_config()
27+
|> Keyword.merge(options, &merge_option/3)
28+
|> put_config()
29+
tab
30+
end
31+
32+
defp get_config() do
33+
Application.get_all_env(:iex)
34+
|> Keyword.take(@keys)
35+
end
36+
37+
defp put_config(config) do
38+
put = fn({key, value}) when key in @keys ->
39+
Application.put_env(:iex, key, value)
40+
end
41+
Enum.each(config, put)
42+
end
43+
44+
defp merge_option(:colors, old, new) when is_list(new), do: Keyword.merge(old, new)
45+
defp merge_option(:inspect, old, new) when is_list(new), do: Keyword.merge(old, new)
46+
defp merge_option(:history_size, _old, new) when is_integer(new), do: new
47+
defp merge_option(:default_prompt, _old, new) when is_binary(new), do: new
48+
defp merge_option(:alive_prompt, _old, new) when is_binary(new), do: new
49+
50+
defp merge_option(k, _old, new) do
51+
raise ArgumentError, "invalid configuration or value for pair #{inspect k} - #{inspect new}"
52+
end
53+
54+
def configuration() do
55+
Keyword.merge(default_config(), get_config(), &merge_option/3)
56+
end
57+
58+
defp default_config() do
59+
Enum.map(@keys, &{&1, default_option(&1)})
60+
end
61+
62+
defp default_option(:colors), do: [{:enabled, IO.ANSI.enabled?} | default_colors()]
63+
defp default_option(:inspect), do: []
64+
defp default_option(:history_size), do: 20
65+
66+
defp default_option(prompt) when prompt in [:default_prompt, :alive_prompt] do
67+
"%prefix(%counter)>"
68+
end
69+
70+
defp default_colors() do
71+
Enum.map(@colors, &{&1, default_color(&1)}) ++ default_doc_colors()
72+
end
73+
74+
defp default_doc_colors() do
75+
Keyword.drop(IO.ANSI.Docs.default_options(), [:enabled, :width])
76+
end
77+
78+
# Used by default on evaluation cycle
79+
defp default_color(:eval_interrupt), do: [:yellow]
80+
defp default_color(:eval_result), do: [:yellow]
81+
defp default_color(:eval_error), do: [:red]
82+
defp default_color(:eval_info), do: [:normal]
83+
defp default_color(:stack_app), do: [:red, :bright]
84+
defp default_color(:stack_info), do: [:red]
85+
86+
# Used by ls
87+
defp default_color(:ls_directory), do: [:blue]
88+
defp default_color(:ls_device), do: [:green]
89+
90+
# Used by ansi docs
91+
defp default_color(doc_color) do
92+
IO.ANSI.Docs.default_options()
93+
|> Keyword.fetch!(doc_color)
94+
end
95+
96+
def color(color) do
97+
colors = Application.get_env(:iex, :colors, [])
98+
if colors_enabled?(colors) do
99+
case Keyword.fetch(colors, color) do
100+
{:ok, value} ->
101+
value
102+
:error ->
103+
default_color(color)
104+
end
105+
else
106+
nil
107+
end
108+
end
109+
110+
defp colors_enabled?(colors \\ Application.get_env(:iex, :colors, [])) do
111+
case Keyword.fetch(colors, :enabled) do
112+
{:ok, enabled} ->
113+
enabled
114+
:error ->
115+
IO.ANSI.enabled?()
116+
end
117+
end
118+
119+
def ansi_docs() do
120+
colors = Application.get_env(:iex, :colors, [])
121+
if enabled = colors_enabled?(colors) do
122+
[width: width(), enabled: enabled] ++ colors
123+
end
124+
end
125+
126+
def inspect_opts() do
127+
inspect_options = Application.get_env(:iex, :inspect, []) ++ [pretty: true]
128+
cond do
129+
Keyword.has_key?(inspect_options, :width) ->
130+
inspect_options
131+
colors_enabled? ->
132+
[width: width()] ++ inspect_options
133+
true ->
134+
inspect_options
135+
end
136+
end
137+
138+
def width() do
139+
case :io.columns() do
140+
{:ok, width} -> min(width, 80)
141+
{:error, _} -> 80
142+
end
143+
end
144+
145+
def after_spawn(fun) do
146+
Agent.update(@agent, __MODULE__, :handle_after_spawn, [fun])
147+
end
148+
149+
def handle_after_spawn(tab, fun) do
150+
:ets.update_element(tab, :after_spawn, {2, [fun | after_spawn()]})
151+
end
152+
153+
def after_spawn() do
154+
:ets.lookup_element(@table, :after_spawn, 2)
155+
end
156+
157+
def started?() do
158+
Process.whereis(@agent) !== nil
159+
end
160+
161+
def history_size(), do: get(:history_size)
162+
163+
def default_prompt(), do: get(:default_prompt)
164+
165+
def alive_prompt(), do: get(:alive_prompt)
166+
167+
defp get(key) do
168+
case Application.fetch_env(:iex, key) do
169+
{:ok, value} ->
170+
value
171+
:error ->
172+
default_option(key)
173+
end
174+
end
175+
176+
def start_link() do
177+
Agent.start_link(__MODULE__, :init, [@table], [name: @agent])
178+
end
179+
180+
def init(tab) do
181+
:public = :ets.info(tab, :protection)
182+
tab
183+
end
184+
end

0 commit comments

Comments
 (0)