%% ------------------------------------------------------------------- %% %% cuttlefish_enum: datatype for simple enum settings with %% customizable names and values %% %% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(cuttlefish_enum). -type enum_list() :: [{atom()|string(), term()}]. -type enum() :: {enum, enum_list()}. -type strict_enum_list() :: [{string(), term()}]. -type strict_enum() :: {enum, strict_enum_list()}. -export_type([enum/0, strict_enum/0]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. -export([ to_string/2, parse/2 ]). -define(FMT(F, A), lists:flatten(io_lib:format(F, A))). -spec to_string(string() | atom(), enum() ) -> string() | cuttlefish_error:error(). to_string(Value, {enum, _Raw} = Enum) -> FriendlEnum = assuage_enum(Enum), case to_string_by_value(Value, FriendlEnum) of {K, _} -> atom_to_list_maybe(K); false -> to_string_by_key(atom_to_list_maybe(Value), FriendlEnum) end. -spec to_string_by_value(term(), strict_enum()) -> term(). to_string_by_value(Value, {enum, FriendlEnum}) -> lists:keyfind(Value, 2, FriendlEnum). -spec to_string_by_key(atom() | string(), strict_enum()) -> string(). to_string_by_key(Key, {enum, FriendlEnum}) -> case lists:keyfind(Key, 1, FriendlEnum) of {K, _} -> K; false -> to_error(Key, FriendlEnum) end. -spec parse(term(), enum()) -> term() | cuttlefish_error:error(). parse(Value, {enum, _Raw} = Enum) -> FriendlEnum = assuage_enum(Enum), case parse_by_key(atom_to_list_maybe(Value), FriendlEnum) of {_Key, Val} -> Val; false -> parse_by_value(Value, FriendlEnum) end. -spec parse_by_key(atom() | string() | term(), strict_enum()) -> {string(), term()} | false. parse_by_key(Key, {enum, FriendlEnum}) -> lists:keyfind(Key, 1, FriendlEnum). -spec parse_by_value(term(), strict_enum()) -> term() | cuttlefish_error:error(). parse_by_value(Value, {enum, FriendlEnum}) -> case lists:keyfind(Value, 2, FriendlEnum) of false -> to_error(Value, FriendlEnum); {_Key, Value} -> Value end. -spec to_error(atom() | string()| term(), strict_enum_list()) -> cuttlefish_error:error(). to_error(Value, FriendlEnum) -> Acceptable = [Key || {Key, _} <- FriendlEnum], {error, {enum_name, {atom_to_list_maybe(Value), Acceptable}}}. -spec atom_to_list_maybe(term()) -> term(). atom_to_list_maybe(Atom) when is_atom(Atom) -> atom_to_list(Atom); atom_to_list_maybe(Other) -> Other. -spec assuage_enum(enum()) -> strict_enum() | cuttlefish_error:error(). assuage_enum({enum, Enum}) -> FriendlEnum = assuage_enum(Enum, []), case cuttlefish_error:is_error(FriendlEnum) of true -> FriendlEnum; _ -> {enum, FriendlEnum} end. -spec assuage_enum(enum_list(), strict_enum_list()) -> strict_enum_list() | cuttlefish_error:error(). assuage_enum([], FriendlEnum) -> lists:reverse(FriendlEnum); %% If the head is a 2-tuple; yay! assuage_enum([{Key, Value}|EnumTail], FriendlEnum) when is_atom(Key) -> assuage_enum(EnumTail, [ { cuttlefish_datatypes:to_string(Key, atom), Value } | FriendlEnum ]); assuage_enum([{Key, Value}|EnumTail], FriendlEnum) when is_list(Key) -> assuage_enum(EnumTail, [ { Key, Value } | FriendlEnum ]); %% If the head is just a string or atom, fall here. assuage_enum([Key|EnumTail], FriendlEnum) when is_atom(Key) -> assuage_enum(EnumTail, [ { cuttlefish_datatypes:to_string(Key, atom), Key } | FriendlEnum]); assuage_enum([Key|EnumTail], FriendlEnum) when is_list(Key) -> assuage_enum(EnumTail, [{Key, Key} | FriendlEnum]); assuage_enum([BadTuple|_], _) when is_tuple(BadTuple) -> {error, {enum_format, BadTuple}}; assuage_enum([ErroneousItem|_], _) -> {error, {enum_format, ErroneousItem}}. -ifdef(TEST). -define(XLATE(X), lists:flatten(cuttlefish_error:xlate(X))). parse_test() -> ?assertEqual(1, parse("one", {enum, [{"one", 1}, two]})), ?assertEqual(two, parse("two", {enum, [{"one", 1}, two]})), ok. assuage_enum_test() -> ?assertEqual({enum, [{"true", true}, {"false", false}]}, assuage_enum({enum, [true, false]})), ?assertEqual({enum, [{"true", "true"}, {"false", "false"}]}, assuage_enum({enum, ["true", "false"]})), ?assertEqual({enum, [{"one", 1}, {"two", 2}]}, assuage_enum({enum, [{one, 1}, {"two", 2}]})), ?assertEqual({enum, [{"off", off}, {"on", "On"}]}, assuage_enum({enum, [off, {on, "On"}]})), ok. assuage_enum_error_test() -> ?assertEqual( "Enum elements must be atoms, strings, or 2-tuples with " "atom or string as first element. Bad value: {one,two,three}", ?XLATE(assuage_enum({enum, [{one, two, three}, oops]})) ), ?assertEqual( "Enum elements must be atoms, strings, or 2-tuples with " "atom or string as first element. Bad value: 7", ?XLATE(assuage_enum({enum, [oops, 7]})) ), ok. -endif.