-module(yodel). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/yodel.gleam"). -export([get_string/2, get_string_or/3, parse_string/2, get_int/2, get_int_or/3, parse_int/2, get_float/2, get_float_or/3, parse_float/2, get_bool/2, get_bool_or/3, parse_bool/2, default_options/0, with_format/2, with_resolve_enabled/2, enable_resolve/1, disable_resolve/1, with_resolve_mode/2, get_format/1, is_resolve_enabled/1, get_resolve_mode/1, with_strict_resolve/1, with_lenient_resolve/1, auto_detect_format/1, as_json/1, as_toml/1, as_yaml/1, load_with_options/2, load/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Yodel is a type-safe configuration loader for Gleam that handles JSON,\n" " YAML, and TOML configs with automatic format detection, environment variable\n" " resolution, and an intuitive dot-notation API for accessing your config\n" " values.\n" "\n" " ```gleam\n" " import yodel\n" "\n" " let ctx = yodel.load(\"config.toml\")\n" " yodel.get_string(ctx, \"foo.bar\") // \"fooey\"\n" " ```\n" "\n" " Yodel can resolve placeholders in the configuration content, using environment variables.\n" " - Placeholders are defined as `${foo}` where `foo` is the placeholder key.\n" " - Placeholders can have default values like `${foo:bar}` where `bar` is the default value.\n" " - Placeholders can be nested like `${foo:${bar}}` where `bar` is another placeholder key.\n" "\n" " ```bash\n" " # system environment variables\n" " echo $FOO # \"fooey\"\n" " echo $BAR # \n" " ```\n" "\n" " ```toml\n" " # config.toml\n" " foo = \"${FOO}\"\n" " bar = \"${BAR:default}\"\n" " ```\n" " ```gleam\n" " import yodel\n" "\n" " let ctx = case yodel.load(\"config.toml\") {\n" " Ok(ctx) -> ctx\n" " Error(e) -> Error(e) // check your config!\n" " }\n" "\n" " yodel.get_string(ctx, \"foo\") // \"fooey\"\n" " yodel.get_string(ctx, \"bar\") // \"default\"\n" " ```\n" "\n" " Yodel makes it easy to access configuration values in your Gleam code.\n" " - Access values from your configuration using dot-notation.\n" " - Get string, integer, float, and boolean values from the configuration.\n" " - Optional return default values if the key is not found.\n" ). -file("src/yodel.gleam", 154). ?DOC( " Get a string value from the configuration.\n" " If the value is not a string, an error will be returned.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.get_string(ctx, \"foo\") {\n" " Ok(value) -> value // \"bar\"\n" " Error(e) -> Error(e)\n" " }\n" " ```\n" ). -spec get_string(yodel@context:context(), binary()) -> {ok, binary()} | {error, yodel@properties:properties_error()}. get_string(Ctx, Key) -> yodel@context:get_string(Ctx, Key). -file("src/yodel.gleam", 165). ?DOC( " Get a string value from the configuration, or a default value if the key is not found.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let value = yodel.get_string_or(ctx, \"foo\", \"default\")\n" " ```\n" ). -spec get_string_or(yodel@context:context(), binary(), binary()) -> binary(). get_string_or(Ctx, Key, Default) -> yodel@context:get_string_or(Ctx, Key, Default). -file("src/yodel.gleam", 182). ?DOC( " Parse a string value from the configuration.\n" "\n" " If the value is not a string, it will be converted to a string.\n" " An error will be returned if the value is not a string or cannot be\n" " converted to a string.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.parse_string(ctx, \"foo\") {\n" " Ok(value) -> value // \"42\"\n" " Error(e) -> Error(e)\n" " }\n" ). -spec parse_string(yodel@context:context(), binary()) -> {ok, binary()} | {error, yodel@properties:properties_error()}. parse_string(Ctx, Key) -> yodel@context:parse_string(Ctx, Key). -file("src/yodel.gleam", 200). ?DOC( " Get an integer value from the configuration.\n" " If the value is not an integer, an error will be returned.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.get_int(ctx, \"foo\") {\n" " Ok(value) -> value // 42\n" " Error(e) -> Error(e)\n" " }\n" " ```\n" ). -spec get_int(yodel@context:context(), binary()) -> {ok, integer()} | {error, yodel@properties:properties_error()}. get_int(Ctx, Key) -> yodel@context:get_int(Ctx, Key). -file("src/yodel.gleam", 211). ?DOC( " Get an integer value from the configuration, or a default value if the key is not found.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let value = yodel.get_int_or(ctx, \"foo\", 42)\n" " ```\n" ). -spec get_int_or(yodel@context:context(), binary(), integer()) -> integer(). get_int_or(Ctx, Key, Default) -> yodel@context:get_int_or(Ctx, Key, Default). -file("src/yodel.gleam", 229). ?DOC( " Parse an integer value from the configuration.\n" "\n" " If the value is not an integer, it will be converted to an integer.\n" " An error will be returned if the value is not an integer or cannot be\n" " converted to an integer.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.parse_int(ctx, \"foo\") {\n" " Ok(value) -> value // 42\n" " Error(e) -> Error(e)\n" " }\n" " ```\n" ). -spec parse_int(yodel@context:context(), binary()) -> {ok, integer()} | {error, yodel@properties:properties_error()}. parse_int(Ctx, Key) -> yodel@context:parse_int(Ctx, Key). -file("src/yodel.gleam", 243). ?DOC( " Get a float value from the configuration.\n" " If the value is not a float, an error will be returned.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.get_float(ctx, \"foo\") {\n" " Ok(value) -> value // 42.0\n" " Error(e) -> Error(e)\n" " }\n" ). -spec get_float(yodel@context:context(), binary()) -> {ok, float()} | {error, yodel@properties:properties_error()}. get_float(Ctx, Key) -> yodel@context:get_float(Ctx, Key). -file("src/yodel.gleam", 254). ?DOC( " Get a float value from the configuration, or a default value if the key is not found.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let value = yodel.get_float_or(ctx, \"foo\", 42.0)\n" " ```\n" ). -spec get_float_or(yodel@context:context(), binary(), float()) -> float(). get_float_or(Ctx, Key, Default) -> yodel@context:get_float_or(Ctx, Key, Default). -file("src/yodel.gleam", 272). ?DOC( " Parse a float value from the configuration.\n" "\n" " If the value is not a float, it will be converted to a float.\n" " An error will be returned if the value is not a float or cannot be\n" " converted to a float.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.parse_float(ctx, \"foo\") {\n" " Ok(value) -> value // 99.999\n" " Error(e) -> Error(e)\n" " }\n" " ```\n" ). -spec parse_float(yodel@context:context(), binary()) -> {ok, float()} | {error, yodel@properties:properties_error()}. parse_float(Ctx, Key) -> yodel@context:parse_float(Ctx, Key). -file("src/yodel.gleam", 286). ?DOC( " Get a boolean value from the configuration.\n" " If the value is not a boolean, an error will be returned.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.get_bool(ctx, \"foo\") {\n" " Ok(value) -> value // True\n" " Error(e) -> Error(e)\n" " }\n" ). -spec get_bool(yodel@context:context(), binary()) -> {ok, boolean()} | {error, yodel@properties:properties_error()}. get_bool(Ctx, Key) -> yodel@context:get_bool(Ctx, Key). -file("src/yodel.gleam", 297). ?DOC( " Get a boolean value from the configuration, or a default value if the key is not found.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let value = yodel.get_bool_or(ctx, \"foo\", False)\n" " ```\n" ). -spec get_bool_or(yodel@context:context(), binary(), boolean()) -> boolean(). get_bool_or(Ctx, Key, Default) -> yodel@context:get_bool_or(Ctx, Key, Default). -file("src/yodel.gleam", 315). ?DOC( " Parse a bool value from the configuration.\n" "\n" " If the value is not a bool, it will be converted to a bool.\n" " An error will be returned if the value is not a bool or cannot be\n" " converted to a bool.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.parse_bool(ctx, \"foo\") {\n" " Ok(value) -> value // True\n" " Error(e) -> Error(e)\n" " }\n" " ```\n" ). -spec parse_bool(yodel@context:context(), binary()) -> {ok, boolean()} | {error, yodel@properties:properties_error()}. parse_bool(Ctx, Key) -> yodel@context:parse_bool(Ctx, Key). -file("src/yodel.gleam", 334). ?DOC( " The default options for loading a configuration file.\n" "\n" " Default Options:\n" "\n" " - Format: `auto_format`\n" " - Resolve Enabled: `True`\n" " - Resolve Mode: `lenient_resolve`\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.load_with_options(\"config.toml\")\n" " ```\n" ). -spec default_options() -> yodel@options:options(). default_options() -> yodel@options:default(). -file("src/yodel.gleam", 348). ?DOC( " Set the format of the configuration file.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.with_format(yodel.json_format)\n" " |> yodel.load_with_options(\"config.json\")\n" " ```\n" ). -spec with_format(yodel@options:options(), yodel@options:format()) -> yodel@options:options(). with_format(Options, Format) -> yodel@options:with_format(Options, Format). -file("src/yodel.gleam", 426). ?DOC( " Enable or disable placeholder resolution.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.with_resolve_enabled(False)\n" " |> yodel.load_with_options(\"config.yaml\")\n" " ```\n" ). -spec with_resolve_enabled(yodel@options:options(), boolean()) -> yodel@options:options(). with_resolve_enabled(Options, Enabled) -> yodel@options:with_resolve_enabled(Options, Enabled). -file("src/yodel.gleam", 443). ?DOC( " Enable placeholder resolution.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.enable_resolve()\n" " |> yodel.load_with_options(\"config.yaml\")\n" " ```\n" ). -spec enable_resolve(yodel@options:options()) -> yodel@options:options(). enable_resolve(Options) -> with_resolve_enabled(Options, true). -file("src/yodel.gleam", 457). ?DOC( " Disable placeholder resolution.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.disable_resolve()\n" " |> yodel.load_with_options(\"config.yaml\")\n" " ```\n" ). -spec disable_resolve(yodel@options:options()) -> yodel@options:options(). disable_resolve(Options) -> with_resolve_enabled(Options, false). -file("src/yodel.gleam", 471). ?DOC( " Set the resolve mode.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.with_resolve_mode(yodel.strict_resolve)\n" " |> yodel.load_with_options(\"config.json\")\n" " ```\n" ). -spec with_resolve_mode(yodel@options:options(), yodel@options:resolve_mode()) -> yodel@options:options(). with_resolve_mode(Options, Mode) -> yodel@options:with_resolve_mode(Options, Mode). -file("src/yodel.gleam", 511). ?DOC( " Get the format of the configuration file.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let format = yodel.get_format(options)\n" " ```\n" ). -spec get_format(yodel@options:options()) -> yodel@options:format(). get_format(Options) -> yodel@options:get_format(Options). -file("src/yodel.gleam", 525). ?DOC( " Check if placeholder resolution is enabled.\n" "\n" " Example:\n" "\n" " ```gleam\n" " case yodel.is_resolve_enabled(options) {\n" " True -> \"Resolution is enabled\"\n" " False -> \"Resolution is disabled\"\n" " }\n" " ```\n" ). -spec is_resolve_enabled(yodel@options:options()) -> boolean(). is_resolve_enabled(Options) -> yodel@options:is_resolve_enabled(Options). -file("src/yodel.gleam", 536). ?DOC( " Get the resolve mode.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let mode = yodel.get_resolve_mode(options)\n" " ```\n" ). -spec get_resolve_mode(yodel@options:options()) -> yodel@options:resolve_mode(). get_resolve_mode(Options) -> yodel@options:get_resolve_mode(Options). -file("src/yodel.gleam", 540). -spec parse( binary(), yodel@options:format(), fun((yodel@properties:properties()) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}) ) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}. parse(Input, Format, Next) -> _pipe = yodel@parser:parse(Input, Format), gleam@result:'try'(_pipe, Next). -file("src/yodel.gleam", 549). -spec validate( yodel@properties:properties(), fun((yodel@properties:properties()) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}) ) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}. validate(Props, Handler) -> _pipe = yodel@validator:validate_properties(Props), gleam@result:'try'(_pipe, Handler). -file("src/yodel.gleam", 557). -spec resolve( binary(), yodel@options:options(), fun((binary()) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}) ) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}. resolve(Input, Options, Handler) -> _pipe@1 = case yodel@options:is_resolve_enabled(Options) of true -> yodel@resolver:resolve_placeholders(Input, Options); false -> _pipe = Input, {ok, _pipe} end, gleam@result:'try'(_pipe@1, Handler). -file("src/yodel.gleam", 569). -spec read( binary(), fun((binary()) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}) ) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}. read(Input, Handler) -> _pipe = case yodel@input:get_content(Input) of {ok, Content} -> {ok, Content}; {error, E} -> {error, E} end, gleam@result:'try'(_pipe, Handler). -file("src/yodel.gleam", 487). ?DOC( " Set the resolve mode to strict.\n" "\n" " Example:\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.with_strict_resolve()\n" " |> yodel.load_with_options(my_config)\n" " ```\n" ). -spec with_strict_resolve(yodel@options:options()) -> yodel@options:options(). with_strict_resolve(Options) -> with_resolve_mode(Options, strict). -file("src/yodel.gleam", 500). ?DOC( " Set the resolve mode to lenient.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.with_lenient_resolve()\n" " |> yodel.load_with_options(my_config)\n" ). -spec with_lenient_resolve(yodel@options:options()) -> yodel@options:options(). with_lenient_resolve(Options) -> with_resolve_mode(Options, lenient). -file("src/yodel.gleam", 412). ?DOC( " Attempt to automatically detect the format of the configuration file.\n" "\n" " If the input is a file, we first try to detect the format from the file extension.\n" " If that fails, we try to detect the format from the content of the file.\n" "\n" " If the input is a string, we try to detect the format from the content.\n" "\n" " If Auto Detection fails, an error will be returned because we can't safely proceed.\n" " If this happens, try specifying the format using `as_json`, `as_toml`, `as_yaml`, or `with_format`.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.auto_detect_format()\n" " |> yodel.load_with_options(my_config)\n" " ```\n" ). -spec auto_detect_format(yodel@options:options()) -> yodel@options:options(). auto_detect_format(Options) -> with_format(Options, auto). -file("src/yodel.gleam", 362). ?DOC( " Set the format of the configuration file to JSON.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.as_json()\n" " |> yodel.load_with_options(my_config)\n" " ```\n" ). -spec as_json(yodel@options:options()) -> yodel@options:options(). as_json(Options) -> with_format(Options, json). -file("src/yodel.gleam", 376). ?DOC( " Set the format of the configuration file to TOML.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.as_toml()\n" " |> yodel.load_with_options(my_config)\n" " ```\n" ). -spec as_toml(yodel@options:options()) -> yodel@options:options(). as_toml(Options) -> with_format(Options, toml). -file("src/yodel.gleam", 390). ?DOC( " Set the format of the configuration file to YAML.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx =\n" " yodel.default_options()\n" " |> yodel.as_yaml()\n" " |> yodel.load_with_options(my_config)\n" " ```\n" ). -spec as_yaml(yodel@options:options()) -> yodel@options:options(). as_yaml(Options) -> with_format(Options, yaml). -file("src/yodel.gleam", 580). -spec select( binary(), binary(), yodel@options:options(), fun((yodel@options:format()) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}) ) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}. select(Input, Content, Options, Handler) -> Formats = [{format_detector, <<"toml"/utf8>>, fun yodel@parsers@toml:detect/1}, {format_detector, <<"json/yaml"/utf8>>, fun yodel@parsers@yaml:detect/1}], _pipe = case yodel@format:get_format(Input, Content, Options, Formats) of json -> json; toml -> toml; yaml -> yaml; auto -> auto end, _pipe@1 = {ok, _pipe}, gleam@result:'try'(_pipe@1, Handler). -file("src/yodel.gleam", 131). ?DOC( " Load a configuration file with options.\n" "\n" " This function will use the provided options to read and parse the config content,\n" " returning a `Context` if successful.\n" ). -spec load_with_options(yodel@options:options(), binary()) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}. load_with_options(Options, Input) -> read( Input, fun(Content) -> select( Input, Content, Options, fun(Format) -> resolve( Content, Options, fun(Resolved) -> parse( Resolved, Format, fun(Parsed) -> validate( Parsed, fun(Validated) -> {ok, yodel@context:new(Validated)} end ) end ) end ) end ) end ). -file("src/yodel.gleam", 123). ?DOC( " Load a configuration file.\n" "\n" " This function will read the config content, detect the format,\n" " resolve the placeholders, parse the config content, returning a `Context` if successful.\n" "\n" " `input` can be a file path or a string containing the configuration content.\n" "\n" " Example:\n" "\n" " ```gleam\n" " let ctx = yodel.load(\"config.toml\")\n" "\n" " let content = \"foo: bar\" // yaml content\n" " let ctx = yodel.load(content)\n" " ```\n" ). -spec load(binary()) -> {ok, yodel@context:context()} | {error, yodel@errors:config_error()}. load(Input) -> load_with_options(default_options(), Input).