src/z_cssmin.erl

%% @author Maas-Maarten Zeeman <mmzeeman@xs4all.nl>
%% @copyright 2018 Maas-Maarten Zeeman
%% @doc Javascript minifier. Based on cssmin.c

%% Copyright 2018 Maas-Maarten Zeeman
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.

%% cssmin is Copyright (c) 2010  (www.ryanday.org)
%% see https://github.com/soldair/cssmin

-module(z_cssmin).

-export([
    minify/1
    ]).

%% @doc Minify a binary containing CSS.
-spec minify( binary() ) -> binary().
minify( CSS ) ->
    machine(CSS, <<>>).

machine(<<>>, Acc) -> Acc;
machine(<<$/, $*, Rest/binary>>, Acc) -> machine(skip_comment(Rest), Acc);
machine(<<C, Rest/binary>>, Acc) when C =:= 32 orelse C =:= 10 -> machine(Rest, Acc);
machine(<<$@, Rest/binary>>, Acc) -> atrule(Rest, <<Acc/binary, $@>>);
machine(Bin, Acc) -> selector(Bin, Acc).

selector(<<>>, Acc) -> Acc;
selector(<<$/, $*, Rest/binary>>, Acc) -> selector(skip_comment(Rest), Acc);
selector(<<${, Rest/binary>>, Acc) -> block(Rest, <<Acc/binary, ${>>);
selector(<<$@, Rest/binary>>, Acc) -> atrule(Rest, <<Acc/binary, $@>>);
selector(<<10, Rest/binary>>, Acc) -> selector(Rest, Acc);
selector(<<32, Rest/binary>>, Acc) -> continue_selector(skip_space(Rest), Acc); 
selector(<<C, Rest/binary>>, Acc) -> selector(Rest, <<Acc/binary, C>>).

continue_selector(<<${, _/binary>> = Rest, Acc) -> selector(Rest, Acc);
continue_selector(Rest, Acc) -> 
    case binary:last(Acc) of
        32 -> selector(Rest, Acc);
        _ -> selector(Rest, <<Acc/binary, 32>>)
    end.

atrule(<<>>, Acc) -> Acc;
atrule(<<$/, $*, Rest/binary>>, Acc) -> atrule(skip_comment(Rest), Acc);
atrule(<<10, Rest/binary>>, Acc) ->
    case Rest of
        <<10, _/binary>> ->
            machine(Rest, <<Acc/binary, $;>>);
        _ ->
            atrule(Rest, <<Acc/binary, 32>>)
    end;
atrule(<<$;, Rest/binary>>, Acc) -> machine(Rest, <<Acc/binary, $;>>);
atrule(<<${, Rest/binary>>, Acc) -> block(Rest, <<Acc/binary, ${>>);
atrule(<<C, Rest/binary>>, Acc) -> atrule(Rest, <<Acc/binary, C>>).
 
block(<<>>, Acc) -> Acc;
block(<<$/, $*, Rest/binary>>, Acc) -> block(skip_comment(Rest), Acc);
block(<<$}, Rest/binary>>, Acc) -> machine(Rest, <<Acc/binary, $}>>);
block(Bin, Acc) -> declaration(skip_whitespace(Bin), Acc).

declaration(<<>>, Acc) -> Acc;

declaration(<<$/, $*, Rest/binary>>, Acc) -> declaration(skip_comment(Rest), Acc);
declaration(<<$(, Rest/binary>>, Acc) -> declaration_in_paren(Rest, <<Acc/binary, $(>>);
declaration(<<$;, Rest/binary>>, Acc) ->
    case skip_whitespace(Rest) of
        <<$}, _/binary>> = Rest1 -> block(Rest1, Acc);
        <<$;, _/binary>> = Rest1 -> block(Rest1, Acc);
        _ -> block(Rest, <<Acc/binary, $;>>)
    end;
declaration(<<$}, Rest/binary>>, Acc) -> machine(Rest, <<Acc/binary, $}>>);
declaration(<<32, Rest/binary>>, Acc) ->
    case binary:last(Acc) of
        32 -> declaration(Rest, Acc);
        _ -> declaration(Rest, <<Acc/binary, 32>>)
    end;
declaration(<<C, Rest/binary>>, Acc) ->
    declaration(Rest, <<Acc/binary, C>>).
 
declaration_in_paren(<<>>, Acc) -> Acc;
declaration_in_paren(<<$), Rest/binary>>, Acc) -> declaration(Rest, <<Acc/binary, $)>>);
declaration_in_paren(<<C, Rest/binary>>, Acc) -> declaration_in_paren(Rest, <<Acc/binary, C>>).

skip_comment(<<>>) -> <<>>;
skip_comment(<<$*, $/, Rest/binary>>) -> Rest;
skip_comment(<<_C, Rest/binary>>) -> skip_comment(Rest).

skip_space(<<32, Rest/binary>>) -> skip_space(Rest);
skip_space(Bin) -> Bin.

skip_whitespace(<<C, Rest/binary>>) when C =:= 32 orelse C =:= 10 -> skip_whitespace(Rest);
skip_whitespace(Bin) -> Bin.