%%%------------------------------------------------------------------- %%% @author bartlomiej.gorny@erlang-solutions.com %%% @doc %%% This module handles formatting maps. %% It allows for trimming output to selected fields, or to nothing at all. It also adds a label %% to a printout. %% To set up a limit for a map, you need to give recon a way to tell the map you want to %% trim from all the other maps, so you have to provide something like a 'type definition'. %% It can be either another map which is compared to the arg, or a fun. %%% @end %%%------------------------------------------------------------------- -module(recon_map). -author("bartlomiej.gorny@erlang-solutions.com"). %% API -export([limit/3, list/0, is_active/0, clear/0, remove/1, rename/2]). -export([process_map/1]). -type map_label() :: atom(). -type pattern() :: map() | function(). -type limit() :: all | none | atom() | binary() | [any()]. %% @doc quickly check if we want to do any record formatting -spec is_active() -> boolean(). is_active() -> case whereis(recon_ets_maps) of undefined -> false; _ -> true end. %% @doc remove all imported definitions, destroy the table, clean up clear() -> maybe_kill(recon_ets_maps), ok. %% @doc Limit output to selected keys of a map (can be 'none', 'all', a key or a list of keys). %% Pattern selects maps to process: a "pattern" is just a map, and if all key/value pairs of a pattern %% are present in a map (in other words, the pattern is a subset), then we say the map matches %% and we process it accordingly (apply the limit). %% %% Patterns are applied in alphabetical order, until a match is found. %% %% Instead of a pattern you can also provide a function which will take a map and return a boolean. %% @end -spec limit(map_label(), pattern(), limit()) -> ok | {error, any()}. limit(Label, #{} = Pattern, Limit) when is_atom(Label) -> store_pattern(Label, Pattern, Limit); limit(Label, Pattern, Limit) when is_atom(Label), is_function(Pattern) -> store_pattern(Label, Pattern, Limit). %% @doc prints out all "known" map definitions and their limit settings. %% Printout tells a map's name, the matching fields required, and the limit options. %% @end list() -> ensure_table_exists(), io:format("~nmap definitions and limits:~n"), list(ets:tab2list(patterns_table_name())). %% @doc remove a given map entry -spec remove(map_label()) -> true. remove(Label) -> ensure_table_exists(), ets:delete(patterns_table_name(), Label). %% @doc rename a given map entry, which allows to to change priorities for %% matching. The first argument is the current name, and the second %% argument is the new name. -spec rename(map_label(), map_label()) -> renamed | missing. rename(Name, NewName) -> ensure_table_exists(), case ets:lookup(patterns_table_name(), Name) of [{Name, Pattern, Limit}] -> ets:insert(patterns_table_name(), {NewName, Pattern, Limit}), ets:delete(patterns_table_name(), Name), renamed; [] -> missing end. %% @doc prints out all "known" map filter definitions and their settings. %% Printout tells the map's label, the matching patterns, and the limit options %% @end list([]) -> io:format("~n"), ok; list([{Label, Pattern, Limit} | Rest]) -> io:format("~p: ~p -> ~p~n", [Label, Pattern, Limit]), list(Rest). %% @private given a map, scans saved patterns for one that matches; if found, returns a label %% and a map with limits applied; otherwise returns 'none' and original map. %% Pattern can be: %%