Packages

Emergence data structure

Retired package: Deprecated - Replaced by OTP 27 stdlib: json module and em_filter 1.0.0

Current section

Files

Jump to
embryo src embryo.erl
Raw

src/embryo.erl

%%%-------------------------------------------------------------------
%%% @doc
%%% `embryo' - Library for managing Emergence configuration and embryo objects
%%%
%%% This module provides functions for:
%%% - Creating and manipulating embryo objects
%%% - Reading Emergence configuration files
%%% - Retrieving discovery service URLs
%%% - Merging lists of embryos
%%%
%%% @author YourName
%%% @copyright 2025 YourCompany
%%% @version 0.1.0
%%% @end
%%%-------------------------------------------------------------------
-module(embryo).
%% Public API
-export([new/1, to_map/1, merge_lists_by_url/2, read_emergence_conf/0, get_em_disco_url/0]).
%% Type specifications
-record(embryo, {properties}).
-type embryo() :: #embryo{}.
-type properties() :: map().
-type config_map() :: map().
%%====================================================================
%% API Functions
%%====================================================================
%%--------------------------------------------------------------------
%% @doc Creates a new embryo object with the given properties.
%%
%% @param Properties A map of properties for the embryo
%% @return A new embryo record
%% @end
%%--------------------------------------------------------------------
-spec new(properties()) -> embryo().
new(Properties) ->
#embryo{properties = Properties}.
%%--------------------------------------------------------------------
%% @doc Converts an embryo record to a map representation.
%%
%% @param Embryo The embryo record to convert
%% @return A map representation of the embryo
%% @end
%%--------------------------------------------------------------------
-spec to_map(embryo()) -> map().
to_map(#embryo{properties = Properties}) ->
#{properties => Properties}.
%%--------------------------------------------------------------------
%% @doc Reads the Emergence configuration file.
%%
%% Looks for emergence.conf in platform-specific locations:
%% - Unix: ~/.config/emergence/emergence.conf
%% - Windows: %APPDATA%\emergence\emergence.conf or %HOME%\AppData\Roaming\emergence\emergence.conf
%%
%% @return Configuration map or 'undefined' if the file doesn't exist
%% @end
%%--------------------------------------------------------------------
-spec read_emergence_conf() -> config_map() | undefined.
read_emergence_conf() ->
ConfigPath = case os:getenv("HOME") of
false ->
case os:getenv("APPDATA") of
false -> undefined;
AppData -> filename:join([AppData, "emergence", "emergence.conf"])
end;
Home ->
case os:type() of
{unix, _} -> filename:join([Home, ".config", "emergence", "emergence.conf"]);
{win32, _} -> filename:join([Home, "AppData", "Roaming", "emergence", "emergence.conf"]);
_ -> undefined
end
end,
case ConfigPath of
undefined -> undefined;
Path ->
case filelib:is_file(Path) of
true -> read_file(Path);
false -> undefined
end
end.
%%--------------------------------------------------------------------
%% @doc Gets the Emergence discovery service URL.
%%
%% Tries to get the URL from:
%% 1. Environment variable "server_url"
%% 2. "em_disco/server_url" in the configuration file
%% 3. Falls back to "http://localhost:8080" if not found
%%
%% @return The discovery service URL as a string
%% @end
%%--------------------------------------------------------------------
-spec get_em_disco_url() -> string().
get_em_disco_url() ->
case os:getenv("server_url") of
false ->
ConfigMap = case read_emergence_conf() of
undefined -> #{};
Map -> Map
end,
case maps:get("em_disco", ConfigMap, undefined) of
undefined -> "http://localhost:8080";
EmDisco ->
case maps:get("server_url", EmDisco, undefined) of
undefined -> "http://localhost:8080";
Url -> Url
end
end;
Url -> Url
end.
%%--------------------------------------------------------------------
%% @doc Merges two lists of embryos, removing duplicates based on URL.
%%
%% @param UriList First list of embryo records
%% @param OtherList Second list of embryo records
%% @return A merged list with duplicates removed
%% @end
%%--------------------------------------------------------------------
-spec merge_lists_by_url([embryo()], [embryo()]) -> [embryo()].
merge_lists_by_url(UriList, OtherList) ->
CombinedList = UriList ++ OtherList,
% Collect all URLs to track duplicates
{Result, _SeenUrls} = lists:foldl(
fun(Embryo, {Acc, Seen}) ->
Properties = Embryo#embryo.properties,
case maps:get(<<"url">>, Properties, undefined) of
undefined ->
{[Embryo | Acc], Seen};
Url ->
case sets:is_element(Url, Seen) of
true ->
{Acc, Seen};
false ->
{[Embryo | Acc], sets:add_element(Url, Seen)}
end
end
end,
{[], sets:new()},
CombinedList
),
lists:reverse(Result).
%%====================================================================
%% Internal Functions
%%====================================================================
%%--------------------------------------------------------------------
%% @doc Reads the contents of a file into a map.
%%
%% @private
%% @param Path Path to the file
%% @return A map representation of the file contents
%% @end
%%--------------------------------------------------------------------
-spec read_file(string()) -> map().
read_file(Path) ->
case file:open(Path, [read]) of
{ok, Device} ->
Result = read_lines(Device, #{}, ""),
file:close(Device),
Result;
{error, _} ->
#{}
end.
%%--------------------------------------------------------------------
%% @doc Reads lines from a file and builds a configuration map.
%%
%% @private
%% @param Device File device to read from
%% @param Map Current configuration map
%% @param CurrentSection Current section being read
%% @return Updated configuration map
%% @end
%%--------------------------------------------------------------------
-spec read_lines(file:io_device(), map(), string()) -> map().
read_lines(Device, Map, CurrentSection) ->
case file:read_line(Device) of
{ok, Line} ->
TrimmedLine = string:trim(Line),
case TrimmedLine of
[$[|Rest] ->
% Section header
Section = string:trim(Rest, trailing, "]"),
NewMap = Map#{Section => #{}},
read_lines(Device, NewMap, Section);
"" ->
% Empty line
read_lines(Device, Map, CurrentSection);
_ ->
% Key-value pair
case string:split(TrimmedLine, "=") of
[Key, Value] ->
TrimmedKey = string:trim(Key),
TrimmedValue = string:trim(Value, both, "\""),
NewMap = case maps:get(CurrentSection, Map, undefined) of
undefined ->
Map;
SectionMap ->
Map#{CurrentSection => SectionMap#{TrimmedKey => TrimmedValue}}
end,
read_lines(Device, NewMap, CurrentSection);
_ ->
% Not a key-value pair
read_lines(Device, Map, CurrentSection)
end
end;
eof ->
Map;
{error, _} ->
Map
end.