1:- module(css_write, [css//1,
    2                      write_css/2]).    3
    4:- use_module(library(apply), [foldl/4, partition/4]).    5:- use_module(library(lists), [append/3, last/2]).    6:- use_module(library(list_util), [take/3]).    7:- use_module(library(yall)).    8:- use_module(library(debug)).    9
   10ensure_list(X, X) :- is_list(X), !.
   11ensure_list(X, [X]).
   12
   13butlast(Ls, Lss) :-
   14    length(Ls, L),
   15    ButL is L - 1,
   16    take(ButL, Ls, Lss).
   17
   18% Copied from list_util:split/3, but addressing
   19% https://github.com/mndrix/list_util/issues/30
   20% which makes joining strings that already have spaces in them fail
   21split([], _, [[]]) :-
   22    !.  % optimization
   23split([Div|T], Div, [[]|Rest]) :-
   24    split(T, Div, Rest),  % implies: dif(Rest, [])
   25    !.  % optimization
   26split([H|T], Div, [[H|First]|Rest]) :-
   28    split(T, Div, [First|Rest])
   28.
   29
 css(+Content)// is det
Generate CSS string from Content.
   33css(Content) -->
   34    css_children(Content), { ! }.
   35
   36css_children([]) --> [], { ! }.
   37css_children([Thing|Things]) -->
   38    css_child(Thing), css_children(Things).
   39
   40css_child(\(Reference)) -->
   41   call(Reference).
   42css_child('@import'(Arg)) --> !, ['@import'(Arg)].
   43css_child('@media'(Query, Children)) -->
   44    !,
   45    [begin_media(Query)],
   46    { ensure_list(Children, Children_) },
   47    css_children(Children_),
   48    [end_media].
   49css_child('@keyframes'(Name, Frames)) -->
   50    !,
   51    { ensure_list(Frames, Frames_),
   52      text_to_string(Name, NameStr),
   53      string_codes(NameStr, NameCodes) },
   54    [begin_animation(NameCodes)],
   55    keyframes(Frames_),
   56    [end_animation].
   57css_child(Thing) -->
   58    { Thing =.. [Sel,StyleOrStyles],
   59      ensure_list(StyleOrStyles, Styles),
   60      text_to_string(Sel, SelStr),
   61      string_codes(SelStr, SelStrCodes) },
   62    [begin_styles(SelStrCodes)],
   63    css_styles(Styles),
   64    [end_styles(SelStrCodes)].
   65css_child(Thing) -->
   66    { Thing =.. [Sel,Styles,Children],
   67      ThingStyles =.. [Sel,Styles] },
   68    css_child(ThingStyles),
   69    { text_to_string(Sel, SelStr),
   70      string_codes(SelStr, SelStrCodes),
   71      ensure_list(Children, Children_) },
   72    [begin_ctx(SelStrCodes)],
   73    css_children(Children_),
   74    [end_ctx(SelStrCodes)].
   75
   76css_styles([]) --> [], { ! }.
   77css_styles([Style|Styles]) -->
   78    css_style(Style), css_styles(Styles).
   79
   80css_style(Style) -->
   81    { Style =.. [Attr, Value],
   82      atom_codes(Attr, AttrCodes),
   83      atom_codes(Value, ValueCodes) },
   84    [style(AttrCodes, ValueCodes)].
   85
   86:- meta_predicate write_css(//, -).
 write_css(+Css, -String) is semidet
True when String is the Css DCG written out as a string.
   90write_css(Css, String) :-
   91    phrase(Css, Elements0),
   92    partition(['@import'(_)]>>true,
   93              Elements0, ImportRules, Elements),
   94    phrase(import_rules(ImportRules), Codes, Codes1),
   95    phrase(css_tokens([], Elements), Codes1), !,
   96    string_codes(String, Codes).
   97
   98import_rules(['@import'(Arg)|Rest]) -->
   99    "@import ",
  100    { ensure_list(Arg, ArgL) }, import_args(ArgL),
  101    ";\n",
  102    import_rules(Rest).
  103import_rules([]) --> [].
  104
  105import_args([url(URL)|Rest]) -->
  106    !,
  107    "url(\"",
  108    { text_to_string(URL, URLStr),
  109      string_codes(URLStr, URLCodes) },
  110    URLCodes, "\")",
  111    import_args(Rest).
  112import_args([X|Rest]) -->
  113    " ",
  114    { text_to_string(X, Str),
  115      string_codes(Str, Codes) },
  116    Codes,
  117    import_args(Rest).
  118import_args([]) --> [].
  119
  120css_tokens(_, []) --> [].
  121css_tokens(Ctx, [begin_styles(S),end_styles(S)|Next]) -->
  122    css_tokens(Ctx, Next), { ! }.
  123css_tokens(Ctx, [begin_styles(SelCodes)|Next]) -->
  124    { append(Ctx, [SelCodes], CombinedCtx),
  125      collapse_ampersands(CombinedCtx, DerivedSels),
  126      split(DerivedSel, 0' , DerivedSels) },
  127    DerivedSel, " {\n", css_tokens(Ctx, Next).
  128css_tokens(Ctx, [end_styles(_)|Next]) -->
  129    "}\n", css_tokens(Ctx, Next).
  130css_tokens(Ctx, [style(Prop, Val)|Next]) -->
  131    "  ", Prop, ": ", Val, ";\n",
  132    css_tokens(Ctx, Next).
  133css_tokens(Ctx, [begin_ctx(AddCtx)|Next]) -->
  134    { append(Ctx, [AddCtx], NewCtx) },
  135    css_tokens(NewCtx, Next).
  136css_tokens(Ctx, [end_ctx(_)|Next]) -->
  137    { butlast(Ctx, NewCtx) },
  138    css_tokens(NewCtx, Next).
  139css_tokens(Ctx, [begin_media(Query)|Next]) -->
  140    "@media ", media_query(Query), " {\n",
  141    css_tokens(Ctx, Next).
  142css_tokens(Ctx, [end_media|Next]) -->
  143    "}\n",
  144    css_tokens(Ctx, Next).
  145css_tokens(Ctx, [begin_animation(Name)|Next]) -->
  146    "@keyframes ", Name, " {\n",
  147    css_tokens(Ctx, Next).
  148css_tokens(Ctx, [begin_keyframe(Pos)|Next]) -->
  149    "  ", Pos, " {\n",
  150    css_tokens(Ctx, Next).
  151css_tokens(Ctx, [end_keyframe|Next]) -->
  152    "  }\n",
  153    css_tokens(Ctx, Next).
  154css_tokens(Ctx, [end_animation|Next]) -->
  155    "}\n",
  156    css_tokens(Ctx, Next).
  157
  158media_query(and(Qs)) -->
  159    !, media_query_ands(Qs).
  160media_query(Elt) -->
  161    media_query_elt(Elt).
  162
  163media_query_ands([A,B|Rest]) -->
  164    media_query(A),
  165    " and ",
  166    media_query_ands([B|Rest]).
  167media_query_ands([E]) -->
  168    media_query(E).
  169media_query_ands([]) --> [].
  170
  171media_query_elt(max_width(W)) -->
  172    !,
  173    { text_to_string(W, S),
  174      string_codes(S, Cs) },
  175    "(max-width: ",  Cs, ")".
  176media_query_elt(min_width(W)) -->
  177    !,
  178    { text_to_string(W, S),
  179      string_codes(S, Cs) },
  180    "(min-width: ",  Cs, ")".
  181media_query_elt(color_scheme(Theme)) -->
  182    !,
  183    { text_to_string(Theme, S),
  184      string_codes(S, Cs) },
  185    "(prefers-color-scheme: ",  Cs, ")".
  186media_query_elt(motion(Type)) -->
  187    !,
  188    { text_to_string(Type, S),
  189      string_codes(S, Cs) },
  190    "(prefers-reduced-motion: ",  Cs, ")".
  191media_query_elt(X) -->
  192    { text_to_string(X, S),
  193      string_codes(S, Cs) },
  194    Cs.
  195
  196collapse_ampersands(Sels, CollapsedSels) :-
  197    foldl(add_selector, Sels, [], CollapsedSels).
  198
  199add_selector([0'&|SubSel], Ctx, NewCtx) :-
  200    last(Ctx, Parent),
  201    append(Parent, SubSel, NewSel),
  202    butlast(Ctx, CtxHead),
  203    append(CtxHead, [NewSel], NewCtx), !.
  204add_selector(SubSel, Ctx, NewCtx) :-
  205    append(Ctx, [SubSel], NewCtx).
  206
  207keyframes([]) --> [].
  208keyframes([Frame|Frames]) -->
  209    { Frame =.. [FramePos|Styles],
  210      text_to_string(FramePos, FramePosString),
  211      string_codes(FramePosString, FramePosCodes) },
  212    [begin_keyframe(FramePosCodes)],
  213    css_styles(Styles),
  214    [end_keyframe],
  215    keyframes(Frames)