@@ -226,6 +226,8 @@ defmodule Access do
226226 end
227227 end
228228
229+ defguardp is_probably_keyword ( list ) when list == [ ] or is_atom ( elem ( hd ( list ) , 0 ) )
230+
229231 @ doc """
230232 Fetches the value for the given key in a container (a map, keyword
231233 list, or struct that implements the `Access` behaviour).
@@ -485,7 +487,7 @@ defmodule Access do
485487 ## Accessors
486488
487489 @ doc """
488- Returns a function that accesses the given key in a map/struct.
490+ Returns a function that accesses the given key in a map/struct/keyword list .
489491
490492 The returned function is typically passed as an accessor to `Kernel.get_in/2`,
491493 `Kernel.get_and_update_in/3`, and friends.
@@ -514,30 +516,56 @@ defmodule Access do
514516 iex> pop_in(map, [Access.key(:user), Access.key(:name)])
515517 {"john", %{user: %{}}}
516518
517- An error is raised if the accessed structure is not a map or a struct:
519+ iex> keyword = [user: [name: "john"]]
520+ iex> get_in(keyword, [Access.key(:unknown, []), Access.key(:name, "john")])
521+ "john"
522+ iex> get_and_update_in(keyword, [Access.key(:user), Access.key(:name)], fn prev ->
523+ ...> {prev, String.upcase(prev)}
524+ ...> end)
525+ {"john", [user: [name: "JOHN"]]}
526+ iex> pop_in(keyword, [Access.key(:user), Access.key(:name)])
527+ {"john", [user: []]}
518528
519- iex> get_in([], [Access.key(:foo)])
520- ** (BadMapError) expected a map, got:
521- ...
529+ An error is raised if the accessed structure is not a map, struct, or keyword list:
530+
531+ iex> get_in(123, [Access.key(:foo)])
532+ ** (RuntimeError) Access.key/2 expected a map/struct/keyword list, got: ...
533+
534+ iex> put_in([1, 2, 3], [Access.key(:foo)], :bar)
535+ ** (RuntimeError) Access.key/2 expected a map/struct/keyword list, got: ...
522536 """
523- @ spec key ( key , term ) :: access_fun ( data :: struct | map , current_value :: term )
537+ @ spec key ( key , term ) :: access_fun ( data :: struct | map | keyword , current_value :: term )
524538 def key ( key , default \\ nil ) do
525539 fn
526- :get , data , next ->
540+ :get , % { } = data , next ->
527541 next . ( Map . get ( data , key , default ) )
528542
529- :get_and_update , data , next ->
543+ :get_and_update , % { } = data , next ->
530544 value = Map . get ( data , key , default )
531545
532546 case next . ( value ) do
533547 { get , update } -> { get , Map . put ( data , key , update ) }
534548 :pop -> { value , Map . delete ( data , key ) }
535549 end
550+
551+ :get , data , next when is_probably_keyword ( data ) ->
552+ next . ( Keyword . get ( data , key , default ) )
553+
554+ :get_and_update , data , next when is_probably_keyword ( data ) ->
555+ value = Keyword . get ( data , key , default )
556+
557+ case next . ( value ) do
558+ { get , update } -> { get , Keyword . put ( data , key , update ) }
559+ :pop -> { value , Keyword . delete ( data , key ) }
560+ end
561+
562+ _op , data , _next ->
563+ raise "Access.key/2 expected a map/struct/keyword list, got: #{ inspect ( data ) } "
536564 end
537565 end
538566
539567 @ doc """
540- Returns a function that accesses the given key in a map/struct.
568+ Returns a function that accesses the given key in a map/struct/keyword list .
541569
542570 The returned function is typically passed as an accessor to `Kernel.get_in/2`,
543571 `Kernel.get_and_update_in/3`, and friends.
@@ -546,6 +574,19 @@ defmodule Access do
546574
547575 ## Examples
548576
577+ iex> keyword = [user: [name: "john"]]
578+ iex> get_in(keyword, [Access.key!(:user), Access.key!(:name)])
579+ "john"
580+ iex> get_and_update_in(keyword, [Access.key!(:user), Access.key!(:name)], fn prev ->
581+ ...> {prev, String.upcase(prev)}
582+ ...> end)
583+ {"john", [user: [name: "JOHN"]]}
584+ iex> pop_in(keyword, [Access.key!(:user), Access.key!(:name)])
585+ {"john", [user: []]}
586+ iex> get_in(keyword, [Access.key!(:user), Access.key!(:unknown)])
587+ ** (KeyError) key :unknown not found in:
588+ ...
589+
549590 iex> map = %{user: %{name: "john"}}
550591 iex> get_in(map, [Access.key!(:user), Access.key!(:name)])
551592 "john"
@@ -574,13 +615,15 @@ defmodule Access do
574615 `Access.key!/1` is useful when the key is not known in advance
575616 and must be accessed dynamically.
576617
577- An error is raised if the accessed structure is not a map/struct:
618+ An error is raised if the accessed structure is not a map/struct/keyword list :
578619
579- iex> get_in([] , [Access.key!(:foo)])
580- ** (RuntimeError) Access.key!/1 expected a map/struct, got: []
620+ iex> get_in(123 , [Access.key!(:foo)])
621+ ** (RuntimeError) Access.key!/1 expected a map/struct/keyword list , got: 123
581622
623+ iex> put_in([1, 2, 3], [Access.key!(:foo)], :bar)
624+ ** (RuntimeError) Access.key!/1 expected a map/struct/keyword list, got: ...
582625 """
583- @ spec key! ( key ) :: access_fun ( data :: struct | map , current_value :: term )
626+ @ spec key! ( key ) :: access_fun ( data :: struct | map | keyword , current_value :: term )
584627 def key! ( key ) do
585628 fn
586629 :get , % { } = data , next ->
@@ -594,8 +637,19 @@ defmodule Access do
594637 :pop -> { value , Map . delete ( data , key ) }
595638 end
596639
640+ :get , data , next when is_probably_keyword ( data ) ->
641+ next . ( Keyword . fetch! ( data , key ) )
642+
643+ :get_and_update , data , next when is_probably_keyword ( data ) ->
644+ value = Keyword . fetch! ( data , key )
645+
646+ case next . ( value ) do
647+ { get , update } -> { get , Keyword . put ( data , key , update ) }
648+ :pop -> { value , Keyword . delete ( data , key ) }
649+ end
650+
597651 _op , data , _next ->
598- raise "Access.key!/1 expected a map/struct, got: #{ inspect ( data ) } "
652+ raise "Access.key!/1 expected a map/struct/keyword list , got: #{ inspect ( data ) } "
599653 end
600654 end
601655
0 commit comments