Skip to main content

src/aws@internal@ini.erl

-module(aws@internal@ini).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/ini.gleam").
-export([parse/1, get_property/3]).
-export_type([parse_error/0, line_kind/0]).

-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(
    " Minimal INI parser, sized for the AWS shared credentials file and the\n"
    " flat parts of `~/.aws/config`. Supports the subset:\n"
    "\n"
    "   - `[section-name]` headers\n"
    "   - `key = value` properties (whitespace around `=` trimmed)\n"
    "   - `#` and `;` whole-line comments\n"
    "   - blank lines\n"
    "\n"
    " Out of scope for now (we'll grow into these when SSO and assume-role\n"
    " providers need them):\n"
    "\n"
    "   - line continuation for multi-line values\n"
    "   - sub-properties (nested key/value inside a property)\n"
    "   - inline `#`/`;` comments mid-value\n"
    "\n"
    " Errors carry the offending line number so the surfaced message can\n"
    " point the user at the broken line of their credentials file.\n"
).

-type parse_error() :: {parse_error, integer(), binary()}.

-type line_kind() :: blank |
    comment |
    {section, binary()} |
    {property, binary(), binary()} |
    {malformed, binary()}.

-file("src/aws/internal/ini.gleam", 123).
-spec commit_section(
    binary(),
    gleam@dict:dict(binary(), binary()),
    gleam@dict:dict(binary(), gleam@dict:dict(binary(), binary()))
) -> gleam@dict:dict(binary(), gleam@dict:dict(binary(), binary())).
commit_section(Name, Props, Ini) ->
    case Name of
        <<""/utf8>> ->
            Ini;

        _ ->
            gleam@dict:insert(Ini, Name, Props)
    end.

-file("src/aws/internal/ini.gleam", 109).
-spec classify_property(binary()) -> line_kind().
classify_property(Line) ->
    case gleam@string:split_once(Line, <<"="/utf8>>) of
        {error, _} ->
            {malformed, <<"expected 'key = value'"/utf8>>};

        {ok, {Key, Value}} ->
            Key@1 = gleam@string:trim(Key),
            Value@1 = gleam@string:trim(Value),
            case Key@1 of
                <<""/utf8>> ->
                    {malformed, <<"property name is empty"/utf8>>};

                _ ->
                    {property, Key@1, Value@1}
            end
    end.

-file("src/aws/internal/ini.gleam", 97).
-spec classify_section(binary()) -> line_kind().
classify_section(Line) ->
    case gleam_stdlib:string_ends_with(Line, <<"]"/utf8>>) of
        false ->
            {malformed, <<"section header missing closing ']'"/utf8>>};

        true ->
            Inner = gleam@string:slice(Line, 1, string:length(Line) - 2),
            {section, gleam@string:trim(Inner)}
    end.

-file("src/aws/internal/ini.gleam", 85).
-spec classify(binary()) -> line_kind().
classify(Line) ->
    case Line of
        <<""/utf8>> ->
            blank;

        _ ->
            case gleam@string:slice(Line, 0, 1) of
                <<"#"/utf8>> ->
                    comment;

                <<";"/utf8>> ->
                    comment;

                <<"["/utf8>> ->
                    classify_section(Line);

                _ ->
                    classify_property(Line)
            end
    end.

-file("src/aws/internal/ini.gleam", 39).
-spec parse_lines(
    list({integer(), binary()}),
    binary(),
    gleam@dict:dict(binary(), binary()),
    gleam@dict:dict(binary(), gleam@dict:dict(binary(), binary()))
) -> {ok, gleam@dict:dict(binary(), gleam@dict:dict(binary(), binary()))} |
    {error, parse_error()}.
parse_lines(Lines, Current_section, Current_props, Ini) ->
    case Lines of
        [] ->
            {ok, commit_section(Current_section, Current_props, Ini)};

        [{Line_no, Raw} | Rest] ->
            Trimmed = gleam@string:trim(Raw),
            case classify(Trimmed) of
                blank ->
                    parse_lines(Rest, Current_section, Current_props, Ini);

                comment ->
                    parse_lines(Rest, Current_section, Current_props, Ini);

                {section, Name} ->
                    New_ini = commit_section(
                        Current_section,
                        Current_props,
                        Ini
                    ),
                    parse_lines(Rest, Name, maps:new(), New_ini);

                {property, Key, Value} ->
                    case Current_section of
                        <<""/utf8>> ->
                            {error,
                                {parse_error,
                                    Line_no,
                                    <<<<"property '"/utf8, Key/binary>>/binary,
                                        "' outside any section"/utf8>>}};

                        _ ->
                            parse_lines(
                                Rest,
                                Current_section,
                                gleam@dict:insert(Current_props, Key, Value),
                                Ini
                            )
                    end;

                {malformed, Message} ->
                    {error, {parse_error, Line_no, Message}}
            end
    end.

-file("src/aws/internal/ini.gleam", 32).
-spec parse(binary()) -> {ok,
        gleam@dict:dict(binary(), gleam@dict:dict(binary(), binary()))} |
    {error, parse_error()}.
parse(Text) ->
    _pipe = Text,
    _pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
    _pipe@2 = gleam@list:index_map(_pipe@1, fun(Line, I) -> {I + 1, Line} end),
    parse_lines(_pipe@2, <<""/utf8>>, maps:new(), maps:new()).

-file("src/aws/internal/ini.gleam", 131).
?DOC(" Look up a single property value inside a section.\n").
-spec get_property(
    gleam@dict:dict(binary(), gleam@dict:dict(binary(), binary())),
    binary(),
    binary()
) -> {ok, binary()} | {error, nil}.
get_property(Ini, Section, Key) ->
    gleam@result:'try'(
        gleam_stdlib:map_get(Ini, Section),
        fun(Props) -> gleam_stdlib:map_get(Props, Key) end
    ).