@@ -53,6 +53,7 @@ defmodule OptionParser do
5353
5454 * `:switches` or `:strict` - see the "Switch definitions" section below
5555 * `:aliases` - see the "Aliases" section below
56+ * `:allow_nonexistent_atoms` - see the "Parsing undefined switches" section below
5657
5758 ## Switch definitions
5859
@@ -112,6 +113,15 @@ defmodule OptionParser do
112113 iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
113114 {[op: false], ["path/to/file"], []}
114115
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+
115125 ## Aliases
116126
117127 A set of aliases can be specified in the `:aliases` option:
@@ -251,8 +261,8 @@ defmodule OptionParser do
251261 { Enum . reverse ( opts ) , Enum . reverse ( args ) , Enum . reverse ( invalid ) }
252262 end
253263
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
256266 { :ok , option , value , rest } ->
257267 # the option exists and it was successfully parsed
258268 kinds = List . wrap Keyword . get ( switches , option )
@@ -307,35 +317,35 @@ defmodule OptionParser do
307317 { :error , argv }
308318
309319 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? )
312322 end
313323
314- defp next ( [ ] , _aliases , _switches , _strict? ) do
324+ defp next ( [ ] , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
315325 { :error , [ ] }
316326 end
317327
318- defp next ( [ "--" | _ ] = argv , _aliases , _switches , _strict? ) do
328+ defp next ( [ "--" | _ ] = argv , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
319329 { :error , argv }
320330 end
321331
322- defp next ( [ "-" | _ ] = argv , _aliases , _switches , _strict? ) do
332+ defp next ( [ "-" | _ ] = argv , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
323333 { :error , argv }
324334 end
325335
326- defp next ( [ "- " <> _ | _ ] = argv , _aliases , _switches , _strict? ) do
336+ defp next ( [ "- " <> _ | _ ] = argv , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
327337 { :error , argv }
328338 end
329339
330340 # 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
332342 { 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? )
335345 end
336346
337347 # 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
339349 { option , value } = split_option ( option )
340350 original = "-" <> option
341351
@@ -345,26 +355,26 @@ defmodule OptionParser do
345355 String . contains? ( option , [ "-" , "_" ] ) ->
346356 { :undefined , original , value , rest }
347357 String . length ( option ) > 1 ->
348- key = get_option_key ( option )
358+ key = get_option_key ( option , allow_nonexistent_atoms? )
349359 option_key = aliases [ key ]
350360 if key && option_key do
351361 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? )
353363 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? )
355365 end
356366 true ->
357367 # 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? )
360370 end
361371 end
362372
363- defp next ( argv , _aliases , _switches , _strict? ) do
373+ defp next ( argv , _aliases , _switches , _strict? , _allow_nonexistent_atoms? ) do
364374 { :error , argv }
365375 end
366376
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
368378 if strict? and not option_defined? ( tagged , switches ) do
369379 { :undefined , original , value , rest }
370380 else
@@ -489,6 +499,7 @@ defmodule OptionParser do
489499
490500 defp compile_config ( opts ) do
491501 aliases = opts [ :aliases ] || [ ]
502+ allow_nonexistent_atoms? = opts [ :allow_nonexistent_atoms ] || false
492503
493504 { switches , strict? } = cond do
494505 opts [ :switches ] && opts [ :strict ] ->
@@ -501,7 +512,7 @@ defmodule OptionParser do
501512 { [ ] , false }
502513 end
503514
504- { aliases , switches , strict? }
515+ { aliases , switches , strict? , allow_nonexistent_atoms? }
505516 end
506517
507518 defp validate_option ( value , kinds ) do
@@ -552,27 +563,27 @@ defmodule OptionParser do
552563 end
553564 end
554565
555- defp tag_option ( "no-" <> option = original , switches ) do
566+ defp tag_option ( "no-" <> option = original , switches , allow_nonexistent_atoms? ) do
556567 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 ] ) ->
558569 { :negated , negated }
559- option_key = get_option_key ( original ) ->
570+ option_key = get_option_key ( original , allow_nonexistent_atoms? ) ->
560571 { :default , option_key }
561572 true ->
562573 :unknown
563574 end
564575 end
565576
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
568579 { :default , option_key }
569580 else
570581 :unknown
571582 end
572583 end
573584
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
576587 { :default , option_key }
577588 else
578589 :unknown
@@ -662,13 +673,15 @@ defmodule OptionParser do
662673 defp to_underscore ( << >> , acc ) ,
663674 do: acc
664675
665- def get_option_key ( option ) do
676+ def get_option_key ( option , allow_nonexistent_atoms? ) do
666677 if string = to_underscore ( option ) do
667- to_existing_key ( string )
678+ to_existing_key ( string , allow_nonexistent_atoms? )
668679 end
669680 end
670681
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
672685 try do
673686 String . to_existing_atom ( option )
674687 rescue
@@ -702,7 +715,8 @@ defmodule OptionParser do
702715 end
703716
704717 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? )
706720
707721 if option_key = opts [ :aliases ] [ key ] do
708722 types [ option_key ]
0 commit comments