-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
shell.ex
141 lines (105 loc) · 3.38 KB
/
shell.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
defmodule Mix.Shell do
@moduledoc """
Defines `Mix.Shell` contract.
"""
@doc false
defstruct [:callback]
@doc """
Prints the given ANSI message to the shell.
"""
@callback info(message :: IO.ANSI.ansidata()) :: :ok
@doc """
Prints the given ANSI error to the shell.
"""
@callback error(message :: IO.ANSI.ansidata()) :: :ok
@doc """
Executes the given command and returns its exit status.
Shortcut for `c:cmd/2` with empty options.
"""
@callback cmd(command :: String.t()) :: integer
@doc """
Executes the given command and returns its exit status.
## Options
This callback should support the following options:
* `:print_app` - when `false`, does not print the app name
when the command outputs something
* `:stderr_to_stdout` - when `false`, does not redirect
stderr to stdout
* `:quiet` - when `true`, do not print the command output
* `:env` - environment options to the executed command
* `:cd` - (since v1.11.0) the directory to run the command in
All the built-in shells support these.
"""
@callback cmd(command :: String.t(), options :: keyword) :: integer
@doc """
Prompts the user for input.
"""
@callback prompt(message :: binary) :: binary
@doc """
Prompts the user for confirmation.
Shortcut for `yes?/2` with empty options.
"""
@callback yes?(message :: binary) :: boolean
@doc """
Prompts the user for confirmation.
## Options
* `:default` - `:yes` or `:no` (the default is `:yes`)
"""
@callback yes?(message :: binary, options :: keyword) :: boolean
@doc """
Prints the current application to the shell if
it was not printed yet.
"""
@callback print_app() :: :ok
@doc """
Returns the printable app name.
This function returns the current application name,
but only if the application name should be printed.
Calling this function automatically toggles its value
to `false` until the current project is re-entered. The
goal is to avoid printing the application name
multiple times.
"""
@spec printable_app_name() :: atom | nil
def printable_app_name do
Mix.ProjectStack.printable_app_name()
end
@doc """
Executes the given `command` as a shell command and
invokes the `callback` for the streamed response.
This is most commonly used by shell implementations
but can also be invoked directly.
`callback` takes the output data of the command. Its
return value is ignored.
## Options
* `:cd` - (since v1.11.0) the directory to run the command in
* `:stderr_to_stdout` - redirects stderr to stdout, defaults to true
* `:env` - a list of environment variables, defaults to `[]`
* `:quiet` - overrides the callback to no-op
"""
@spec cmd(String.t(), keyword, (binary -> term)) :: exit_status :: non_neg_integer
def cmd(command, options \\ [], callback) when is_function(callback, 1) do
callback =
if options[:quiet] do
fn x -> x end
else
callback
end
options =
options
|> Keyword.take([:cd, :stderr_to_stdout, :env])
|> Keyword.put(:into, %Mix.Shell{callback: callback})
|> Keyword.put_new(:stderr_to_stdout, true)
{_, status} = System.shell(command, options)
status
end
defimpl Collectable do
def into(%Mix.Shell{callback: fun}) do
{:ok,
fn
_, {:cont, data} -> fun.(data)
_, _ -> :ok
end}
end
end
end