Skip to content

Commit 6ed7e40

Browse files
author
José Valim
committed
Add OptionParser.split/1
1 parent 2fc76fe commit 6ed7e40

File tree

2 files changed

+87
-2
lines changed

2 files changed

+87
-2
lines changed

lib/elixir/lib/option_parser.ex

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ defmodule OptionParser do
185185

186186
@spec next(argv, options) ::
187187
{:ok, key :: atom, value :: term, argv} |
188-
{:invalid, key :: atom, value :: term, argv} |
189-
{:undefined, key :: atom, value :: term, argv} |
188+
{:invalid, String.t, String.t | nil, argv} |
189+
{:undefined, String.t, String.t | nil, argv} |
190190
{:error, argv}
191191

192192
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
@@ -231,6 +231,62 @@ defmodule OptionParser do
231231
{:error, argv}
232232
end
233233

234+
@doc ~S"""
235+
Splits a string into argv chunks.
236+
237+
## Examples
238+
239+
iex> OptionParser.split("foo bar")
240+
["foo", "bar"]
241+
242+
iex> OptionParser.split("foo \"bar baz\"")
243+
["foo", "bar baz"]
244+
"""
245+
@spec split(String.t) :: argv
246+
def split(string) do
247+
do_split(strip_leading_spaces(string), "", [], nil)
248+
end
249+
250+
# If we have a escaped quote, simply remove the escape
251+
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
252+
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
253+
254+
# If we have a quote and we were not in a quote, start one
255+
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
256+
do: do_split(t, buffer, acc, quote)
257+
258+
# If we have a quote and we were inside it, close it
259+
defp do_split(<<quote, t :: binary>>, buffer, acc, quote),
260+
do: do_split(t, buffer, acc, nil)
261+
262+
# If we have a escaped quote/space, simply remove the escape as long as we are not inside a quote
263+
defp do_split(<<?\\, h, t :: binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
264+
do: do_split(t, <<buffer::binary, h>>, acc, nil)
265+
266+
# If we have space and we are outside of a quote, start new segment
267+
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
268+
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
269+
270+
# All other characters are moved to buffer
271+
defp do_split(<<h, t::binary>>, buffer, acc, quote) do
272+
do_split(t, <<buffer::binary, h>>, acc, quote)
273+
end
274+
275+
# Finish the string expecting a nil marker
276+
defp do_split(<<>>, "", acc, nil),
277+
do: Enum.reverse(acc)
278+
279+
defp do_split(<<>>, buffer, acc, nil),
280+
do: Enum.reverse([buffer|acc])
281+
282+
# Otherwise raise
283+
defp do_split(<<>>, _, _acc, marker) do
284+
raise "argv string did not terminate properly, a #{<<marker>>} was opened but never closed"
285+
end
286+
287+
defp strip_leading_spaces(" " <> t), do: strip_leading_spaces(t)
288+
defp strip_leading_spaces(t), do: t
289+
234290
## Helpers
235291

236292
defp compile_config(opts) do

lib/elixir/test/elixir/option_parser_test.exs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,33 @@ defmodule OptionParserTest do
285285
assert OptionParser.next(["--no-bool=", "..."], config)
286286
== {:invalid, "--no-bool", "", ["..."]}
287287
end
288+
289+
test "split" do
290+
assert OptionParser.split(~S[])
291+
== []
292+
293+
assert OptionParser.split(~S[foo])
294+
== ["foo"]
295+
296+
assert OptionParser.split(~S[foo bar])
297+
== ["foo", "bar"]
298+
299+
assert OptionParser.split(~S[ foo bar ])
300+
== ["foo", "bar"]
301+
302+
assert OptionParser.split(~S[foo\ bar])
303+
== ["foo bar"]
304+
305+
assert OptionParser.split(~S[foo" bar"])
306+
== ["foo bar"]
307+
308+
assert OptionParser.split(~S[foo\" bar\"])
309+
== ["foo\"", "bar\""]
310+
311+
assert OptionParser.split(~S[foo "\ bar\""])
312+
== ["foo", "\\ bar\""]
313+
314+
assert OptionParser.split(~S[foo '\"bar"\'\ '])
315+
== ["foo", "\\\"bar\"'\\ "]
316+
end
288317
end

0 commit comments

Comments
 (0)