Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions lib/mix/lib/mix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,25 @@ defmodule Mix do
end

@doc """
The shell is a wrapper for doing IO.
Returns the current shell.

It contains conveniences for asking the user information,
printing status and so forth. It is also swappable,
allowing developers to use a test shell that simply sends the
messages to the current process.
`shell/0` can be used as a wrapper for the current shell. It contains
conveniences for asking information to the user, printing things and so
forth. The Mix shell is swappable (see `shell/1`), allowing developers to use
a test shell that simply sends messages to the current process instead of
doing IO (see `Mix.Shell.Process`).

By default, this returns `Mix.Shell.IO`.
"""
def shell do
Mix.State.get(:shell, Mix.Shell.IO)
end

@doc """
Sets the current shell.

After calling this function, `shell` becomes the shell that is returned by
`shell/0`.
"""
def shell(shell) do
Mix.State.put(:shell, shell)
Expand Down
55 changes: 45 additions & 10 deletions lib/mix/lib/mix/shell/process.ex
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
defmodule Mix.Shell.Process do
@moduledoc """
This is a Mix shell that uses the current process mailbox
for communication instead of IO.
Mix shell that uses the current process mailbox for communication.

When a developer calls `info("hello")`, the following
message will be sent to the current process:

This module provides a Mix shell implementation that uses
the current process mailbox for communication instead of IO.

As an example, when `Mix.shell.info("hello")` is called,
the following message will be sent to the calling process:

{:mix_shell, :info, ["hello"]}

This is mainly useful in tests, allowing us to assert
if given messages were received or not. Since we need
to guarantee a clean slate between tests, there
is also a `flush/1` function responsible for flushing all
`:mix_shell` related messages from the process inbox.
if given messages were received or not instead of performing
checks on some captured IO. Since we need to guarantee a clean
slate between tests, there is also a `flush/1` function
responsible for flushing all `:mix_shell` related messages
from the process inbox.

## Examples

Mix.shell.info "hello"
receive do {:mix_shell, :info, [msg]} -> msg end
#=> "hello"

send self(), {:mix_shell_input, :prompt, "Pretty cool"}
Mix.shell.prompt?("How cool was that?!")
#=> "Pretty cool"

"""

@behaviour Mix.Shell

@doc """
Flushes all `:mix_shell` and `:mix_shell_input` messages from the current process.

If a callback is given, it is invoked for each received message.

## Examples
Expand Down Expand Up @@ -83,12 +99,23 @@ defmodule Mix.Shell.Process do

@doc """
Forwards the message to the current process.

It also checks the inbox for an input message matching:

{:mix_shell_input, :prompt, value}

If one does not exist, it will abort since there was no shell
process inputs given. Value must be a string.
process inputs given. `value` must be a string.

## Examples

The following will answer with `"Meg"` to the prompt
`"What's your name?"`:

# The response is sent before calling prompt/1 so that prompt/1 can read it
send self(), {:mix_shell_input, :prompt, "Meg"}
Mix.shell.prompt("What's your name?")

"""
def prompt(message) do
print_app
Expand All @@ -103,12 +130,20 @@ defmodule Mix.Shell.Process do

@doc """
Forwards the message to the current process.

It also checks the inbox for an input message matching:

{:mix_shell_input, :yes?, value}

If one does not exist, it will abort since there was no shell
process inputs given. Value must be `true` or `false`.
process inputs given. `value` must be `true` or `false`.

## Example

# Send the response to self() first so that yes?/1 will be able to read it
send self(), {:mix_shell_input, :yes?, true}
Mix.shell.yes?("Are you sure you want to continue?")

"""
def yes?(message) do
print_app
Expand Down