-module(sketch@css). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([compute_modules/1, generate_stylesheets/3, main/0]). -export_type([module_/0, css/0]). -type module_() :: {module, binary(), binary(), gleam@option:option(glance:module_())}. -type css() :: {css, list({binary(), binary()}), list(binary())}. -spec recursive_modules_read(binary()) -> {ok, list(module_())} | {error, simplifile:file_error()}. recursive_modules_read(Dir) -> gleam@result:map( simplifile_erl:read_directory(Dir), fun(Dir_content) -> gleam@list:flatten( (gleam@list:filter_map( Dir_content, fun(Path) -> Path@1 = gleam@string:join([Dir, Path], <<"/"/utf8>>), gleam@result:'try'( simplifile_erl:is_directory(Path@1), fun(Is_dir) -> gleam@bool:guard( Is_dir, recursive_modules_read(Path@1), fun() -> gleam@result:map( simplifile:read(Path@1), fun(Content) -> [{module, Path@1, Content, none}] end ) end ) end ) end )) ) end ). -spec parse_modules(list(module_())) -> list(module_()). parse_modules(Modules) -> gleam@list:filter_map( Modules, fun(Module) -> gleam@result:map( begin _pipe = glance:module(erlang:element(3, Module)), sketch@css@error:glance(_pipe) end, fun(Ast) -> erlang:setelement(4, Module, {some, Ast}) end ) end ). -spec select_css_files(list(module_()), binary()) -> list(module_()). select_css_files(Modules, Interface) -> gleam@list:filter( Modules, fun(Module) -> not gleam_stdlib:contains_string( erlang:element(2, Module), Interface ) andalso ((gleam@string:ends_with( erlang:element(2, Module), <<"_styles.gleam"/utf8>> ) orelse gleam@string:ends_with( erlang:element(2, Module), <<"_css.gleam"/utf8>> )) orelse gleam@string:ends_with( erlang:element(2, Module), <<"_sketch.gleam"/utf8>> )) end ). -spec find_sketch_imports(list(glance:definition(glance:import()))) -> {list(binary()), list(binary()), list({binary(), binary()})}. find_sketch_imports(Imports) -> Imports@1 = gleam@list:filter( Imports, fun(I) -> erlang:element(2, erlang:element(3, I)) =:= <<"sketch"/utf8>> end ), Aliases = begin _pipe = gleam@list:map( Imports@1, fun(I@1) -> case erlang:element(3, erlang:element(3, I@1)) of none -> erlang:element(2, erlang:element(3, I@1)); {some, {discarded, _}} -> erlang:element(2, erlang:element(3, I@1)); {some, {named, S}} -> S end end ), gleam@list:unique(_pipe) end, Exposed = (gleam@list:flat_map( Imports@1, fun(I@2) -> gleam@list:filter_map( erlang:element(5, erlang:element(3, I@2)), fun(Value) -> gleam@bool:guard( erlang:element(2, Value) /= <<"class"/utf8>>, {error, nil}, fun() -> _pipe@1 = gleam@option:map( erlang:element(3, Value), fun(Field@0) -> {ok, Field@0} end ), gleam@option:unwrap( _pipe@1, {ok, erlang:element(2, Value)} ) end ) end ) end )), Properties = (gleam@list:flat_map( Imports@1, fun(I@3) -> gleam@list:filter_map( erlang:element(5, erlang:element(3, I@3)), fun(Value@1) -> gleam@bool:guard( erlang:element(2, Value@1) =:= <<"class"/utf8>>, {error, nil}, fun() -> {ok, {begin _pipe@2 = erlang:element(3, Value@1), gleam@option:unwrap( _pipe@2, erlang:element(2, Value@1) ) end, erlang:element(2, Value@1)}} end ) end ) end )), {Aliases, Exposed, Properties}. -spec keep_fn_call(glance:function_()) -> {ok, {glance:expression(), list(glance:expression())}} | {error, nil}. keep_fn_call(Function) -> case erlang:element(6, Function) of [{expression, {call, Call, [{field, _, {list, Body, _}}]}}] -> {ok, {Call, Body}}; _ -> {error, nil} end. -spec keep_valid_class(list(binary()), list(binary()), glance:expression()) -> {ok, nil} | {error, nil}. keep_valid_class(Imports, Exposed, Call) -> case Call of {variable, Fcall} -> Is_class = not gleam@list:contains(Exposed, Fcall), gleam@bool:guard(Is_class, {error, nil}, fun() -> {ok, nil} end); {field_access, {variable, Module}, Fcall@1} -> Not_class = Fcall@1 /= <<"class"/utf8>>, Not_sketch_class = not gleam@list:contains(Imports, Module) orelse Not_class, gleam@bool:guard( Not_sketch_class, {error, nil}, fun() -> {ok, nil} end ); _ -> {error, nil} end. -spec add_expressions({list({NSJ, list(NYG)}), NYB}, NSJ, list(NYG)) -> {list({NSJ, list(NYG)}), NYB}. add_expressions(Results, Name, Expressions) -> {Results@1, Medias} = Results, {begin _pipe = gleam@list:key_find(Results@1, Name), _pipe@1 = gleam@result:unwrap(_pipe, []), _pipe@2 = lists:append(_pipe@1, Expressions), gleam@list:key_set(Results@1, Name, _pipe@2) end, Medias}. -spec string_to_string(glance:expression()) -> binary(). string_to_string(Param) -> case Param of {string, M} -> M; {variable, V} -> <<<<"var(--"/utf8, (gleam@string:replace(V, <<"_"/utf8>>, <<"-"/utf8>>))/binary>>/binary, ")"/utf8>>; _ -> <<""/utf8>> end. -spec property_to_string(list(glance:field(glance:expression()))) -> {ok, binary()} | {error, nil}. property_to_string(Param) -> case Param of [{field, none, {string, Param@1}}, {field, none, P}] -> {ok, <<<<<<<<" "/utf8, Param@1/binary>>/binary, ": "/utf8>>/binary, (string_to_string(P))/binary>>/binary, ";"/utf8>>}; _ -> {error, nil} end. -spec template_areas_to_string(list(glance:field(glance:expression()))) -> {ok, binary()} | {error, nil}. template_areas_to_string(Param) -> case Param of [{field, none, {list, Content, _}}] -> {ok, <<<<" grid-template-areas:\n"/utf8, (begin _pipe = gleam@list:map( Content, fun string_to_string/1 ), _pipe@1 = gleam@list:map( _pipe, fun(A) -> <<<<" \""/utf8, A/binary>>/binary, "\""/utf8>> end ), gleam@string:join(_pipe@1, <<"\n"/utf8>>) end)/binary>>/binary, ";"/utf8>>}; _ -> {error, nil} end. -spec remove_file(binary()) -> binary(). remove_file(Dst_path) -> _pipe = Dst_path, _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>), _pipe@2 = lists:reverse(_pipe@1), _pipe@3 = gleam@list:drop(_pipe@2, 1), _pipe@4 = lists:reverse(_pipe@3), gleam@string:join(_pipe@4, <<"/"/utf8>>). -spec rewrite_pipe(glance:expression()) -> glance:expression(). rewrite_pipe(Expression) -> case Expression of {binary_operator, pipe, Left, Right} -> case Right of {call, F, Args} -> {call, F, gleam@list:prepend(Args, {field, none, Left})}; {variable, V} -> {call, {variable, V}, [{field, none, Left}]}; _ -> Expression end; _ -> Expression end. -spec select_composes(list({binary(), binary()}), list(glance:expression())) -> list(binary()). select_composes(Properties, Expressions) -> gleam@list:fold( Expressions, [], fun(Classes, Expression) -> case rewrite_pipe(Expression) of {call, {variable, V}, [{field, none, {call, {variable, T}, _}}]} -> F = begin _pipe = gleam@list:key_find(Properties, V), gleam@result:unwrap(_pipe, <<""/utf8>>) end, gleam@bool:guard( F /= <<"compose"/utf8>>, Classes, fun() -> _pipe@1 = gleam@string:replace( T, <<"_"/utf8>>, <<"-"/utf8>> ), gleam@list:prepend(Classes, _pipe@1) end ); {call, {variable, V}, [{field, none, {call, {field_access, _, T}, _}}]} -> F = begin _pipe = gleam@list:key_find(Properties, V), gleam@result:unwrap(_pipe, <<""/utf8>>) end, gleam@bool:guard( F /= <<"compose"/utf8>>, Classes, fun() -> _pipe@1 = gleam@string:replace( T, <<"_"/utf8>>, <<"-"/utf8>> ), gleam@list:prepend(Classes, _pipe@1) end ); {call, {field_access, _, F@1}, [{field, none, {call, {variable, T@1}, _}}]} -> gleam@bool:guard( F@1 /= <<"compose"/utf8>>, Classes, fun() -> _pipe@2 = gleam@string:replace( T@1, <<"_"/utf8>>, <<"-"/utf8>> ), gleam@list:prepend(Classes, _pipe@2) end ); {call, {field_access, _, F@1}, [{field, none, {call, {field_access, _, T@1}, _}}]} -> gleam@bool:guard( F@1 /= <<"compose"/utf8>>, Classes, fun() -> _pipe@2 = gleam@string:replace( T@1, <<"_"/utf8>>, <<"-"/utf8>> ), gleam@list:prepend(Classes, _pipe@2) end ); _ -> Classes end end ). -spec size_to_string(glance:expression()) -> binary(). size_to_string(Value) -> case rewrite_pipe(Value) of {int, I} -> I; {float, F} -> F; _ -> <<""/utf8>> end. -spec property_body(list(glance:field(glance:expression()))) -> binary(). property_body(Param) -> case Param of [{field, none, P}] -> case rewrite_pipe(P) of {call, {field_access, {variable, _}, Size}, [{field, none, Value}]} -> <<(size_to_string(Value))/binary, Size/binary>>; {call, {variable, Size@1}, [{field, none, Value@1}]} -> <<(size_to_string(Value@1))/binary, Size@1/binary>>; P@1 -> string_to_string(P@1) end; _ -> gleam@string:inspect(Param) end. -spec media_to_string(list({binary(), binary()}), glance:expression()) -> {ok, binary()} | {error, nil}. media_to_string(Properties, Media) -> case rewrite_pipe(Media) of {call, {variable, Media@1}, P} -> gleam@result:'try'( gleam@list:key_find(Properties, Media@1), fun(Name) -> gleam@bool:guard( Name /= <<"max_width"/utf8>>, {error, nil}, fun() -> {ok, <<"(max-width: "/utf8, (property_body(P))/binary>>} end ) end ); {call, {field_access, {variable, _}, Media@2}, P@1} -> case Media@2 of <<"max_width"/utf8>> -> {ok, <<<<<<<<"("/utf8, (gleam@string:replace( Media@2, <<"_"/utf8>>, <<"-"/utf8>> ))/binary>>/binary, ": "/utf8>>/binary, (property_body(P@1))/binary>>/binary, ")"/utf8>>}; <<"min_width"/utf8>> -> {ok, <<<<<<<<"("/utf8, (gleam@string:replace( Media@2, <<"_"/utf8>>, <<"-"/utf8>> ))/binary>>/binary, ": "/utf8>>/binary, (property_body(P@1))/binary>>/binary, ")"/utf8>>}; <<"max_height"/utf8>> -> {ok, <<<<<<<<"("/utf8, (gleam@string:replace( Media@2, <<"_"/utf8>>, <<"-"/utf8>> ))/binary>>/binary, ": "/utf8>>/binary, (property_body(P@1))/binary>>/binary, ")"/utf8>>}; <<"min_height"/utf8>> -> {ok, <<<<<<<<"("/utf8, (gleam@string:replace( Media@2, <<"_"/utf8>>, <<"-"/utf8>> ))/binary>>/binary, ": "/utf8>>/binary, (property_body(P@1))/binary>>/binary, ")"/utf8>>}; <<"not"/utf8>> -> case P@1 of [{field, none, M}] -> gleam@result:'try'( media_to_string(Properties, M), fun(Left) -> {ok, <<<<"not("/utf8, Left/binary>>/binary, ")"/utf8>>} end ); _ -> {error, nil} end; <<"and"/utf8>> -> case P@1 of [{field, none, M@1}, {field, none, N}] -> gleam@result:'try'( media_to_string(Properties, M@1), fun(Left@1) -> gleam@result:'try'( media_to_string(Properties, N), fun(Right) -> {ok, <<<<<<<>/binary, Media@2/binary>>/binary, " "/utf8>>/binary, Right/binary>>} end ) end ); _ -> {error, nil} end; <<"or"/utf8>> -> case P@1 of [{field, none, M@1}, {field, none, N}] -> gleam@result:'try'( media_to_string(Properties, M@1), fun(Left@1) -> gleam@result:'try'( media_to_string(Properties, N), fun(Right) -> {ok, <<<<<<<>/binary, Media@2/binary>>/binary, " "/utf8>>/binary, Right/binary>>} end ) end ); _ -> {error, nil} end; <<"landscape"/utf8>> -> {ok, <<<<"(orientation: "/utf8, Media@2/binary>>/binary, ")"/utf8>>}; <<"portait"/utf8>> -> {ok, <<<<"(orientation: "/utf8, Media@2/binary>>/binary, ")"/utf8>>}; <<"dark_theme"/utf8>> -> {ok, <<<<"(prefers-color-scheme: "/utf8, (gleam@string:replace( Media@2, <<"_"/utf8>>, <<"-"/utf8>> ))/binary>>/binary, ")"/utf8>>}; <<"light_theme"/utf8>> -> {ok, <<<<"(prefers-color-scheme: "/utf8, (gleam@string:replace( Media@2, <<"_"/utf8>>, <<"-"/utf8>> ))/binary>>/binary, ")"/utf8>>}; _ -> {error, nil} end; _ -> {error, nil} end. -spec css_property(binary(), list(glance:field(glance:expression()))) -> {ok, binary()} | {error, any()}. css_property(Name, Param) -> Prop = begin _pipe = gleam@string:split(Name, <<"-"/utf8>>), _pipe@1 = gleam@list:filter(_pipe, fun(A) -> A /= <<""/utf8>> end), gleam@string:join(_pipe@1, <<"-"/utf8>>) end, Body = property_body(Param), {ok, <<<<<<<<" "/utf8, Prop/binary>>/binary, ": "/utf8>>/binary, Body/binary>>/binary, ";"/utf8>>}. -spec dst_flag() -> glint:flag(binary()). dst_flag() -> _pipe = glint:string_flag(<<"dest"/utf8>>), _pipe@1 = glint:flag_default(_pipe, <<"styles"/utf8>>), glint:flag_help( _pipe@1, <<"Define the directory in which styles should be output."/utf8>> ). -spec src_flag() -> glint:flag(binary()). src_flag() -> _pipe = glint:string_flag(<<"src"/utf8>>), _pipe@1 = glint:flag_default(_pipe, <<"src"/utf8>>), glint:flag_help( _pipe@1, <<"Define the directory in which styles should be read. Default to src."/utf8>> ). -spec interface_flag() -> glint:flag(binary()). interface_flag() -> _pipe = glint:string_flag(<<"interface"/utf8>>), _pipe@1 = glint:flag_default(_pipe, <<"src/sketch/styles"/utf8>>), glint:flag_help( _pipe@1, <<"Define the directory in which interfaces should be output. Default to src/sketch/styles."/utf8>> ). -spec skip_not_css_properties(binary()) -> {ok, binary()} | {error, nil}. skip_not_css_properties(Name) -> case gleam@list:contains( [<<"compose"/utf8>>, <<"none"/utf8>>, <<"media"/utf8>>], Name ) orelse gleam@result:is_ok( gleam@list:key_find( [{<<"placeholder"/utf8>>, <<"::placeholder"/utf8>>}, {<<"hover"/utf8>>, <<":hover"/utf8>>}, {<<"active"/utf8>>, <<":active"/utf8>>}, {<<"focus"/utf8>>, <<":focus"/utf8>>}, {<<"focus_visible"/utf8>>, <<":focus_visible"/utf8>>}, {<<"focus_within"/utf8>>, <<":focus-within"/utf8>>}, {<<"enabled"/utf8>>, <<":enabled"/utf8>>}, {<<"disabled"/utf8>>, <<":disabled"/utf8>>}, {<<"read_only"/utf8>>, <<":read-only"/utf8>>}, {<<"read_write"/utf8>>, <<":read-write"/utf8>>}, {<<"checked"/utf8>>, <<":checked"/utf8>>}, {<<"blank"/utf8>>, <<":blank"/utf8>>}, {<<"valid"/utf8>>, <<":valid"/utf8>>}, {<<"invalid"/utf8>>, <<":invalid"/utf8>>}, {<<"required"/utf8>>, <<":required"/utf8>>}, {<<"optional"/utf8>>, <<":optional"/utf8>>}, {<<"link"/utf8>>, <<":link"/utf8>>}, {<<"visited"/utf8>>, <<":visited"/utf8>>}, {<<"target"/utf8>>, <<":target"/utf8>>}, {<<"nth_child"/utf8>>, <<":nth-child"/utf8>>}, {<<"nth_last_child"/utf8>>, <<":nth-last-child"/utf8>>}, {<<"nth_of_type"/utf8>>, <<":nth-of-type"/utf8>>}, {<<"nth_last_of_type"/utf8>>, <<":nth-last-of-type"/utf8>>}, {<<"first_child"/utf8>>, <<":first-child"/utf8>>}, {<<"last_child"/utf8>>, <<":last-child"/utf8>>}, {<<"only_child"/utf8>>, <<":only-child"/utf8>>}, {<<"first_of_type"/utf8>>, <<":first-of-type"/utf8>>}, {<<"last_of_type"/utf8>>, <<":last-of-type"/utf8>>}, {<<"only_of_type"/utf8>>, <<":only-of-type"/utf8>>}, {<<"pseudo_selector"/utf8>>, <<":pseudo-selector"/utf8>>}], Name ) ) of true -> {error, nil}; false -> {ok, Name} end. -spec compute_css_property(binary(), list(glance:field(glance:expression()))) -> {ok, binary()} | {error, nil}. compute_css_property(Name, Param) -> gleam@result:'try'( skip_not_css_properties(Name), fun(Name@1) -> Is_property = Name@1 =:= <<"property"/utf8>>, Is_areas = Name@1 =:= <<"grid_template_areas"/utf8>>, gleam@bool:guard( Is_property, property_to_string(Param), fun() -> gleam@bool:guard( Is_areas, template_areas_to_string(Param), fun() -> css_property(Name@1, Param) end ) end ) end ). -spec class_body( list(binary()), list({binary(), binary()}), list(glance:expression()) ) -> list(binary()). class_body(Imports, Properties, Body) -> gleam@list:filter_map(Body, fun(Property) -> case Property of {call, {variable, Name}, Param} -> gleam@result:'try'( gleam@list:key_find(Properties, Name), fun(Name@1) -> compute_css_property(Name@1, Param) end ); {call, {field_access, {variable, Module_name}, Name@2}, Param@1} -> Is_sketch = gleam@list:contains(Imports, Module_name), gleam@bool:guard( not Is_sketch, {error, nil}, fun() -> compute_css_property(Name@2, Param@1) end ); _ -> {error, nil} end end). -spec compile_classes( list(binary()), list({binary(), binary()}), binary(), list({binary(), list(glance:expression())}), binary() ) -> binary(). compile_classes(Imports, Properties, Name, All_classes, Prefix) -> _pipe@2 = gleam@list:map( All_classes, fun(Class) -> {Selector, Body} = Class, Body@1 = begin _pipe = class_body(Imports, Properties, Body), _pipe@1 = gleam@list:map( _pipe, fun(S) -> <> end ), gleam@string:join(_pipe@1, <<"\n"/utf8>>) end, Head = <<<<<<<>/binary, Name/binary>>/binary, Selector/binary>>/binary, " {\n"/utf8>>, <<<<<<<>/binary, "\n"/utf8>>/binary, Prefix/binary>>/binary, "}"/utf8>> end ), gleam@string:join(_pipe@2, <<"\n\n"/utf8>>). -spec add_media( list(binary()), list({binary(), binary()}), {list({binary(), list(glance:expression())}), list({binary(), list({binary(), list(glance:expression())})})}, list(glance:field(glance:expression())) ) -> fun(() -> {ok, {list({binary(), list(glance:expression())}), list({binary(), list({binary(), list(glance:expression())})})}} | {error, nil}). add_media(Imports, Properties, Results, Param) -> fun() -> {Results@1, Medias} = Results, case Param of [{field, none, Media}, {field, none, {list, Content, _}}] -> gleam@result:'try'( media_to_string(Properties, Media), fun(Media@1) -> {Content@1, _} = split_body( Imports, Properties, Content ), {ok, {Results@1, gleam@list:key_set(Medias, Media@1, Content@1)}} end ); _ -> {ok, {Results@1, Medias}} end end. -spec split_body( list(binary()), list({binary(), binary()}), list(glance:expression()) ) -> {list({binary(), list(glance:expression())}), list({binary(), list({binary(), list(glance:expression())})})}. split_body(Imports, Properties, Body) -> gleam@list:fold(Body, {[], []}, fun(Results, Item) -> _pipe = case Item of {call, {variable, Name}, Param} -> push_in_subclass( Imports, Results, Item, Name, Param, Properties, true ); {call, {field_access, {variable, Module_name}, Name@1}, Param@1} -> Is_sketch = gleam@list:contains(Imports, Module_name), gleam@bool:guard( not Is_sketch, {error, nil}, fun() -> push_in_subclass( Imports, Results, Item, Name@1, Param@1, Properties, false ) end ); _ -> {error, nil} end, gleam@result:unwrap(_pipe, Results) end). -spec push_in_subclass( list(binary()), {list({binary(), list(glance:expression())}), list({binary(), list({binary(), list(glance:expression())})})}, glance:expression(), binary(), list(glance:field(glance:expression())), list({binary(), binary()}), boolean() ) -> {ok, {list({binary(), list(glance:expression())}), list({binary(), list({binary(), list(glance:expression())})})}} | {error, nil}. push_in_subclass(Imports, Results, Item, Name, Param, Properties, Unqualified) -> Is_media = Name =:= <<"media"/utf8>>, Add_media = add_media(Imports, Properties, Results, Param), gleam@bool:lazy_guard( Is_media, Add_media, fun() -> gleam@result:'try'(case Unqualified of true -> gleam@list:key_find(Properties, Name); false -> {ok, Name} end, fun(Name@1) -> Is_pseudo = gleam@list:key_find( [{<<"placeholder"/utf8>>, <<"::placeholder"/utf8>>}, {<<"hover"/utf8>>, <<":hover"/utf8>>}, {<<"active"/utf8>>, <<":active"/utf8>>}, {<<"focus"/utf8>>, <<":focus"/utf8>>}, {<<"focus_visible"/utf8>>, <<":focus_visible"/utf8>>}, {<<"focus_within"/utf8>>, <<":focus-within"/utf8>>}, {<<"enabled"/utf8>>, <<":enabled"/utf8>>}, {<<"disabled"/utf8>>, <<":disabled"/utf8>>}, {<<"read_only"/utf8>>, <<":read-only"/utf8>>}, {<<"read_write"/utf8>>, <<":read-write"/utf8>>}, {<<"checked"/utf8>>, <<":checked"/utf8>>}, {<<"blank"/utf8>>, <<":blank"/utf8>>}, {<<"valid"/utf8>>, <<":valid"/utf8>>}, {<<"invalid"/utf8>>, <<":invalid"/utf8>>}, {<<"required"/utf8>>, <<":required"/utf8>>}, {<<"optional"/utf8>>, <<":optional"/utf8>>}, {<<"link"/utf8>>, <<":link"/utf8>>}, {<<"visited"/utf8>>, <<":visited"/utf8>>}, {<<"target"/utf8>>, <<":target"/utf8>>}, {<<"nth_child"/utf8>>, <<":nth-child"/utf8>>}, {<<"nth_last_child"/utf8>>, <<":nth-last-child"/utf8>>}, {<<"nth_of_type"/utf8>>, <<":nth-of-type"/utf8>>}, {<<"nth_last_of_type"/utf8>>, <<":nth-last-of-type"/utf8>>}, {<<"first_child"/utf8>>, <<":first-child"/utf8>>}, {<<"last_child"/utf8>>, <<":last-child"/utf8>>}, {<<"only_child"/utf8>>, <<":only-child"/utf8>>}, {<<"first_of_type"/utf8>>, <<":first-of-type"/utf8>>}, {<<"last_of_type"/utf8>>, <<":last-of-type"/utf8>>}, {<<"only_of_type"/utf8>>, <<":only-of-type"/utf8>>}, {<<"pseudo_selector"/utf8>>, <<":pseudo-selector"/utf8>>}], Name@1 ), Add_body = fun() -> {ok, add_expressions(Results, <<""/utf8>>, [Item])} end, gleam@bool:lazy_guard( gleam@result:is_error(Is_pseudo), Add_body, fun() -> {ok, Name@2} = case Is_pseudo of {ok, _} -> Is_pseudo; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"sketch/css"/utf8>>, function => <<"push_in_subclass"/utf8>>, line => 250}) end, case {Name@2, Param} of {<<":pseudo-selector"/utf8>>, [{field, none, {string, Selector}}, {field, none, {list, Expressions, _}}]} -> {ok, add_expressions( Results, Selector, Expressions )}; {<<":nth-child"/utf8>>, [{field, none, {string, Selector@1}}, {field, none, {list, Expressions@1, _}}]} -> {ok, add_expressions( Results, <<<<<>/binary, Selector@1/binary>>/binary, ")"/utf8>>, Expressions@1 )}; {<<":nth-last-child"/utf8>>, [{field, none, {string, Selector@2}}, {field, none, {list, Expressions@2, _}}]} -> {ok, add_expressions( Results, <<<<<>/binary, Selector@2/binary>>/binary, ")"/utf8>>, Expressions@2 )}; {<<":nth-of-type"/utf8>>, [{field, none, {string, Selector@3}}, {field, none, {list, Expressions@3, _}}]} -> {ok, add_expressions( Results, <<<<<>/binary, Selector@3/binary>>/binary, ")"/utf8>>, Expressions@3 )}; {<<":nth-last-of-type"/utf8>>, [{field, none, {string, Selector@4}}, {field, none, {list, Expressions@4, _}}]} -> {ok, add_expressions( Results, <<<<<>/binary, Selector@4/binary>>/binary, ")"/utf8>>, Expressions@4 )}; {_, [{field, none, {list, Expressions@5, _}}]} -> {ok, add_expressions( Results, Name@2, Expressions@5 )}; {_, _} -> {error, nil} end end ) end) end ). -spec function_definition( list(binary()), list(binary()), list({binary(), binary()}), glance:function_() ) -> {ok, {binary(), binary(), binary()}} | {error, nil}. function_definition(Imports, Exposed, Properties, Function) -> gleam@result:'try'( keep_fn_call(Function), fun(_use0) -> {Call, Body} = _use0, gleam@result:'try'( keep_valid_class(Imports, Exposed, Call), fun(_) -> Name = gleam@string:replace( erlang:element(2, Function), <<"_"/utf8>>, <<"-"/utf8>> ), {All_classes, Medias} = split_body( Imports, Properties, Body ), Body@1 = compile_classes( Imports, Properties, Name, All_classes, <<""/utf8>> ), Composes = begin _pipe = All_classes, _pipe@1 = gleam@list:flat_map( _pipe, fun gleam@pair:second/1 ), select_composes(Properties, _pipe@1) end, Body@3 = begin Medias@1 = begin _pipe@2 = gleam@list:map( Medias, fun(Media) -> {Media@1, All_classes@1} = Media, Body@2 = compile_classes( Imports, Properties, Name, All_classes@1, <<" "/utf8>> ), Head = <<<<"@media "/utf8, Media@1/binary>>/binary, " {\n"/utf8>>, <<<>/binary, "\n}"/utf8>> end ), gleam@string:join(_pipe@2, <<"\n\n"/utf8>>) end, gleam@bool:guard( Medias@1 =:= <<""/utf8>>, Body@1, fun() -> <<<>/binary, Medias@1/binary>> end ) end, Name@1 = begin _pipe@3 = [Name | Composes], _pipe@4 = lists:reverse(_pipe@3), gleam@string:join(_pipe@4, <<" "/utf8>>) end, {ok, {erlang:element(2, Function), Name@1, Body@3}} end ) end ). -spec parse_css_modules(list(module_()), list(module_())) -> list({module_(), css()}). parse_css_modules(Styles_modules, _) -> gleam@list:filter_map( Styles_modules, fun(Styles_module) -> case erlang:element(4, Styles_module) of none -> {error, nil}; {some, Ast} -> {ok, ({Styles_module, begin {Imports, Exposed, Properties} = find_sketch_imports( erlang:element(2, Ast) ), gleam@list:fold( erlang:element(6, Ast), {css, [], []}, fun(Css, Function) -> _pipe = function_definition( Imports, Exposed, Properties, erlang:element(3, Function) ), _pipe@1 = gleam@result:map( _pipe, fun(Content) -> Classes = gleam@list:key_set( erlang:element(2, Css), erlang:element(1, Content), erlang:element(2, Content) ), Content@1 = gleam@list:prepend( erlang:element(3, Css), erlang:element(3, Content) ), {css, Classes, Content@1} end ), gleam@result:unwrap(_pipe@1, Css) end ) end})} end end ). -spec compute_modules(list(module_())) -> list({module_(), css()}). compute_modules(Modules) -> Styles_modules = parse_modules(Modules), parse_css_modules(Styles_modules, Modules). -spec compute_styles_modules(list(module_()), binary()) -> list({module_(), css()}). compute_styles_modules(Modules, Interface) -> Modules@1 = parse_modules(Modules), Styles_modules = select_css_files(Modules@1, Interface), parse_css_modules(Styles_modules, Modules@1). -spec generate_stylesheets(binary(), binary(), binary()) -> {ok, nil} | {error, sketch@css@error:error()}. generate_stylesheets(Src, Dst, Src_interfaces) -> gleam@result:'try'( begin _pipe = simplifile_erl:is_directory(Src), sketch@css@error:simplifile(_pipe) end, fun(Is_dir) -> gleam@bool:guard( not Is_dir, sketch@css@error:not_a_directory(Src), fun() -> gleam@result:map( begin _pipe@1 = recursive_modules_read(Src), sketch@css@error:simplifile(_pipe@1) end, fun(Modules) -> Css_modules = compute_styles_modules( Modules, Src_interfaces ), _ = simplifile:create_directory_all(Dst), gleam@list:each( Css_modules, fun(_use0) -> {Module, Css_module} = _use0, Dst_path = gleam@string:replace( erlang:element(2, Module), Src, Dst ), Dst_path@1 = gleam@string:replace( Dst_path, <<".gleam"/utf8>>, <<".css"/utf8>> ), Parent_dst_path = remove_file(Dst_path@1), _ = simplifile:create_directory_all( Parent_dst_path ), _ = simplifile:write( Dst_path@1, gleam@string:join( erlang:element(3, Css_module), <<"\n\n"/utf8>> ) ), Src_styles_path = gleam@string:replace( erlang:element(2, Module), Src, Src_interfaces ), Parent_src_styles_path = remove_file( Src_styles_path ), _ = simplifile:create_directory_all( Parent_src_styles_path ), _ = simplifile:write( Src_styles_path, begin _pipe@2 = gleam@list:map( erlang:element(2, Css_module), fun(C) -> <<<<<<<<"pub const "/utf8, (erlang:element( 1, C ))/binary>>/binary, " = \""/utf8>>/binary, (erlang:element( 2, C ))/binary>>/binary, "\""/utf8>> end ), gleam@string:join( _pipe@2, <<"\n\n"/utf8>> ) end ) end ) end ) end ) end ). -spec css() -> glint:command(nil). css() -> glint:command_help( <<"Generate CSS for your gleam_styles.gleam files!"/utf8>>, fun() -> glint:flag( dst_flag(), fun(Dst) -> glint:flag( src_flag(), fun(Src) -> glint:flag( interface_flag(), fun(Interface) -> glint:command( fun(_, _, Flags) -> _assert_subject = Dst(Flags), {ok, Dst@1} = 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 => <<"sketch/css"/utf8>>, function => <<"css"/utf8>>, line => 584} ) end, _assert_subject@1 = Src(Flags), {ok, Src@1} = 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 => <<"sketch/css"/utf8>>, function => <<"css"/utf8>>, line => 585} ) end, _assert_subject@2 = Interface(Flags), {ok, Interface@1} = case _assert_subject@2 of {ok, _} -> _assert_subject@2; _assert_fail@2 -> erlang:error( #{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@2, module => <<"sketch/css"/utf8>>, function => <<"css"/utf8>>, line => 586} ) end, _assert_subject@3 = simplifile:current_directory( ), {ok, Cwd} = case _assert_subject@3 of {ok, _} -> _assert_subject@3; _assert_fail@3 -> erlang:error( #{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@3, module => <<"sketch/css"/utf8>>, function => <<"css"/utf8>>, line => 587} ) end, Src@2 = gleam@string:join( [Cwd, Src@1], <<"/"/utf8>> ), Dst@2 = gleam@string:join( [Cwd, Dst@1], <<"/"/utf8>> ), Interface@2 = gleam@string:join( [Cwd, Interface@1], <<"/"/utf8>> ), gleam@io:println( <<"Compiling Gleam styles files in "/utf8, Src@2/binary>> ), gleam@io:println( <<"Writing CSS files to "/utf8, Dst@2/binary>> ), gleam@io:println( <<"Writing interfaces files to "/utf8, Interface@2/binary>> ), _ = generate_stylesheets( Src@2, Dst@2, Interface@2 ), gleam@io:println(<<"Done!"/utf8>>), nil end ) end ) end ) end ) end ). -spec main() -> nil. main() -> _pipe = glint:new(), _pipe@1 = glint:with_name(_pipe, <<"Sketch CSS"/utf8>>), _pipe@2 = glint:pretty_help(_pipe@1, glint:default_pretty_help()), _pipe@3 = glint:add(_pipe@2, [<<"generate"/utf8>>], css()), glint:run(_pipe@3, erlang:element(4, argv:load())).