-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add parse!/2 alternative in OptionParser #4021
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,10 @@ defmodule OptionParser do | |
| @type errors :: [{String.t, String.t | nil}] | ||
| @type options :: [switches: Keyword.t, strict: Keyword.t, aliases: Keyword.t] | ||
|
|
||
| defmodule InvalidOptionError do | ||
| defexception [:message] | ||
| end | ||
|
|
||
| @doc """ | ||
| Parses `argv` into a keywords list. | ||
|
|
||
|
|
@@ -64,6 +68,9 @@ defmodule OptionParser do | |
|
|
||
| If a switch can't be parsed, it is returned in the invalid options list. | ||
|
|
||
| If you want to raise an exception for all the invalid options, please use | ||
| `parse!/2`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove this addition; in many places we have |
||
|
|
||
| The following extra "types" are supported: | ||
|
|
||
| * `:keep` - keeps duplicated items in the list instead of overriding them. | ||
|
|
@@ -117,6 +124,36 @@ defmodule OptionParser do | |
| do_parse(argv, compile_config(opts), [], [], [], true) | ||
| end | ||
|
|
||
| @doc """ | ||
| The same as `parse/2` but raises an `OptionParser.InvalidOptionError` | ||
| exception if any invalid options are given. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would make this summary shorter without mentioning the kind of error that is raised (we can mention that in the body of the doc):
This is similar to the documentation of other bang variants such as
|
||
|
|
||
| If there weren't any errors, returns a three-element tuple as follows: | ||
|
|
||
| 1. parsed options, | ||
| 2. remaining arguments, | ||
| 3. empty list. | ||
|
|
||
| ## Examples | ||
|
|
||
| iex> OptionParser.parse!(["--limit", "xyz"], strict: [limit: :integer]) | ||
| ** (OptionParser.InvalidOptionError) 1 error found! Option --limit is of the wrong type, expected a integer, given "xyz" | ||
|
|
||
| iex> OptionParser.parse!(["--unknown", "xyz"], strict: []) | ||
| ** (OptionParser.InvalidOptionError) 1 error found! Unknown option --unknown | ||
|
|
||
| iex> OptionParser.parse!(["-l", "xyz", "-f", "bar"], strict: [limit: :integer, foo: :integer], aliases: [l: :limit, f: :foo]) | ||
| ** (OptionParser.InvalidOptionError) 2 errors found! Option -l is of the wrong type, expected a integer, given "xyz". Option -f is of the wrong type, expected a integer, given "bar" | ||
|
|
||
| """ | ||
| @spec parse!(argv, options) :: {parsed, argv, errors} | no_return | ||
| def parse!(argv, opts \\ []) when is_list(argv) and is_list(opts) do | ||
| case parse(argv, opts) do | ||
| {_parsed, _argv, []} = result -> result | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this function should return |
||
| {_parsed, _argv, errors} -> raise InvalidOptionError, message: format_errors(errors, opts) | ||
| end | ||
| end | ||
|
|
||
| @doc """ | ||
| Similar to `parse/2` but only parses the head of `argv`; | ||
| as soon as it finds a non-switch, it stops parsing. | ||
|
|
@@ -137,14 +174,40 @@ defmodule OptionParser do | |
| do_parse(argv, compile_config(opts), [], [], [], false) | ||
| end | ||
|
|
||
| @doc """ | ||
| The same as `parse_head/2` but raises an `OptionParser.InvalidOptionError` | ||
| exception if any invalid options are given. | ||
|
|
||
| If there weren't any errors, returns a three-element tuple as follows: | ||
|
|
||
| 1. parsed options, | ||
| 2. remaining arguments, | ||
| 3. empty list. | ||
|
|
||
| ## Examples | ||
|
|
||
| iex> OptionParser.parse_head!(["--number", "lib", "test/enum_test.exs", "--verbose"], strict: [number: :integer]) | ||
| ** (OptionParser.InvalidOptionError) 1 error found! Option --number is of the wrong type, expected a integer, given "lib" | ||
|
|
||
| iex> OptionParser.parse_head!(["--verbose", "true", "--source", "lib", "test/enum_test.exs", "--unlock"], strict: [verbose: :integer, source: :integer]) | ||
| ** (OptionParser.InvalidOptionError) 2 errors found! Option --verbose is of the wrong type, expected a integer, given "true". Option --source is of the wrong type, expected a integer, given "lib" | ||
| """ | ||
| @spec parse_head!(argv, options) :: {parsed, argv, errors} | no_return | ||
| def parse_head!(argv, opts \\ []) when is_list(argv) and is_list(opts) do | ||
| case parse_head(argv, opts) do | ||
| {_parsed, _argv, []} = result -> result | ||
| {_parsed, _argv, errors} -> raise InvalidOptionError, message: format_errors(errors, opts) | ||
| end | ||
| end | ||
|
|
||
| defp do_parse([], _config, opts, args, invalid, _all?) do | ||
| {Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)} | ||
| end | ||
|
|
||
| defp do_parse(argv, {aliases, switches, strict}=config, opts, args, invalid, all?) do | ||
| case next(argv, aliases, switches, strict) do | ||
| {:ok, option, value, rest} -> | ||
| # the option exist and it was successfully parsed | ||
| # the option exists and it was successfully parsed | ||
| kinds = List.wrap Keyword.get(switches, option) | ||
| new_opts = do_store_option(opts, option, value, kinds) | ||
| do_parse(rest, config, new_opts, args, invalid, all?) | ||
|
|
@@ -499,4 +562,28 @@ defmodule OptionParser do | |
| defp negative_number?(arg) do | ||
| match?({_, ""}, Float.parse(arg)) | ||
| end | ||
|
|
||
| defp format_errors(errors, opts) do | ||
| details = Enum.map_join(errors, ". ", &format_error(&1, opts)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we format this so that the output looks like ? I think it looks clearer and easier to read and understand. |
||
| total = Enum.count(errors) | ||
| error = if total == 1, do: "error", else: "errors" | ||
| "#{total} #{error} found! #{details}" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I mentioned this a while back, but I'm 👎 on the exclamation mark. Now, we even have this recent commit that removed the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No worries about those. My plan is to merge the OptionParser related PRs this week and I will fix any inconsistency (specially because those PRs have been blocking on me for a while). :)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @josevalim ah, awesome then. If you're going to merge this PR in the current state, give a look at my comments then :) |
||
| end | ||
|
|
||
| defp format_error({option, nil}, _) do | ||
| "Unknown option #{option}" | ||
| end | ||
|
|
||
| defp format_error({option, value}, opts) do | ||
| option_key = option |> String.lstrip(?-) |> String.to_atom() | ||
|
|
||
| type = | ||
| if option_alias = opts[:aliases][option_key] do | ||
| opts[:strict][option_alias] | ||
| else | ||
| opts[:strict][option_key] | ||
| end | ||
|
|
||
| "Option #{option} is of the wrong type, expected a #{type}, given #{inspect value}" | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're calling this
InvalidOptionErrorbut we're raising it with multiple errors for different options. Maybe we can call itInvalidOptionsError?