@@ -53,6 +53,7 @@ defmodule OptionParser do
53
53
54
54
* `:switches` or `:strict` - see the "Switch definitions" section below
55
55
* `:aliases` - see the "Aliases" section below
56
+ * `:allow_nonexistent_atoms` - see the "Parsing undefined switches" section below
56
57
57
58
## Switch definitions
58
59
@@ -112,6 +113,15 @@ defmodule OptionParser do
112
113
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
113
114
{[op: false], ["path/to/file"], []}
114
115
116
+ ### Parsing undefined switches
117
+
118
+ By default, only arguments that have defined atom representation will be parsed.
119
+ This happens because creating atoms at runtime is considered to be unsafe,
120
+ but you can still force creation of atoms by passing `allow_nonexistent_atoms: true`
121
+ to the list of function options.
122
+
123
+ This is useful when you are building command-line applications that receive dynamically-named arguments.
124
+
115
125
## Aliases
116
126
117
127
A set of aliases can be specified in the `:aliases` option:
@@ -251,8 +261,8 @@ defmodule OptionParser do
251
261
{ Enum . reverse ( opts ) , Enum . reverse ( args ) , Enum . reverse ( invalid ) }
252
262
end
253
263
254
- defp do_parse ( argv , { aliases , switches , strict? } = config , opts , args , invalid , all? ) do
255
- case next ( argv , aliases , switches , strict? ) do
264
+ defp do_parse ( argv , { aliases , switches , strict? , allow_nonexistent_atoms? } = config , opts , args , invalid , all? ) do
265
+ case next ( argv , aliases , switches , strict? , allow_nonexistent_atoms? ) do
256
266
{ :ok , option , value , rest } ->
257
267
# the option exists and it was successfully parsed
258
268
kinds = List . wrap Keyword . get ( switches , option )
@@ -307,35 +317,35 @@ defmodule OptionParser do
307
317
{ :error , argv }
308
318
309
319
def next ( argv , opts \\ [ ] ) when is_list ( argv ) and is_list ( opts ) do
310
- { aliases , switches , strict? } = compile_config ( opts )
311
- next ( argv , aliases , switches , strict? )
320
+ { aliases , switches , strict? , allow_nonexistent_atoms? } = compile_config ( opts )
321
+ next ( argv , aliases , switches , strict? , allow_nonexistent_atoms? )
312
322
end
313
323
314
- defp next ( [ ] , _aliases , _switches , _strict? ) do
324
+ defp next ( [ ] , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
315
325
{ :error , [ ] }
316
326
end
317
327
318
- defp next ( [ "--" | _ ] = argv , _aliases , _switches , _strict? ) do
328
+ defp next ( [ "--" | _ ] = argv , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
319
329
{ :error , argv }
320
330
end
321
331
322
- defp next ( [ "-" | _ ] = argv , _aliases , _switches , _strict? ) do
332
+ defp next ( [ "-" | _ ] = argv , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
323
333
{ :error , argv }
324
334
end
325
335
326
- defp next ( [ "- " <> _ | _ ] = argv , _aliases , _switches , _strict? ) do
336
+ defp next ( [ "- " <> _ | _ ] = argv , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
327
337
{ :error , argv }
328
338
end
329
339
330
340
# Handles --foo or --foo=bar
331
- defp next ( [ "--" <> option | rest ] , _aliases , switches , strict? ) do
341
+ defp next ( [ "--" <> option | rest ] , _aliases , switches , strict? , allow_nonexistent_atoms? ) do
332
342
{ option , value } = split_option ( option )
333
- tagged = tag_option ( option , switches )
334
- do_next ( tagged , value , "--" <> option , rest , switches , strict? )
343
+ tagged = tag_option ( option , switches , allow_nonexistent_atoms? )
344
+ do_next ( tagged , value , "--" <> option , rest , switches , strict? , allow_nonexistent_atoms? )
335
345
end
336
346
337
347
# Handles -a, -abc, -abc=something
338
- defp next ( [ "-" <> option | rest ] = argv , aliases , switches , strict? ) do
348
+ defp next ( [ "-" <> option | rest ] = argv , aliases , switches , strict? , allow_nonexistent_atoms? ) do
339
349
{ option , value } = split_option ( option )
340
350
original = "-" <> option
341
351
@@ -345,26 +355,26 @@ defmodule OptionParser do
345
355
String . contains? ( option , [ "-" , "_" ] ) ->
346
356
{ :undefined , original , value , rest }
347
357
String . length ( option ) > 1 ->
348
- key = get_option_key ( option )
358
+ key = get_option_key ( option , allow_nonexistent_atoms? )
349
359
option_key = aliases [ key ]
350
360
if key && option_key do
351
361
IO . warn "multi-letter aliases are deprecated, got: #{ inspect ( key ) } "
352
- do_next ( { :default , option_key } , value , original , rest , switches , strict? )
362
+ do_next ( { :default , option_key } , value , original , rest , switches , strict? , allow_nonexistent_atoms? )
353
363
else
354
- next ( expand_multiletter_alias ( option , value ) ++ rest , aliases , switches , strict? )
364
+ next ( expand_multiletter_alias ( option , value ) ++ rest , aliases , switches , strict? , allow_nonexistent_atoms? )
355
365
end
356
366
true ->
357
367
# We have a regular one-letter alias here
358
- tagged = tag_oneletter_alias ( option , aliases )
359
- do_next ( tagged , value , original , rest , switches , strict? )
368
+ tagged = tag_oneletter_alias ( option , aliases , allow_nonexistent_atoms? )
369
+ do_next ( tagged , value , original , rest , switches , strict? , allow_nonexistent_atoms? )
360
370
end
361
371
end
362
372
363
- defp next ( argv , _aliases , _switches , _strict? ) do
373
+ defp next ( argv , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
364
374
{ :error , argv }
365
375
end
366
376
367
- defp do_next ( tagged , value , original , rest , switches , strict? ) do
377
+ defp do_next ( tagged , value , original , rest , switches , strict? , allow_nonexistent_atoms? ) do
368
378
if strict? and not option_defined? ( tagged , switches ) do
369
379
{ :undefined , original , value , rest }
370
380
else
@@ -489,6 +499,7 @@ defmodule OptionParser do
489
499
490
500
defp compile_config ( opts ) do
491
501
aliases = opts [ :aliases ] || [ ]
502
+ allow_nonexistent_atoms? = opts [ :allow_nonexistent_atoms ] || false
492
503
493
504
{ switches , strict? } = cond do
494
505
opts [ :switches ] && opts [ :strict ] ->
@@ -501,7 +512,7 @@ defmodule OptionParser do
501
512
{ [ ] , false }
502
513
end
503
514
504
- { aliases , switches , strict? }
515
+ { aliases , switches , strict? , allow_nonexistent_atoms? }
505
516
end
506
517
507
518
defp validate_option ( value , kinds ) do
@@ -552,27 +563,27 @@ defmodule OptionParser do
552
563
end
553
564
end
554
565
555
- defp tag_option ( "no-" <> option = original , switches ) do
566
+ defp tag_option ( "no-" <> option = original , switches , allow_nonexistent_atoms? ) do
556
567
cond do
557
- ( negated = get_option_key ( option ) ) && :boolean in List . wrap ( switches [ negated ] ) ->
568
+ ( negated = get_option_key ( option , allow_nonexistent_atoms? ) ) && :boolean in List . wrap ( switches [ negated ] ) ->
558
569
{ :negated , negated }
559
- option_key = get_option_key ( original ) ->
570
+ option_key = get_option_key ( original , allow_nonexistent_atoms? ) ->
560
571
{ :default , option_key }
561
572
true ->
562
573
:unknown
563
574
end
564
575
end
565
576
566
- defp tag_option ( option , _switches ) do
567
- if option_key = get_option_key ( option ) do
577
+ defp tag_option ( option , _switches , allow_nonexistent_atoms? ) do
578
+ if option_key = get_option_key ( option , allow_nonexistent_atoms? ) do
568
579
{ :default , option_key }
569
580
else
570
581
:unknown
571
582
end
572
583
end
573
584
574
- defp tag_oneletter_alias ( alias , aliases ) when is_binary ( alias ) do
575
- if option_key = aliases [ to_existing_key ( alias ) ] do
585
+ defp tag_oneletter_alias ( alias , aliases , allow_nonexistent_atoms? ) when is_binary ( alias ) do
586
+ if option_key = aliases [ to_existing_key ( alias , allow_nonexistent_atoms? ) ] do
576
587
{ :default , option_key }
577
588
else
578
589
:unknown
@@ -662,13 +673,15 @@ defmodule OptionParser do
662
673
defp to_underscore ( << >> , acc ) ,
663
674
do: acc
664
675
665
- def get_option_key ( option ) do
676
+ def get_option_key ( option , allow_nonexistent_atoms? ) do
666
677
if string = to_underscore ( option ) do
667
- to_existing_key ( string )
678
+ to_existing_key ( string , allow_nonexistent_atoms? )
668
679
end
669
680
end
670
681
671
- defp to_existing_key ( option ) do
682
+ defp to_existing_key ( option , true ) ,
683
+ do: String . to_atom ( option )
684
+ defp to_existing_key ( option , false ) do
672
685
try do
673
686
String . to_existing_atom ( option )
674
687
rescue
@@ -702,7 +715,8 @@ defmodule OptionParser do
702
715
end
703
716
704
717
defp get_type ( option , opts , types ) do
705
- key = option |> String . trim_leading ( "-" ) |> get_option_key ( )
718
+ allow_nonexistent_atoms? = opts [ :allow_nonexistent_atoms ] || false
719
+ key = option |> String . trim_leading ( "-" ) |> get_option_key ( allow_nonexistent_atoms? )
706
720
707
721
if option_key = opts [ :aliases ] [ key ] do
708
722
types [ option_key ]
0 commit comments