1:- module(tailwind_generate, [text_tw_css/2,
    2                              tw_from_file/2]).

generate CSS from text containing selectors

Predicates for generating tailwind CSS stylesheets based on used selectors

author
- James Cash */
   10:- use_module(library(apply_macros)).   11:- use_module(library(apply), [maplist/2, convlist/3]).   12:- use_module(library(pcre), [re_replace/4]).   13:- use_module(library(ordsets), [list_to_ord_set/2]).   14:- use_module(library(yall)).   15
   16:- use_module(tailwind_generate/grammar, [tailwind//1, prefixes//2]).   17
   18tw_selector(Media, Variants, Styles) -->
   19    prefixes(Media, Variants), tailwind(Styles).
   20
   21word_style(Word, Style) :-
   22    string_codes(Word, Codes),
   23    re_replace("([^-a-zA-Z0-9])"/g, "\\$1", Word, EscapedWord),
   24    format(atom(Sel), ".~w", [EscapedWord]),
   25    phrase(tw_selector(Media, Variants, Styles), Codes),
   26    ( is_list(Styles)
   27    -> partition(['@keyframes'(_, _)]>>true, Styles, Anims, Styles0)
   28    ;  ( Anims = [], Styles0 = Styles) ),
   29    make_style(Sel, Media, Variants, Styles0, Style_),
   30    ( Anims == []
   31    -> Style = Style_
   32    ;  Style = [Style_|Anims]).
   33
   34apply_inner(BaseSel, [], BaseSel) :- !.
   35apply_inner(BaseSel, Inners, Selector) :-
   36    maplist([inner(Attr), Attr]>>true, Inners, Attrs),
   37    atomic_list_concat(Attrs, ':', PseudoClasses),
   38    format(atom(Selector), "~w:~w", [BaseSel, PseudoClasses]).
   39
   40wrap_outers(BaseSel, [], BaseSel) :- !.
   41wrap_outers(BaseSel, Outers, Selector) :-
   42    maplist([outer(Sel), Sel]>>true, Outers, OuterSels),
   43    % does having multiple outer variants even make sense?
   44    atomic_list_concat(OuterSels, '', OuterSel),
   45    format(atom(Selector), "~w ~w", [OuterSel, BaseSel]).
   46
   47make_style(ClsName, [], [], &(ChildSel), Style) :- !,
   48    Style =.. [ClsName, [], ChildSel].
   49make_style(ClsName, [], [], Styles, Style) :- !,
   50    Style =.. [ClsName, Styles].
   51make_style(ClsName, [], Variants, Styles, Css) :- !,
   52    partition([outer(_)]>>true, Variants, Outers, Inners),
   53    apply_inner(ClsName, Inners, InnerSelector),
   54    wrap_outers(InnerSelector, Outers, Selector),
   55    make_style(Selector, [], [], Styles, Css).
   56make_style(ClsName, Media, Variants, Styles, '@media'(and(Media), NestedStyle)) :-
   57    make_style(ClsName, [], Variants, Styles, NestedStyle).
 text_tw_css(+Text, -Css) is det
True when Text is an atom or string containing zero or more space-seperated Tailwind-style selectors and Css is the corresponding list of CSS styles, suitable to be fed to write_css/2.
   65text_tw_css(Text, Css) :-
   66    text_to_string(Text, String),
   67    split_string(String, " ", "", Strs),
   68    convlist(word_style, Strs, Css0),
   69    merge_keyframe_styles(Css0, Css, []).
   70
   71merge_keyframe_styles([], Tail, Tail) :- !.
   72merge_keyframe_styles([C|Cs], Tail0, Tail) :-
   73    is_list(C), !,
   74    append(C, Tail1, Tail0),
   75    merge_keyframe_styles(Cs, Tail1, Tail).
   76merge_keyframe_styles([C|Cs], [C|Tail0], Tail) :-
   77    merge_keyframe_styles(Cs, Tail0, Tail).
   78
   79% like read_file_to_terms, put ignoring quasi_quotes
   80% can't just pass quasi_quotations(_) to =read_file_to_terms=
   81% because each call of read_terms/3 tries to unify the variable to different values
   82read_file_to_terms_noqq(File, Terms) :-
   83    setup_call_cleanup(
   84        open(File, read, Stream),
   85        read_stream_to_terms_noqq(Stream, Terms),
   86        close(Stream)
   87    ).
   88
   89read_stream_to_terms_noqq(Stream, Terms) :-
   90    read_term(Stream, C0, [quasi_quotations(_), syntax_errors(quiet)]),
   91    read_stream_to_terms_noqq(C0, Stream, Terms).
   92
   93read_stream_to_terms_noqq(end_of_file, _, []) :- !.
   94read_stream_to_terms_noqq(C, Stream, [C|Ts]) :-
   95    read_term(Stream, C2, [quasi_quotations(_), syntax_errors(quiet)]),
   96    read_stream_to_terms_noqq(C2, Stream, Ts).
   97
   98text_from_file(File, UniqTexts) :-
   99    read_file_to_terms_noqq(File, Terms), !,
  100    extract_text_from_terms(Terms, Texts, []),
  101    list_to_ord_set(Texts, UniqTexts).
  102text_from_file(_, []).
  103
  104extract_text_from_terms([], Tail, Tail1) => Tail = Tail1.
  105extract_text_from_terms([Term|Terms], Tail, Tail0), string(Term) =>
  106    Tail = [Term|NewTail],
  107    extract_text_from_terms(Terms, NewTail, Tail0).
  108extract_text_from_terms([Term|Terms], Tail, Tail0), atom(Term) =>
  109    Tail = [Term|NewTail],
  110    extract_text_from_terms(Terms, NewTail, Tail0).
  111extract_text_from_terms([Term|Terms], Tail, Tail0), is_list(Term) =>
  112    extract_text_from_terms(Term, Tail, NewTail),
  113    extract_text_from_terms(Terms, NewTail, Tail0).
  114extract_text_from_terms([Term|Terms], Tail, Tail0), compound(Term) =>
  115    Term =.. [_|Children],
  116    extract_text_from_terms(Children, Tail, NewTail),
  117    extract_text_from_terms(Terms, NewTail, Tail0).
  118extract_text_from_terms([_|Terms], Tail, Tail0) =>
  119    extract_text_from_terms(Terms, Tail, Tail0).
 tw_from_file(+File, -Css) is det
When File is the path to a file, then Css will be the corresponding list of CSS selectors & styles corresponding to all the tailwind-style selectors found inside File, suitable to be used as input to write_css/2.
  127tw_from_file(File, Css) :-
  128    text_from_file(File, Texts),
  129    all_tws_from(Texts, Css).
  130
  131all_tws_from(Text, Css) :-
  132    all_tws_from(Text, Css-Css, _-[]).
  133
  134all_tws_from([], List-Tail, List-Tail) :- !.
  135all_tws_from([Text|Texts], Csses-Tail0, Csses-Tail) :-
  136    text_tw_css(Text, Css),
  137    append(Css, Tail1, Tail0),
  138    all_tws_from(Texts, Csses-Tail1, Csses-Tail)