Skip to content

Commit 6378750

Browse files
author
José Valim
committed
Only accept --no-SWITCH for booleans
The previous behaviour was confusing and a special case. This enables negated switches only for booleans, otherwise it is considered as a regular switch (as the combination of --no-SWITCH for any other type is an error anyway).
1 parent fc25f60 commit 6378750

File tree

11 files changed

+60
-86
lines changed

11 files changed

+60
-86
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
* [Kernel] `exit(integer)` is no longer supported from scripts to configure the exit signal. Use `exit({:shutdown, integer})` instead
4747
* [Mix] `mix archive` has been renamed to `mix archive.build`
4848
* [Mix] `Mix.shell.info/1` no longer automatically escape ANSI sequences. Instead if has to be explicitly enabled with the `ansi: true` option
49+
* [OptionParser] `--no-SWITCH` are only allowed for declared booleans switches
4950

5051
## v0.14.2 (2014-06-29)
5152

lib/elixir/lib/option_parser.ex

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ defmodule OptionParser do
4545
* `:strict` - the switches are strict. Any switch that does not
4646
exist in the switch list is treated as an error.
4747
48-
* `:switches` - configure some switches. Switches that does not
48+
* `:switches` - defines some switches. Switches that does not
4949
exist in the switch list are still attempted to be parsed.
5050
5151
Note only `:strict` or `:switches` may be given at once.
@@ -59,7 +59,7 @@ defmodule OptionParser do
5959
* `:float` - parses the switch as a float.
6060
* `:string` - returns the switch as a string.
6161
62-
If a switch can't be parsed or is not specfied in the strict case,
62+
If a switch can't be parsed or is not specified in the strict case,
6363
the option is returned in the invalid options list (third element
6464
of the returned tuple).
6565
@@ -91,14 +91,8 @@ defmodule OptionParser do
9191
9292
## Negation switches
9393
94-
All switches starting with `--no-` are considered to be booleans and never
95-
parse the next value:
96-
97-
iex> OptionParser.parse(["--no-op", "path/to/file"])
98-
{[no_op: true], ["path/to/file"], []}
99-
100-
However, in case the base switch exists, it sets that particular switch to
101-
false:
94+
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
95+
which will set the option to false:
10296
10397
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
10498
{[op: false], ["path/to/file"], []}
@@ -221,7 +215,7 @@ defmodule OptionParser do
221215
defp next(["-" <> option|rest], aliases, switches, strict) do
222216
{option, value} = split_option(option)
223217
opt_name_bin = "-" <> option
224-
tagged = tag_option(option, value, switches, aliases)
218+
tagged = tag_option(option, switches, aliases)
225219

226220
if strict and not option_defined?(tagged, switches) do
227221
{:undefined, opt_name_bin, value, rest}
@@ -296,11 +290,11 @@ defmodule OptionParser do
296290
end
297291
end
298292

299-
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
300-
get_negated(option, value, switches)
293+
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
294+
get_negated(option, switches)
301295
end
302296

303-
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
297+
defp tag_option(option, _switches, aliases) when is_binary(option) do
304298
opt = get_option(option)
305299
if alias = aliases[opt] do
306300
{:default, alias}
@@ -325,23 +319,14 @@ defmodule OptionParser do
325319
{nil, [:invalid], value}
326320
end
327321

328-
defp normalize_option({:negated, option}, nil, switches) do
329-
kinds = List.wrap(switches[option])
330-
331-
cond do
332-
:boolean in kinds ->
333-
{option, kinds, false}
334-
kinds == [] ->
335-
{option, kinds, true}
336-
true ->
337-
{reverse_negated(option), [:invalid], nil}
322+
defp normalize_option({:negated, option}, value, switches) do
323+
if value do
324+
{option, [:invalid], value}
325+
else
326+
{option, List.wrap(switches[option]), false}
338327
end
339328
end
340329

341-
defp normalize_option({:negated, option}, value, _switches) do
342-
{option, [:invalid], value}
343-
end
344-
345330
defp normalize_option({:default, option}, value, switches) do
346331
{option, List.wrap(switches[option]), value}
347332
end
@@ -396,24 +381,18 @@ defmodule OptionParser do
396381
end
397382
end
398383

399-
defp reverse_negated(negated) do
400-
String.to_atom("no_" <> Atom.to_string(negated))
401-
end
402-
403-
defp get_negated("no-" <> rest = option, value, switches) do
404-
if negated = get_option(rest) do
405-
option = if Keyword.has_key?(switches, negated) and value == nil do
406-
negated
407-
else
408-
get_option(option)
409-
end
410-
{:negated, option}
411-
else
412-
:unknown
384+
defp get_negated("no-" <> rest = original, switches) do
385+
cond do
386+
(negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
387+
{:negated, negated}
388+
option = get_option(original) ->
389+
{:default, option}
390+
true ->
391+
:unknown
413392
end
414393
end
415394

416-
defp get_negated(rest, _value, _switches) do
395+
defp get_negated(rest, _switches) do
417396
if option = get_option(rest) do
418397
{:default, option}
419398
else

lib/elixir/test/elixir/option_parser_test.exs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ defmodule OptionParserTest do
4848
assert OptionParser.parse(["--no-bool"], strict: [])
4949
== {[], [], [{"--no-bool", nil}]}
5050
assert OptionParser.parse(["--no-bool=...", "other"])
51-
== {[], ["other"], [{"--no-bool", "..."}]}
51+
== {[no_bool: "..."], ["other"], []}
5252
end
5353

5454
test "does not parse -- as an alias" do
@@ -103,7 +103,7 @@ defmodule OptionParserTest do
103103
assert OptionParser.parse(["--value"], switches: [value: :string])
104104
== {[], [], [{"--value", nil}]}
105105
assert OptionParser.parse(["--no-value"], switches: [value: :string])
106-
== {[], [], [{"--no-value", nil}]}
106+
== {[no_value: true], [], []}
107107
end
108108

109109
test "parses configured integers" do
@@ -134,11 +134,6 @@ defmodule OptionParserTest do
134134
== {[], ["foo"], [{"--value", "WAT"}]}
135135
end
136136

137-
test "parses no switches as flags" do
138-
assert OptionParser.parse(["--no-docs", "foo"])
139-
== {[no_docs: true], ["foo"], []}
140-
end
141-
142137
test "overrides options by default" do
143138
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"])
144139
== {[require: "bar"], ["baz"], []}
@@ -157,17 +152,17 @@ defmodule OptionParserTest do
157152
end
158153

159154
test "stops on --" do
160-
options = OptionParser.parse(["--source", "from_docs/", "--", "1", "2", "3"])
161-
assert options == {[source: "from_docs/"], ["1", "2", "3"], []}
155+
options = OptionParser.parse(["--source", "foo", "--", "1", "2", "3"])
156+
assert options == {[source: "foo"], ["1", "2", "3"], []}
162157

163-
options = OptionParser.parse_head(["--source", "from_docs/", "--", "1", "2", "3"])
164-
assert options == {[source: "from_docs/"], ["1", "2", "3"], []}
158+
options = OptionParser.parse_head(["--source", "foo", "--", "1", "2", "3"])
159+
assert options == {[source: "foo"], ["1", "2", "3"], []}
165160

166-
options = OptionParser.parse(["--no-dash", "foo", "bar", "--", "-x"])
167-
assert options == {[no_dash: true], ["foo", "bar", "-x"], []}
161+
options = OptionParser.parse(["--source", "foo", "bar", "--", "-x"])
162+
assert options == {[source: "foo"], ["bar", "-x"], []}
168163

169-
options = OptionParser.parse_head(["--no-dash", "foo", "bar", "--", "-x"])
170-
assert options == {[no_dash: true], ["foo", "bar", "--", "-x"], []}
164+
options = OptionParser.parse_head(["--source", "foo", "bar", "--", "-x"])
165+
assert options == {[source: "foo"], ["bar", "--", "-x"], []}
171166
end
172167

173168
test "goes beyond the first non option arguments" do
@@ -288,6 +283,6 @@ defmodule OptionParserTest do
288283
assert OptionParser.next(["--bool=", "..."], config)
289284
== {:invalid, "--bool", "", ["..."]}
290285
assert OptionParser.next(["--no-bool=", "..."], config)
291-
== {:undefined, "--no-bool", "", ["..."]}
286+
== {:invalid, "--no-bool", "", ["..."]}
292287
end
293288
end

lib/mix/lib/mix/tasks/app.start.ex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@ defmodule Mix.Tasks.App.Start do
1818
1919
"""
2020
def run(args) do
21-
{opts, _, _} = OptionParser.parse(args)
2221
Mix.Task.run "loadpaths", ["--no-readd"|args]
2322

24-
unless opts[:no_compile] do
23+
unless "--no-compile" in args do
2524
Mix.Task.run "compile", ["--no-readd"|args]
2625
end
2726

28-
unless opts[:no_start] do
27+
unless "--no-start" in args do
2928
start(Mix.Project.config[:app])
3029
end
3130

lib/mix/lib/mix/tasks/archive.build.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ defmodule Mix.Tasks.Archive.Build do
3333

3434
def run(args) do
3535
{opts, _, _} = OptionParser.parse(args, aliases: [o: :output, i: :input],
36-
switches: [force: :boolean, no_compile: :boolean])
36+
switches: [force: :boolean, compile: :boolean])
3737

3838
project = Mix.Project.get
3939

40-
if project && !opts[:no_compile] do
40+
if project && Keyword.get(opts, :compile, true) do
4141
Mix.Task.run :compile, args
4242
end
4343

lib/mix/lib/mix/tasks/compile.elixir.ex

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@ defmodule Mix.Tasks.Compile.Elixir do
1717
1818
## Command line options
1919
20-
* `--force` - forces compilation regardless of modification times
21-
* `--no-docs` - do not attach documentation to compiled modules
22-
* `--no-debug-info` - do not attach debug info to compiled modules
23-
* `--ignore-module-conflict`
24-
- do not emit warnings if a module was previously defined
25-
* `--warnings-as-errors`
26-
- treat warnings as errors and return a non-zero exit code
20+
* `--force` - forces compilation regardless of modification times
21+
* `--docs` (`--no-docs`) - attach (or not) documentation to compiled modules
22+
* `--debug-info` (`--no-debug-info`) - attach (or not) debug info to compiled modules
23+
* `--ignore-module-conflict` - do not emit warnings if a module was previously defined
24+
* `--warnings-as-errors` - treat warnings as errors and return a non-zero exit code
2725
2826
## Configuration
2927

lib/mix/lib/mix/tasks/deps.check.ex

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Deps.Check do
22
use Mix.Task
33

44
import Mix.Dep, only: [loaded: 1, loaded_by_name: 2, format_dep: 1,
5-
format_status: 1, check_lock: 2, ok?: 1]
5+
format_status: 1, check_lock: 2, ok?: 1]
66

77
@moduledoc """
88
Checks if all dependencies are valid and if not, abort.
@@ -17,7 +17,6 @@ defmodule Mix.Tasks.Deps.Check do
1717
1818
"""
1919
def run(args) do
20-
{opts, _, _} = OptionParser.parse(args)
2120
lock = Mix.Dep.Lock.read
2221
all = Enum.map(loaded(env: Mix.env), &check_lock(&1, lock))
2322

@@ -27,10 +26,10 @@ defmodule Mix.Tasks.Deps.Check do
2726
cond do
2827
not_ok != [] ->
2928
show_not_ok(not_ok)
30-
compile == [] or opts[:no_compile] ->
29+
compile == [] or "--no-compile" in args ->
3130
:ok
3231
true ->
33-
Mix.Tasks.Deps.Compile.compile(compile, opts)
32+
Mix.Tasks.Deps.Compile.compile(compile)
3433
show_not_ok compile
3534
|> Enum.map(& &1.app)
3635
|> loaded_by_name(env: Mix.env)

lib/mix/lib/mix/tasks/deps.compile.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ defmodule Mix.Tasks.Deps.Compile do
3131
Mix.Project.get! # Require the project to be available
3232

3333
case OptionParser.parse(args) do
34-
{opts, [], _} ->
35-
compile(Enum.filter(loaded(env: Mix.env), &compilable?/1), opts)
36-
{opts, tail, _} ->
37-
compile(loaded_by_name(tail, env: Mix.env), opts)
34+
{_, [], _} ->
35+
compile(Enum.filter(loaded(env: Mix.env), &compilable?/1))
36+
{_, tail, _} ->
37+
compile(loaded_by_name(tail, env: Mix.env))
3838
end
3939
end
4040

4141
@doc false
42-
def compile(deps, _opts) do
42+
def compile(deps) do
4343
shell = Mix.shell
4444
config = Mix.Project.deps_config
4545

lib/mix/lib/mix/tasks/escript.build.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ defmodule Mix.Tasks.Escript.Build do
7272
7373
"""
7474
def run(args) do
75-
{opts, _, _} = OptionParser.parse(args, switches: [force: :boolean, no_compile: :boolean])
75+
{opts, _, _} = OptionParser.parse(args, switches: [force: :boolean, compile: :boolean])
7676

7777
# Require the project to be available
7878
Mix.Project.get!
7979

80-
unless opts[:no_compile] do
80+
if Keyword.get(opts, :compile, true) do
8181
Mix.Task.run :compile, args
8282
end
8383

lib/mix/lib/mix/tasks/run.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ defmodule Mix.Tasks.Run do
3636
def run(args) do
3737
{opts, head, _} = OptionParser.parse_head(args,
3838
aliases: [r: :require, pr: :parallel_require, e: :eval, c: :config],
39-
switches: [parallel_require: :keep, require: :keep, eval: :keep, config: :keep])
39+
switches: [parallel_require: :keep, require: :keep, eval: :keep, config: :keep,
40+
halt: :boolean, compile: :boolean, deps_check: :boolean, start: :boolean])
4041

4142
# Require the project to be available
4243
Mix.Project.get!
@@ -63,7 +64,8 @@ defmodule Mix.Tasks.Run do
6364
Mix.raise "No such file: #{file}"
6465
end
6566
end
66-
if opts[:no_halt], do: :timer.sleep(:infinity)
67+
68+
unless Keyword.get(opts, :halt, true), do: :timer.sleep(:infinity)
6769
end
6870

6971
defp process_config(opts) do

0 commit comments

Comments
 (0)