-module(squirrel). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([exit/1, main/0]). -spec walk(binary()) -> gleam@dict:dict(binary(), list(binary())). walk(From) -> case filepath:base_name(From) of <<"sql"/utf8>> -> _assert_subject = simplifile_erl:read_directory(From), {ok, Files} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"squirrel"/utf8>>, function => <<"walk"/utf8>>, line => 199}) end, Files@1 = (gleam@list:filter_map( Files, fun(File) -> gleam@result:'try'( filepath:extension(File), fun(Extension) -> gleam@bool:guard( Extension /= <<"sql"/utf8>>, {error, nil}, fun() -> File_name = filepath:join(From, File), case simplifile_erl:is_file(File_name) of {ok, true} -> {ok, File_name}; {ok, false} -> {error, nil}; {error, _} -> {error, nil} end end ) end ) end )), maps:from_list([{From, Files@1}]); _ -> _assert_subject@1 = simplifile_erl:read_directory(From), {ok, Files@2} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"squirrel"/utf8>>, function => <<"walk"/utf8>>, line => 214}) end, Directories = (gleam@list:filter_map( Files@2, fun(File@1) -> File_name@1 = filepath:join(From, File@1), case simplifile_erl:is_directory(File_name@1) of {ok, true} -> {ok, File_name@1}; {ok, false} -> {error, nil}; {error, _} -> {error, nil} end end )), _pipe = gleam@list:map(Directories, fun walk/1), gleam@list:fold(_pipe, gleam@dict:new(), fun gleam@dict:merge/2) end. -spec term_width() -> integer(). term_width() -> _pipe = term_size:columns(), gleam@result:unwrap(_pipe, 80). -spec pluralise(integer(), binary(), binary()) -> binary(). pluralise(Count, Singular, Plural) -> case Count of 1 -> Singular; _ -> Plural end. -spec flexible_string(binary()) -> glam@doc:document(). flexible_string(String) -> _pipe = gleam@string:split(String, <<"\n"/utf8>>), _pipe@1 = gleam@list:flat_map( _pipe, fun(_capture) -> gleam@string:split(_capture, <<" "/utf8>>) end ), _pipe@2 = gleam@list:map(_pipe@1, fun glam@doc:from_string/1), _pipe@3 = glam@doc:join(_pipe@2, {flex_break, <<" "/utf8>>, <<""/utf8>>}), glam@doc:group(_pipe@3). -spec text_with_header(binary(), binary()) -> glam@doc:document(). text_with_header(Header, Text) -> _pipe@1 = [glam@doc:from_string(Header), begin _pipe = flexible_string(Text), glam@doc:nest(_pipe, gleam@string:length(Header)) end], _pipe@2 = glam@doc:concat(_pipe@1), glam@doc:group(_pipe@2). -spec pretty_report( gleam@dict:dict(binary(), {integer(), list(squirrel@internal@error:error())}) ) -> {binary(), integer()}. pretty_report(Dirs) -> {Ok@1, Errors@1} = (gleam@dict:fold( Dirs, {0, []}, fun(_use0, _, Result) -> {All_ok, All_errors} = _use0, {Ok, Errors} = Result, {All_ok + Ok, begin _pipe = Errors, lists:append(_pipe, All_errors) end} end )), Errors_doc = begin _pipe@1 = gleam@list:map(Errors@1, fun squirrel@internal@error:to_doc/1), glam@doc:join(_pipe@1, glam@doc:lines(2)) end, Status_code = case Errors@1 of [_ | _] -> 1; [] -> 0 end, Report = case {Ok@1, Errors@1} of {0, [_ | _]} -> glam@doc:to_string(Errors_doc, term_width()); {0, []} -> _pipe@2 = text_with_header( <<"🐿️ "/utf8>>, <<"I couldn't find any `*.sql` file to generate queries from"/utf8>> ), _pipe@3 = glam@doc:to_string(_pipe@2, term_width()), gleam_community@ansi:yellow(_pipe@3); {N, []} -> _pipe@4 = text_with_header( <<"🐿️ "/utf8>>, <<<<<<"Generated "/utf8, (gleam@int:to_string(N))/binary>>/binary, " "/utf8>>/binary, (pluralise(N, <<"query"/utf8>>, <<"queries"/utf8>>))/binary>> ), _pipe@5 = glam@doc:to_string(_pipe@4, term_width()), gleam_community@ansi:green(_pipe@5); {N@1, [_ | _]} -> _pipe@6 = [Errors_doc, glam@doc:lines(2), text_with_header( <<"🥜 "/utf8>>, <<<<<<"I could still generate "/utf8, (gleam@int:to_string(N@1))/binary>>/binary, " "/utf8>>/binary, (pluralise(N@1, <<"query"/utf8>>, <<"queries"/utf8>>))/binary>> )], _pipe@7 = glam@doc:concat(_pipe@6), glam@doc:to_string(_pipe@7, term_width()) end, {Report, Status_code}. -spec exit(integer()) -> nil. exit(N) -> squirrel_ffi:exit(N). -spec write_queries(list(squirrel@internal@query:typed_query()), binary()) -> {ok, integer()} | {error, squirrel@internal@error:error()}. write_queries(Queries, File) -> gleam@bool:guard( Queries =:= [], {ok, 0}, fun() -> Directory = filepath:directory_name(File), _ = simplifile:create_directory_all(Directory), Code = squirrel@internal@query:generate_code( Queries, <<"v1.4.0"/utf8>> ), Try_write = begin _pipe = simplifile:write(File, Code), gleam@result:map_error( _pipe, fun(_capture) -> {cannot_write_to_file, File, _capture} end ) end, gleam@result:'try'( Try_write, fun(_) -> {ok, erlang:length(Queries)} end ) end ). -spec run( gleam@dict:dict(binary(), list(binary())), squirrel@internal@database@postgres:connection_options() ) -> gleam@dict:dict(binary(), {integer(), list(squirrel@internal@error:error())}). run(Directories, Connection) -> gleam@dict:map_values( Directories, fun(Directory, Files) -> {Queries, Errors} = begin _pipe = gleam@list:map( Files, fun squirrel@internal@query:from_file/1 ), gleam@result:partition(_pipe) end, {Queries@2, Errors@1} = case squirrel@internal@database@postgres:main( Queries, Connection ) of {error, Error} -> {[], [Error | Errors]}; {ok, {Queries@1, Type_errors}} -> {Queries@1, lists:append(Errors, Type_errors)} end, Output_file = begin _pipe@1 = filepath:directory_name(Directory), filepath:join(_pipe@1, <<"sql.gleam"/utf8>>) end, case write_queries(Queries@2, Output_file) of {ok, N} -> {N, Errors@1}; {error, Error@1} -> {erlang:length(Queries@2), [Error@1 | Errors@1]} end end ). -spec parse_database(binary()) -> {ok, binary()} | {error, nil}. parse_database(Path) -> case gleam@uri:path_segments(Path) of [] -> {ok, <<"database"/utf8>>}; [Database] -> {ok, Database}; [_, _ | _] -> {error, nil} end. -spec parse_userinfo(gleam@option:option(binary())) -> {binary(), binary()}. parse_userinfo(Userinfo) -> case Userinfo of none -> {<<"postgres"/utf8>>, <<""/utf8>>}; {some, Userinfo@1} -> case gleam@string:split(Userinfo@1, <<":"/utf8>>) of [User, Password] -> {User, Password}; [User@1] -> {User@1, <<""/utf8>>}; _ -> {<<"postgres"/utf8>>, <<""/utf8>>} end end. -spec connection_options_from_variables() -> squirrel@internal@database@postgres:connection_options(). connection_options_from_variables() -> Host = begin _pipe = envoy_ffi:get(<<"PGHOST"/utf8>>), gleam@result:unwrap(_pipe, <<"localhost"/utf8>>) end, User = begin _pipe@1 = envoy_ffi:get(<<"PGUSER"/utf8>>), gleam@result:unwrap(_pipe@1, <<"postgres"/utf8>>) end, Password = begin _pipe@2 = envoy_ffi:get(<<"PGPASSWORD"/utf8>>), gleam@result:unwrap(_pipe@2, <<""/utf8>>) end, Database = begin _pipe@3 = envoy_ffi:get(<<"PGDATABASE"/utf8>>), _pipe@4 = gleam@result:'or'(_pipe@3, squirrel@internal@project:name()), gleam@result:unwrap(_pipe@4, <<"database"/utf8>>) end, Port = begin _pipe@5 = envoy_ffi:get(<<"PGPORT"/utf8>>), _pipe@6 = gleam@result:then(_pipe@5, fun gleam@int:parse/1), gleam@result:unwrap(_pipe@6, 5432) end, {connection_options, Host, Port, User, Password, Database, 1000}. -spec parse_connection_url(binary()) -> {ok, squirrel@internal@database@postgres:connection_options()} | {error, nil}. parse_connection_url(Raw) -> gleam@result:'try'( gleam@uri:parse(Raw), fun(_use0) -> {uri, Scheme, Userinfo, Host, Port, Path, _, _} = _use0, Is_valid_scheme = (Scheme =:= none) orelse (Scheme =:= {some, <<"postgres"/utf8>>}), gleam@bool:guard( not Is_valid_scheme, {error, nil}, fun() -> Host@1 = begin _pipe = Host, gleam@option:unwrap(_pipe, <<"localhost"/utf8>>) end, Port@1 = begin _pipe@1 = Port, gleam@option:unwrap(_pipe@1, 5432) end, {User, Password} = parse_userinfo(Userinfo), gleam@result:'try'( parse_database(Path), fun(Database) -> {ok, {connection_options, Host@1, Port@1, User, Password, Database, 1000}} end ) end ) end ). -spec connection_options() -> {ok, squirrel@internal@database@postgres:connection_options()} | {error, squirrel@internal@error:error()}. connection_options() -> case envoy_ffi:get(<<"DATABASE_URL"/utf8>>) of {ok, Url} -> _pipe = parse_connection_url(Url), gleam@result:replace_error(_pipe, {invalid_connection_string, Url}); {error, _} -> {ok, connection_options_from_variables()} end. -spec main() -> nil. main() -> case connection_options() of {error, Error} -> _pipe = squirrel@internal@error:to_doc(Error), _pipe@1 = glam@doc:to_string(_pipe, term_width()), gleam@io:println(_pipe@1), squirrel_ffi:exit(1); {ok, Options} -> {Report, Status_code} = begin _pipe@2 = walk(squirrel@internal@project:src()), _pipe@3 = run(_pipe@2, Options), pretty_report(_pipe@3) end, gleam@io:println(Report), squirrel_ffi:exit(Status_code) end.