Skip to content

Commit 334f3ce

Browse files
author
José Valim
committed
Merge pull request #1106 from alco/iex-opts-1062
Implement IEx.Options per #1062
2 parents 0dcd550 + 0da2b59 commit 334f3ce

File tree

8 files changed

+329
-58
lines changed

8 files changed

+329
-58
lines changed

lib/elixir/lib/io/ansi.ex

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,13 @@ defmodule IO.ANSI do
136136
end
137137

138138
@doc %B"""
139-
Escapes a string coverting named ANSI sequences into actual ANSI codes.
139+
Escapes a string by converting named ANSI sequences into actual ANSI codes.
140140
141-
The format for referring sequences is `%{red}` and `%{red,bright}` (for
142-
multiple sequences)
141+
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
142+
multiple sequences).
143143
144-
It will also force a %{reset} to get appended to every string. If you don't
145-
want this behaviour, use `escape_fragment/1` and `escape_fragment/2`.
144+
It will also append a %{reset} to the string. If you don't want this
145+
behaviour, use `escape_fragment/1` and `escape_fragment/2`.
146146
147147
An optional boolean parameter can be passed to enable or disable
148148
emitting actual ANSI codes. When false, no ANSI codes will emitted.
@@ -165,10 +165,10 @@ defmodule IO.ANSI do
165165
end
166166

167167
@doc %B"""
168-
Escapes a string coverting named ANSI sequences into actual ANSI codes.
168+
Escapes a string by converting named ANSI sequences into actual ANSI codes.
169169
170-
The format for referring sequences is `%{red}` and `%{red,bright}` (for
171-
multiple sequences)
170+
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
171+
multiple sequences).
172172
173173
An optional boolean parameter can be passed to enable or disable
174174
emitting actual ANSI codes. When false, no ANSI codes will emitted.
@@ -177,8 +177,10 @@ defmodule IO.ANSI do
177177
178178
## Example
179179
180-
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
181-
"Hello \e[31m\e[1m\e[32myes\e[0m"
180+
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes")
181+
"Hello \e[31m\e[1m\e[32myes"
182+
iex> IO.ANSI.escape_fragment("%{reset}bye")
183+
"\e[0mbye"
182184
183185
"""
184186
@spec escape_fragment(String.t, emit :: boolean) :: String.t

lib/elixir/lib/kernel.ex

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,17 +1892,26 @@ defmodule Kernel do
18921892
18931893
The following options are supported:
18941894
1895-
* :raw - tuples are not formatted as the inspect protocol, they are
1896-
always shown as tuples, defaults to false;
1895+
* raw -- when true, record tuples are not formatted by the inspect protocol,
1896+
but are printed as just tuples; default: false
18971897
1898-
* :limit - the limit of items that are shown in tuples, bitstrings and
1899-
lists. Does not apply to strings;
1898+
* limit -- limits the number of items that are printed for tuples, bitstrings,
1899+
and lists; does not apply to strings
19001900
19011901
## Examples
19021902
19031903
iex> inspect(:foo)
19041904
":foo"
19051905
1906+
iex> inspect [1,2,3,4,5], limit: 3
1907+
"[1,2,3,...]"
1908+
1909+
inspect(ArgumentError[])
1910+
#=> "ArgumentError[message: \"argument error\"]"
1911+
1912+
inspect(ArgumentError[], raw: true)
1913+
#=> "{ArgumentError,:__exception__,\"argument error\"}"
1914+
19061915
Note that the inspect protocol does not necessarily return a valid
19071916
representation of an Elixir term. In such cases, the inspected result must
19081917
start with `#`. For example, inspecting a function will return:

lib/iex/lib/iex.ex

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,30 @@ defmodule IEx do
9090
It is possible to override the default loading sequence for .iex file by
9191
supplying the --dot-iex option to iex. See `iex --help`.
9292
93+
## Configuring the shell
94+
95+
There is a number of customization options provided by the shell. Take a look
96+
at the docs for the `IEx.Options` module.
97+
98+
The main functions there are `IEx.Options.get/1` and `IEx.Options.set/2`. One
99+
can also use `IEx.Options.list/0` to get the list of all supported options.
100+
`IEx.Options.print_help/1` will print documentation for the given option.
101+
102+
In particular, it might be convenient to customize those options inside your
103+
.iex file like this:
104+
105+
# .iex
106+
IEx.Options.set :inspect, limit: 3
107+
108+
### now run the shell ###
109+
110+
$ iex
111+
Erlang R16B (erts-5.10.1) [...]
112+
113+
Interactive Elixir (0.9.1.dev) - press Ctrl+C to exit (type h() ENTER for help)
114+
iex(1)> [1,2,3,4,5]
115+
[1,2,3,...]
116+
93117
## Expressions in IEx
94118
95119
As an interactive shell, IEx evaluates expressions. This has some
@@ -147,19 +171,16 @@ defmodule IEx do
147171
match?({ :ok, true }, :application.get_env(:iex, :started))
148172
end
149173
150-
@doc """
151-
Registers options used on inspect.
152-
"""
174+
@doc false
153175
def inspect_opts(opts) when is_list(opts) do
154-
:application.set_env(:iex, :inspect_opts, Keyword.merge(inspect_opts, opts))
176+
IO.write "[WARNING] IEx.inspect_opts is deprecated, please use IEx.Options.get/set instead. See `IEx.Options.print_help :inspect`.\n#{Exception.format_stacktrace}"
177+
IEx.Options.set :inspect, opts
155178
end
156179
157-
@doc """
158-
Returns currently registered inspect options.
159-
"""
180+
@doc false
160181
def inspect_opts do
161-
{ :ok, opts } = :application.get_env(:iex, :inspect_opts)
162-
opts
182+
IO.write "[WARNING] IEx.inspect_opts is deprecated, please use IEx.Options.get/set instead. See `IEx.Options.print_help :inspect`.\n#{Exception.format_stacktrace}"
183+
IEx.Options.get :inspect
163184
end
164185
165186
# This is a callback invoked by Erlang shell utilities
@@ -203,7 +224,7 @@ defmodule IEx do
203224
)
204225
205226
if opts[:inspect_opts] do
206-
IEx.inspect_opts(opts[:inspect_opts])
227+
IEx.Options.set :inspect, opts[:inspect_opts]
207228
end
208229
209230
IEx.Config[
@@ -237,4 +258,28 @@ defmodule IEx do
237258
defp run_after_spawn do
238259
lc fun inlist Enum.reverse(after_spawn), do: fun.()
239260
end
261+
262+
@doc """
263+
Returns `string` escaped using the specified color.
264+
"""
265+
def color(color_name, string) do
266+
colors = IEx.Options.get(:colors)
267+
IO.ANSI.escape "%{#{colors[color_name]}}#{string}", colors[:enabled]
268+
end
269+
270+
@doc """
271+
Returns an escaped fragment using the specified color.
272+
"""
273+
def color_fragment(color_name) do
274+
colors = IEx.Options.get(:colors)
275+
IO.ANSI.escape_fragment "%{#{colors[color_name]}}", colors[:enabled]
276+
end
277+
278+
@doc """
279+
Returns an escaped fragment that resets colors and attributes.
280+
"""
281+
def color_reset() do
282+
colors = IEx.Options.get(:colors)
283+
IO.ANSI.escape_fragment "%{reset}", colors[:enabled]
284+
end
240285
end

lib/iex/lib/iex/helpers.ex

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ defmodule IEx.Helpers do
8686
end
8787

8888
defp print_history(config) do
89-
IO.puts IO.ANSI.escape("%{yellow}#{config.counter}: #{config.cache}#=> #{inspect config.result}\n")
89+
IO.puts IEx.color(:info, "#{config.counter}: #{config.cache}#=> #{inspect config.result}\n")
9090
end
9191

9292
@doc """
@@ -294,7 +294,7 @@ defmodule IEx.Helpers do
294294
Prints the current working directory.
295295
"""
296296
def pwd do
297-
IO.puts IO.ANSI.escape("%{yellow}#{System.cwd!}")
297+
IO.puts IEx.color(:info, System.cwd!)
298298
end
299299

300300
@doc """
@@ -304,7 +304,7 @@ defmodule IEx.Helpers do
304304
case File.cd(expand_home(directory)) do
305305
:ok -> pwd
306306
{ :error, :enoent } ->
307-
IO.puts IO.ANSI.escape("%{red}No directory #{directory}")
307+
IO.puts IEx.color(:error, "No directory #{directory}")
308308
end
309309
end
310310

@@ -320,10 +320,10 @@ defmodule IEx.Helpers do
320320
ls_print(path, sorted_items)
321321

322322
{ :error, :enoent } ->
323-
IO.puts IO.ANSI.escape("%{red}No such file or directory #{path}")
323+
IO.puts IEx.color(:error, "No such file or directory #{path}")
324324

325325
{ :error, :enotdir } ->
326-
IO.puts IO.ANSI.escape("%{yellow}#{Path.absname(path)}")
326+
IO.puts IEx.color(:info, Path.absname(path))
327327
end
328328
end
329329

@@ -365,9 +365,9 @@ defmodule IEx.Helpers do
365365
defp format_item(path, representation) do
366366
case File.stat(path) do
367367
{ :ok, File.Stat[type: :device] } ->
368-
IO.ANSI.escape("%{green}#{representation}")
368+
IEx.color(:device, representation)
369369
{ :ok, File.Stat[type: :directory] } ->
370-
IO.ANSI.escape("%{blue}#{representation}")
370+
IEx.color(:directory, representation)
371371
_ ->
372372
representation
373373
end

lib/iex/lib/iex/introspection.ex

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,22 @@ defmodule IEx.Introspection do
99
{ :module, _ } ->
1010
case module.__info__(:moduledoc) do
1111
{ _, binary } when is_binary(binary) ->
12-
IO.puts IO.ANSI.escape("%{yellow}# #{inspect module}\n")
13-
IO.write IO.ANSI.escape_fragment("%{yellow}") <> binary <> IO.ANSI.escape_fragment("%{reset}")
12+
IO.puts IEx.color(:info, "# #{inspect module}\n")
13+
# We use fragments here so that escape sequences in `binary` are
14+
# treated literally and don't get converted to ANSI escapes.
15+
IO.write IEx.color_fragment(:info) <> binary <> IEx.color_reset()
1416
{ _, _ } ->
15-
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module} have been found")
17+
IO.puts IEx.color(:error, "No docs for #{inspect module} have been found")
1618
_ ->
17-
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
19+
IO.puts IEx.color(:error, "#{inspect module} was not compiled with docs")
1820
end
1921
{ :error, reason } ->
20-
IO.puts IO.ANSI.escape("%{red}Could not load module #{inspect module}: #{reason}")
22+
IO.puts IEx.color(:error, "Could not load module #{inspect module}: #{reason}")
2123
end
2224
end
2325

2426
def h(_) do
25-
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
27+
IO.puts IEx.color(:error, "Invalid arguments for h helper")
2628
end
2729

2830
@doc false
@@ -34,7 +36,7 @@ defmodule IEx.Introspection do
3436
end
3537

3638
unless result == :ok, do:
37-
IO.puts IO.ANSI.escape("%{red}No docs for #{function} have been found")
39+
IO.puts IEx.color(:error, "No docs for #{function} have been found")
3840

3941
:ok
4042
end
@@ -44,9 +46,9 @@ defmodule IEx.Introspection do
4446
:ok ->
4547
:ok
4648
:no_docs ->
47-
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
49+
IO.puts IEx.color(:error, "#{inspect module} was not compiled with docs")
4850
:not_found ->
49-
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module}.#{function} have been found")
51+
IO.puts IEx.color(:error, "No docs for #{inspect module}.#{function} have been found")
5052
end
5153

5254
:ok
@@ -57,7 +59,7 @@ defmodule IEx.Introspection do
5759
end
5860

5961
def h(_, _) do
60-
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
62+
IO.puts IEx.color(:error, "Invalid arguments for h helper")
6163
end
6264

6365
defp h_mod_fun(mod, fun) when is_atom(mod) and is_atom(fun) do
@@ -82,7 +84,7 @@ defmodule IEx.Introspection do
8284
end
8385

8486
unless result == :ok, do:
85-
IO.puts IO.ANSI.escape("%{red}No docs for #{function}/#{arity} have been found")
87+
IO.puts IEx.color(:error, "No docs for #{function}/#{arity} have been found")
8688

8789
:ok
8890
end
@@ -92,16 +94,16 @@ defmodule IEx.Introspection do
9294
:ok ->
9395
:ok
9496
:no_docs ->
95-
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
97+
IO.puts IEx.color(:error, "#{inspect module} was not compiled with docs")
9698
:not_found ->
97-
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module}.#{function}/#{arity} have been found")
99+
IO.puts IEx.color(:error, "No docs for #{inspect module}.#{function}/#{arity} have been found")
98100
end
99101

100102
:ok
101103
end
102104

103105
def h(_, _, _) do
104-
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
106+
IO.puts IEx.color(:error, "Invalid arguments for h helper")
105107
end
106108

107109
defp h_mod_fun_arity(mod, fun, arity) when is_atom(mod) and is_atom(fun) and is_integer(arity) do
@@ -147,8 +149,10 @@ defmodule IEx.Introspection do
147149

148150
defp print_doc({ { fun, _ }, _line, kind, args, doc }) do
149151
args = Enum.map_join(args, ", ", print_doc_arg(&1))
150-
IO.puts IO.ANSI.escape("%{yellow}* #{kind} #{fun}(#{args})\n")
151-
if doc, do: IO.write IO.ANSI.escape_fragment("%{yellow}") <> doc <> IO.ANSI.escape_fragment("%{reset}")
152+
IO.puts IEx.color(:info, "* #{kind} #{fun}(#{args})\n")
153+
# We use fragments here so that escape sequences in `doc` are
154+
# treated literally and don't get converted to ANSI escapes.
155+
if doc, do: IO.write IEx.color_fragment(:info) <> doc <> IEx.color_reset()
152156
end
153157

154158
defp print_doc_arg({ ://, _, [left, right] }) do
@@ -164,7 +168,7 @@ defmodule IEx.Introspection do
164168
types = lc type inlist Kernel.Typespec.beam_types(module), do: print_type(type)
165169

166170
if types == [] do
167-
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module} have been found")
171+
IO.puts IEx.color(:error, "No types for #{inspect module} have been found")
168172
end
169173

170174
:ok
@@ -179,7 +183,7 @@ defmodule IEx.Introspection do
179183
end
180184

181185
if types == [] do
182-
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module}.#{type} have been found")
186+
IO.puts IEx.color(:error, "No types for #{inspect module}.#{type} have been found")
183187
end
184188

185189
:ok
@@ -192,7 +196,7 @@ defmodule IEx.Introspection do
192196

193197
case types do
194198
[] ->
195-
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module}.#{type}/#{arity} have been found")
199+
IO.puts IEx.color(:error, "No types for #{inspect module}.#{type}/#{arity} have been found")
196200
[type] ->
197201
print_type(type)
198202
end
@@ -205,7 +209,7 @@ defmodule IEx.Introspection do
205209
specs = lc spec inlist beam_specs(module), do: print_spec(spec)
206210

207211
if specs == [] do
208-
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module} have been found")
212+
IO.puts IEx.color(:error, "No specs for #{inspect module} have been found")
209213
end
210214

211215
:ok
@@ -220,7 +224,7 @@ defmodule IEx.Introspection do
220224
end
221225

222226
if specs == [] do
223-
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module}.#{function} have been found")
227+
IO.puts IEx.color(:error, "No specs for #{inspect module}.#{function} have been found")
224228
end
225229

226230
:ok
@@ -235,7 +239,7 @@ defmodule IEx.Introspection do
235239
end
236240

237241
if specs == [] do
238-
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module}.#{function} have been found")
242+
IO.puts IEx.color(:error, "No specs for #{inspect module}.#{function} have been found")
239243
end
240244

241245
:ok
@@ -249,14 +253,14 @@ defmodule IEx.Introspection do
249253

250254
defp print_type({ kind, type }) do
251255
ast = Kernel.Typespec.type_to_ast(type)
252-
IO.puts IO.ANSI.escape("%{yellow}@#{kind} #{Macro.to_binary(ast)}")
256+
IO.puts IEx.color(:info, "@#{kind} #{Macro.to_binary(ast)}")
253257
true
254258
end
255259

256260
defp print_spec({kind, { { name, _arity }, specs }}) do
257261
Enum.each specs, fn(spec) ->
258262
binary = Macro.to_binary Kernel.Typespec.spec_to_ast(name, spec)
259-
IO.puts IO.ANSI.escape("%{yellow}@#{kind} #{binary}")
263+
IO.puts IEx.color(:info, "@#{kind} #{binary}")
260264
end
261265
true
262266
end

0 commit comments

Comments
 (0)