Permalink
Browse files

Wip cssmin (#35)

* Added css minifier based on cssmin.c

* Skip spaces in selector

* Reduce zero units

* Condense multiple semicolons
  • Loading branch information...
mmzeeman committed Apr 17, 2018
1 parent 1adad76 commit 6d3ff66260b0d195bc8e89774809fe3fadb8a9d7
Showing with 149 additions and 0 deletions.
  1. +108 −0 src/z_cssmin.erl
  2. +41 −0 test/z_cssmin_test.erl
View
@@ -0,0 +1,108 @@
+%% @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
+ ]).
+
+-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.
+
+
+
+
+
+
View
@@ -0,0 +1,41 @@
+%% @author Maas-Maarten Zeeman <mmzeeman@xs4all.nl>
+
+-module(z_cssmin_test).
+
+-include_lib("eunit/include/eunit.hrl").
+
+simple_test() ->
+ ?assertEqual(<<>>, minify(<<>>)),
+ ?assertEqual(<<>>, minify(<<" ">>)),
+ ?assertEqual(<<>>, minify(<<"/* comment */">>)),
+ ok.
+
+class_selector_test() ->
+ ?assertEqual(<<"p :link">>, minify(<<"p :link">>)),
+
+ ?assertEqual(<<"code.html{color: #191970}">>, minify(<<"code.html { color: #191970; }">>)),
+ ?assertEqual(<<"code.html{color: #191970}">>, minify(<<"code.html { color: #191970; }">>)),
+
+ %% The comment after the selector causes the insertion of an extra space
+ %% it also leaves an extra colon after the last element of a block
+ ?assertEqual(<<"code.html { color: #191970; }">>,
+ minify(<<"/* a */code.html /* b */ { /* c */ color: /* d */ #191970; /* e */ } /* f */">>)),
+ ok.
+
+condense_multiple_semicolons_test() ->
+ ?assertEqual(<<".btn{color: red;display: block}">>, minify(<<".btn { color: red;; display: block;;;}">>)),
+ ok.
+
+minimize_zero_units_test() ->
+ ?assertEqual(<<"div{border: 10px}">>, minify(<<"div {border: 10px}">>)),
+ ?assertEqual(<<"div{border: 0px}">>, minify(<<"div {border: 0px}">>)),
+ ?assertEqual(<<"div{border: 0pt}">>, minify(<<"div {border: 0pt}">>)),
+ ?assertEqual(<<"div{width: 0%;border: 0in}">>, minify(<<"div {width: 0%; border: 0in}">>)),
+ ok.
+
+%%
+%% Helpers
+%%
+
+minify(Bin) ->
+ z_cssmin:minify(Bin).

0 comments on commit 6d3ff66

Please sign in to comment.