Skip to content

Commit 6e8fb6c

Browse files
author
José Valim
committed
Merge pull request #1100 from alco/dot-iex
Add support for .iex files
2 parents d99ec5a + e216f18 commit 6e8fb6c

File tree

8 files changed

+200
-58
lines changed

8 files changed

+200
-58
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# v0.9.0.dev
22

33
* enhancements
4+
* [IEx] Add support for .iex files that are loaded during shell's boot process
5+
* [IEx] Add `import_file/1` helper
46

57
* bug fix
68

bin/iex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
1414
--name \"name\" Makes and assigns a name to the distributed node
1515
--sname \"name\" Makes and assigns a short name to the distributed node
1616
--remsh \"name\" Connects to a node using a remote shell
17+
--dot-iex \"path\" Overrides default .iex file and uses path instead;
18+
path can be empty, then no file will be loaded
1719
1820
** Options marked with (*) can be given more than once
1921
** Options given after the .exs file or -- are passed down to the executed code
@@ -33,4 +35,4 @@ readlink_f () {
3335

3436
SELF=$(readlink_f "$0")
3537
SCRIPT_PATH=$(dirname "$SELF")
36-
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" +iex "$@"
38+
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" +iex "$@"

lib/elixir/lib/kernel/cli.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,12 @@ defmodule Kernel.CLI do
222222
{ config, t }
223223
end
224224

225+
# This clause is here so that Kernel.CLI does not error out with "unknown
226+
# option"
227+
defp process_iex(["--dot-iex",_|t], config) do
228+
process_iex t, config
229+
end
230+
225231
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
226232
process_iex t, config
227233
end

lib/elixir/src/elixir.erl

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
-module(elixir).
22
-behaviour(application).
33
-export([main/1, start_cli/0,
4-
scope_for_eval/1, eval/2, eval/3, eval/4,
4+
scope_for_eval/1, scope_for_eval/2,
5+
eval/2, eval/3, eval/4,
56
eval_quoted/2, eval_quoted/3, eval_quoted/4,
67
eval_forms/3, translate_forms/3]).
78
-include("elixir.hrl").
@@ -46,37 +47,47 @@ start_cli() ->
4647
%% EVAL HOOKS
4748

4849
scope_for_eval(Opts) ->
49-
case lists:keyfind(file, 1, Opts) of
50-
{ file, RawFile } -> File = to_binary(RawFile);
51-
false -> File = <<"nofile">>
50+
scope_for_eval(#elixir_scope{
51+
file = <<"nofile">>,
52+
local = nil,
53+
aliases = [],
54+
requires = elixir_dispatch:default_requires(),
55+
functions = elixir_dispatch:default_functions(),
56+
macros = elixir_dispatch:default_macros()
57+
}, Opts).
58+
59+
scope_for_eval(Scope, Opts) ->
60+
File = case lists:keyfind(file, 1, Opts) of
61+
{ file, RawFile } -> to_binary(RawFile);
62+
false -> Scope#elixir_scope.file
5263
end,
5364

54-
case lists:keyfind(delegate_locals_to, 1, Opts) of
55-
{ delegate_locals_to, Local } -> Local;
56-
false -> Local = nil
65+
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
66+
{ delegate_locals_to, LocalOpt } -> LocalOpt;
67+
false -> Scope#elixir_scope.local
5768
end,
5869

59-
case lists:keyfind(aliases, 1, Opts) of
60-
{ aliases, Aliases } -> Aliases;
61-
false -> Aliases = []
70+
Aliases = case lists:keyfind(aliases, 1, Opts) of
71+
{ aliases, AliasesOpt } -> AliasesOpt;
72+
false -> Scope#elixir_scope.aliases
6273
end,
6374

64-
case lists:keyfind(requires, 1, Opts) of
65-
{ requires, List } -> Requires = ordsets:from_list(List);
66-
false -> Requires = elixir_dispatch:default_requires()
75+
Requires = case lists:keyfind(requires, 1, Opts) of
76+
{ requires, List } -> ordsets:from_list(List);
77+
false -> Scope#elixir_scope.requires
6778
end,
6879

69-
case lists:keyfind(functions, 1, Opts) of
70-
{ functions, Functions } -> Functions;
71-
false -> Functions = elixir_dispatch:default_functions()
80+
Functions = case lists:keyfind(functions, 1, Opts) of
81+
{ functions, FunctionsOpt } -> FunctionsOpt;
82+
false -> Scope#elixir_scope.functions
7283
end,
7384

74-
case lists:keyfind(macros, 1, Opts) of
75-
{ macros, Macros } -> Macros;
76-
false -> Macros = elixir_dispatch:default_macros()
85+
Macros = case lists:keyfind(macros, 1, Opts) of
86+
{ macros, MacrosOpt } -> MacrosOpt;
87+
false -> Scope#elixir_scope.macros
7788
end,
7889

79-
#elixir_scope{
90+
Scope#elixir_scope{
8091
file=File, local=Local,
8192
macros=Macros, functions=Functions,
8293
requires=Requires, aliases=Aliases }.

lib/iex/lib/iex.ex

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
defrecord IEx.Config, binding: nil, cache: '', counter: 1, scope: nil, result: nil
1+
defrecord IEx.Config, binding: nil, cache: '', counter: 1, scope: nil,
2+
result: nil, dot_iex_path: nil
23

34
defmodule IEx do
45
@moduledoc %B"""
56
Welcome to IEx.
67
7-
This module is the main entry point Interactive Elixir and
8+
This module is the main entry point for Interactive Elixir and
89
in this documentation we will talk a bit about how IEx works.
910
1011
Notice some of the functionality described here will be available
@@ -20,11 +21,11 @@ defmodule IEx do
2021
2122
## The User Switch command
2223
23-
Besides the break command, one can type Ctrl+G to get the to
24-
the user switch command. When reached, you can type `h` to
24+
Besides the break command, one can type Ctrl+G to get to the
25+
user switch command menu. When reached, you can type `h` to
2526
get more information.
2627
27-
In this switch, developers are able to create new shell and
28+
In this menu, developers are able to start new shells and
2829
alternate in between them. Let's give it a try:
2930
3031
User switch command
@@ -36,7 +37,7 @@ defmodule IEx do
3637
3738
hello = :world
3839
39-
Now, let's rollback to the first shell:
40+
Now, let's roll back to the first shell:
4041
4142
User switch command
4243
--> c 1
@@ -46,23 +47,57 @@ defmodule IEx do
4647
hello
4748
** (UndefinedFunctionError) undefined function: IEx.Helpers.hello/0
4849
49-
The command above fails because we have changed the shells
50-
and they are isolated from each other, you can access the
51-
variables defined in one in the other.
50+
The command above fails because we have switched the shells.
51+
Since shells are isolated from each other, you can't access the
52+
variables defined in one shell from the other one.
5253
53-
The User Switch also allow developers to connect to remote
54-
shells using r. Keep in mind that you can't connect to a
54+
The user switch command menu also allows developers to connect to remote
55+
shells using the "r" command. Keep in mind that you can't connect to a
5556
remote node if you haven't given a name to the current node
5657
(i.e. Process.is_alive? must return true).
5758
59+
## The .iex file
60+
61+
When starting IEx, it will look for a local .iex file (located in the current
62+
working directory), then a global one (located at ~/.iex) and will load the
63+
first one it finds (if any). The code in the chosen .iex file will be
64+
evaluated in the shell's context. So, for instance, any modules that are
65+
loaded or variables that are bound in the .iex file will be available in the
66+
shell after it has booted.
67+
68+
Sample contents of a local .iex file:
69+
70+
# source another .iex file
71+
import_file "~/.iex"
72+
73+
# print something before the shell starts
74+
IO.puts "hello world"
75+
76+
# bind a variable that'll be accessible in the shell
77+
value = 13
78+
79+
Running the shell in the directory where the above .iex file is located
80+
results in
81+
82+
$ iex
83+
Erlang R15B03 (erts-5.9.3.1) [...]
84+
85+
hello world
86+
Interactive Elixir (0.8.3.dev) - press Ctrl+C to exit (type h() ENTER for help)
87+
iex(1)> value
88+
13
89+
90+
It is possible to override the default loading sequence for .iex file by
91+
supplying the --dot-iex option to iex. See `iex --help`.
92+
5893
## Expressions in IEx
5994
6095
As an interactive shell, IEx evalutes expressions. This has some
61-
interesting consequences worthy discussing.
96+
interesting consequences that are worth discussing.
6297
6398
The first one is that the code is truly evaluated and not compiled.
64-
This means that, any benchmarking done in the shell is going to have
65-
skewed results. So never run any profiling nor benchmark in the shell.
99+
This means that any benchmarking done in the shell is going to have
100+
skewed results. So never run any profiling nor benchmarks in the shell.
66101
67102
Second of all, IEx alows you to break an expression into many lines,
68103
since this is common in Elixir. For example:
@@ -173,7 +208,8 @@ defmodule IEx do
173208

174209
IEx.Config[
175210
binding: opts[:binding] || [],
176-
scope: scope
211+
scope: scope,
212+
dot_iex_path: Keyword.get(opts, :dot_iex_path),
177213
]
178214
end
179215

lib/iex/lib/iex/cli.ex

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ defmodule IEx.CLI do
1919
else
2020
:user.start
2121
IO.puts "Warning: could not run smart terminal, falling back to dumb one"
22-
IEx.start([], fn -> :elixir.start_cli end)
22+
config = [dot_iex_path: find_dot_iex(:init.get_plain_arguments)]
23+
IEx.start(config, fn -> :elixir.start_cli end)
2324
end
2425
end
2526

@@ -37,12 +38,15 @@ defmodule IEx.CLI do
3738
end
3839

3940
defp tty do
41+
plain_args = :init.get_plain_arguments
42+
43+
config = [dot_iex_path: find_dot_iex(plain_args)]
4044
function = fn ->
41-
IEx.start([], fn -> :elixir.start_cli end)
45+
IEx.start(config, fn -> :elixir.start_cli end)
4246
end
4347

4448
args =
45-
if remote = get_remsh(:init.get_plain_arguments) do
49+
if remote = get_remsh(plain_args) do
4650
unless is_alive do
4751
function = fn ->
4852
IO.puts(:stderr, "In order to use --remsh, you need to name the current node using --name or --sname. Aborting...")
@@ -60,6 +64,10 @@ defmodule IEx.CLI do
6064
:user_drv.start([:"tty_sl -c -e", args])
6165
end
6266

67+
defp find_dot_iex(['--dot-iex',h|_]), do: :unicode.characters_to_binary(h)
68+
defp find_dot_iex([_|t]), do: find_dot_iex(t)
69+
defp find_dot_iex([]), do: nil
70+
6371
defp get_remsh(['--remsh',h|_]), do: list_to_atom(h)
6472
defp get_remsh([_|t]), do: get_remsh(t)
6573
defp get_remsh([]), do: nil

lib/iex/lib/iex/helpers.ex

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ defmodule IEx.Helpers do
2525
* `t/1` — prints type information
2626
* `v/0` - prints all commands and values
2727
* `v/1` - retrieves nth value from console
28+
* `import_file/1` - evaluate the given file in the shell's context
2829
2930
Help for functions in this module can be consulted
3031
directly from the command line, as an example, try:
@@ -54,7 +55,6 @@ defmodule IEx.Helpers do
5455
5556
c "baz.ex"
5657
#=> [Baz]
57-
5858
"""
5959
def c(files, path // ".") do
6060
tuples = Kernel.ParallelCompiler.files_to_path List.wrap(files), path
@@ -111,7 +111,6 @@ defmodule IEx.Helpers do
111111
h receive/1
112112
h Enum.all?/2
113113
h Enum.all?
114-
115114
"""
116115
defmacro h({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
117116
quote do
@@ -154,7 +153,6 @@ defmodule IEx.Helpers do
154153
t(Enum)
155154
t(Enum.t/0)
156155
t(Enum.t)
157-
158156
"""
159157
defmacro t({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
160158
quote do
@@ -188,7 +186,6 @@ defmodule IEx.Helpers do
188186
s(Enum.all?/2)
189187
s(list_to_atom)
190188
s(list_to_atom/1)
191-
192189
"""
193190
defmacro s({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
194191
quote do
@@ -375,4 +372,30 @@ defmodule IEx.Helpers do
375372
representation
376373
end
377374
end
375+
376+
@doc """
377+
Evaluates the contents of file at `path` as if it were directly typed into
378+
the shell. `path` has to be a literal binary.
379+
380+
Leading `~` in `path` is automatically expanded.
381+
382+
## Examples
383+
384+
# ~/file.exs
385+
value = 13
386+
387+
# in the shell
388+
iex(1)> import_file "~/file.exs"
389+
13
390+
iex(2)> value
391+
13
392+
"""
393+
defmacro import_file(path) when is_binary(path) do
394+
path = Path.expand(path)
395+
Code.string_to_ast! File.read!(path), file: path
396+
end
397+
398+
defmacro import_file(_) do
399+
raise ArgumentError, message: "import_file/1 expects a literal binary as its argument"
400+
end
378401
end

0 commit comments

Comments
 (0)