front-compiler 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +14 -0
- data/LICENSE +20 -0
- data/README +174 -0
- data/Rakefile +82 -0
- data/bin/frontcc +53 -0
- data/init.rb +25 -0
- data/lib/front-compiler.rb +5 -0
- data/lib/front_compiler/css_source/nested_styles.rb +72 -0
- data/lib/front_compiler/css_source.rb +42 -0
- data/lib/front_compiler/html_compactor.rb +24 -0
- data/lib/front_compiler/java_script/logic_compactor.rb +135 -0
- data/lib/front_compiler/java_script/names_compactor.rb +142 -0
- data/lib/front_compiler/java_script/self_builder.rb +137 -0
- data/lib/front_compiler/java_script.rb +53 -0
- data/lib/front_compiler/source_code.rb +105 -0
- data/lib/front_compiler.rb +59 -0
- data/lib/front_compiler_helper.rb +45 -0
- data/spec/lib/front_compiler/css_source/nested_styles_spec.rb.rb +65 -0
- data/spec/lib/front_compiler/css_source_spec.rb +111 -0
- data/spec/lib/front_compiler/html_compactor_spec.rb +41 -0
- data/spec/lib/front_compiler/java_script/logic_compactor_spec.rb +363 -0
- data/spec/lib/front_compiler/java_script/names_compactor_spec.rb +219 -0
- data/spec/lib/front_compiler/java_script/self_builder_spec.rb +253 -0
- data/spec/lib/front_compiler/java_script_spec.rb +60 -0
- data/spec/lib/front_compiler/source_code_spec.rb +26 -0
- data/spec/lib/front_compiler_helper_spec.rb +34 -0
- data/spec/lib/front_compiler_spec.rb +36 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +7 -0
- metadata +82 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
#
|
2
|
+
# This module contains the names compactor functionality
|
3
|
+
#
|
4
|
+
# This module is a part of the JavaScript class and taken out
|
5
|
+
# just to keep the things simple
|
6
|
+
#
|
7
|
+
# Copyright (C) Nikolay V. Nemshilov aka St.
|
8
|
+
#
|
9
|
+
class FrontCompiler
|
10
|
+
class JavaScript < FrontCompiler::SourceCode
|
11
|
+
module NamesCompactor
|
12
|
+
def compact_names!
|
13
|
+
string_safely do
|
14
|
+
compact_names_of self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
FUNCTION_START_RE = %r{
|
20
|
+
(\A|[^\da-z_\$]) # ensure this is not a weird method name
|
21
|
+
function(\s
|
22
|
+
([a-z\d\$_]+) # function name
|
23
|
+
)*\s*\(
|
24
|
+
([a-z\d\\$\s_,]*) # arguments list
|
25
|
+
\)\s* # end of the function definition
|
26
|
+
}imx
|
27
|
+
def compact_names_of(src)
|
28
|
+
offset = 0
|
29
|
+
while pos = src.index(FUNCTION_START_RE, offset)
|
30
|
+
func_start = $&.dup
|
31
|
+
args_block = $4.dup
|
32
|
+
body_block = find_block("{}", pos + func_start.size, src)
|
33
|
+
|
34
|
+
args, body = process_names(args_block, compact_names_of(body_block.dup))
|
35
|
+
func = func_start.gsub "(#{args_block})", "(#{args})"
|
36
|
+
|
37
|
+
src[pos, func_start.size + body_block.size] = func + body
|
38
|
+
|
39
|
+
offset = pos + func.size + body.size
|
40
|
+
end
|
41
|
+
|
42
|
+
src
|
43
|
+
end
|
44
|
+
|
45
|
+
# handles the names processing
|
46
|
+
def process_names(args, body)
|
47
|
+
args = args.dup # <- duplicating the string case they will be parsed
|
48
|
+
body = body.dup
|
49
|
+
|
50
|
+
# building the names replacement map
|
51
|
+
guess_names_map(body,
|
52
|
+
find_body_varnames(body).select{ |n| n.size > 1
|
53
|
+
}.concat(args.scan(/[\w\d_\$]+/im)
|
54
|
+
).uniq.sort.select{ |n| n != '$super' } # <- escape the Prototype stuff
|
55
|
+
).each do |name, replacement|
|
56
|
+
# replacing the names
|
57
|
+
[args, body].each do |str|
|
58
|
+
str.gsub!(/(\A|[^\w\d_\.\$])#{Regexp.escape(name)}(?![\w\d_\$]|\s*:)/) do
|
59
|
+
$1 + replacement
|
60
|
+
end
|
61
|
+
|
62
|
+
# replacing the names in the short 'a ? b : c' conditions
|
63
|
+
str.gsub!(/([^\{,\s\w\d_\.\$]\s*)#{Regexp.escape(name)}(\s*?:)/) do
|
64
|
+
$1 + replacement + $2
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
[args, body]
|
70
|
+
end
|
71
|
+
|
72
|
+
# makes decisions about the names cutting down
|
73
|
+
def guess_names_map(body, names)
|
74
|
+
names_map = { }
|
75
|
+
used_renames = []
|
76
|
+
|
77
|
+
@replacements ||= ('a'...'z').to_a.concat(('A'...'Z').to_a)
|
78
|
+
|
79
|
+
names.each do |name|
|
80
|
+
[name[/[a-z]/i]||'a'].concat(@replacements).each do |rename|
|
81
|
+
if !used_renames.include?(rename) and !body.match(/[^\w\d_\.\$]#{rename}[^\w\d_\$]/)
|
82
|
+
names_map[name] = rename
|
83
|
+
used_renames << rename
|
84
|
+
break
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
names_map
|
90
|
+
end
|
91
|
+
|
92
|
+
# extracts localy defined varnames of the given function body block
|
93
|
+
def find_body_varnames(body)
|
94
|
+
# getting the body body
|
95
|
+
body = body[1, body.size-2]
|
96
|
+
|
97
|
+
names = []
|
98
|
+
|
99
|
+
# removing the internal functions
|
100
|
+
while pos = body.index(FUNCTION_START_RE)
|
101
|
+
func = $&.dup
|
102
|
+
|
103
|
+
func_name = $3 ? $3.dup : ''
|
104
|
+
names << func_name unless func_name == ''
|
105
|
+
|
106
|
+
body[pos+1, func.size-1+find_block("{}", pos+func.size, body).size] = ''
|
107
|
+
end
|
108
|
+
|
109
|
+
# removing functions calls blocks
|
110
|
+
offset = 0
|
111
|
+
while pos = body.index(/[a-z0-9_]\(/i, offset)
|
112
|
+
pos += 1
|
113
|
+
block = find_block("()", pos, body)
|
114
|
+
body[pos, block.size] = '' unless body[0, pos].match(/(if|for|while|catch)\s*\Z/im)
|
115
|
+
|
116
|
+
offset = pos + 1
|
117
|
+
end
|
118
|
+
|
119
|
+
# getting the vars definitions
|
120
|
+
body.scan(/[\s\(:;=\{\[^$]+var\s(.*?)(;|$)/im) do |match|
|
121
|
+
line = $1.dup
|
122
|
+
|
123
|
+
# removing arrays and objects definitions out of the line
|
124
|
+
['[]', '{}'].each do |token|
|
125
|
+
while pos = line.index(token=='[]' ? /\[.*?\]/ : /\{.*?\}/)
|
126
|
+
line[pos, $&.size] = ''
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# removing objects definitions
|
132
|
+
|
133
|
+
names.concat(line.split(",").collect{ |token|
|
134
|
+
token[/[\w\d_\$]+/i]
|
135
|
+
}.compact)
|
136
|
+
end
|
137
|
+
|
138
|
+
names
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#
|
2
|
+
# This module contains the hashes compactor functionality
|
3
|
+
#
|
4
|
+
# This module is a part of the JavaScript class and taken out
|
5
|
+
# just to keep the things simple
|
6
|
+
#
|
7
|
+
# Copyright (C) 2009 Nikolay V. Nemshilov aka St.
|
8
|
+
#
|
9
|
+
class FrontCompiler
|
10
|
+
class JavaScript < FrontCompiler::SourceCode
|
11
|
+
module SelfBuilder
|
12
|
+
def create_self_build
|
13
|
+
rehashed_version = process_hashes_in(self)
|
14
|
+
rehashed_version.size > self.size ? self : rehashed_version
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def process_hashes_in(string)
|
20
|
+
create_build_script *compact_hashes_in(string)
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_build_script(source, names_map)
|
24
|
+
"eval((function(s,d){"+
|
25
|
+
# building the postprocessing script
|
26
|
+
"for(var i=d.length-1;i>-1;i--)"+
|
27
|
+
"if(d[i])"+
|
28
|
+
"s=s.replace(new RegExp(i,'g'),d[i]);"+
|
29
|
+
"return s"+
|
30
|
+
"})("+
|
31
|
+
"\"#{source.gsub("\\", "\\\\\\\\").gsub("\n", '\\n').gsub('"', '\"').gsub('\\\'', '\\\\\\\\\'')}\","+
|
32
|
+
"\"#{names_map.join(',')}\".split(\",\")"+
|
33
|
+
"));"
|
34
|
+
end
|
35
|
+
|
36
|
+
def compact_hashes_in(string)
|
37
|
+
string = string.dup
|
38
|
+
|
39
|
+
names_map = guess_replacements_map(string, tokens_to_replace_in(string))
|
40
|
+
|
41
|
+
names_map.each_with_index do |token, i|
|
42
|
+
string.gsub! token, "#{i}" if token != ''
|
43
|
+
end
|
44
|
+
|
45
|
+
[string, names_map]
|
46
|
+
end
|
47
|
+
|
48
|
+
def tokens_to_replace_in(string)
|
49
|
+
# grabbign the basic list of impact tokens
|
50
|
+
impact_tokens = get_impact_tokens(string,
|
51
|
+
/(?![^a-z0-9_$])([a-z0-9_$]{#{MINUMUM_REPLACEABLE_TOKEN_SIZE},88})(?![a-z0-9_$])/i)
|
52
|
+
|
53
|
+
# getting unprocessed sub-tokens list
|
54
|
+
string = string.dup
|
55
|
+
impact_tokens.each{ |token| string.gsub! token.split(':').first, '' }
|
56
|
+
|
57
|
+
sub_impact_tokens = get_impact_tokens(string, /([A-Z][a-z]{#{MINUMUM_REPLACEABLE_TOKEN_SIZE-1},88})(?![a-z])/)
|
58
|
+
|
59
|
+
|
60
|
+
#
|
61
|
+
# NOTE: with the optimisation, the sub-tokens should be on the tokens list
|
62
|
+
# after the basic tokens, so they were not affecting each other
|
63
|
+
#
|
64
|
+
|
65
|
+
# the basic dictionary has some space for new tokens, adding some from the sub-tokens list
|
66
|
+
if impact_tokens.size < MAXIMUM_DICTIONARY_SIZE
|
67
|
+
sub_tokens = sub_impact_tokens[0, MAXIMUM_DICTIONARY_SIZE - impact_tokens.size]
|
68
|
+
else
|
69
|
+
# replacing the shortest basic tokens with longest sub-tokens
|
70
|
+
sub_tokens = []
|
71
|
+
while impact_tokens.last && sub_impact_tokens.first && impact_tokens.last.size < sub_impact_tokens.first.size
|
72
|
+
sub_tokens << sub_impact_tokens.shift
|
73
|
+
impact_tokens.pop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
impact_tokens.concat(sub_tokens)
|
78
|
+
|
79
|
+
# grabbing the single tokens back
|
80
|
+
impact_tokens.collect{|line| line.split(':').first }
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# creates a list of impact-tokens (tokens multiplied to the number of their appearances)
|
85
|
+
#
|
86
|
+
def get_impact_tokens(string, regexp)
|
87
|
+
keys = {}
|
88
|
+
|
89
|
+
# scanning through the string and calculating the number of appearances
|
90
|
+
string.scan(regexp).collect(&:last).each do |key|
|
91
|
+
keys[key] ||= 0
|
92
|
+
keys[key] += 1
|
93
|
+
end
|
94
|
+
|
95
|
+
# kicking of the tokens which appears less then twice
|
96
|
+
keys.reject!{|key,number| number < 2 }
|
97
|
+
|
98
|
+
# creating the impact tokens
|
99
|
+
tokens = keys.collect do |token, number|
|
100
|
+
line = []
|
101
|
+
number.times{ line << token }
|
102
|
+
line.join(":")
|
103
|
+
end
|
104
|
+
|
105
|
+
# sorting the tokens by impact
|
106
|
+
tokens.sort{|a,b| b.size <=> a.size}[0, MAXIMUM_DICTIONARY_SIZE]
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Generic replacements quessing method
|
111
|
+
#
|
112
|
+
def guess_replacements_map(string, keys)
|
113
|
+
map = []
|
114
|
+
index = -1
|
115
|
+
keys.each do |old_name|
|
116
|
+
index += 1
|
117
|
+
|
118
|
+
while string.match(/#{index}/)
|
119
|
+
map << ''
|
120
|
+
index+= 1
|
121
|
+
end
|
122
|
+
|
123
|
+
map << old_name
|
124
|
+
|
125
|
+
string = string.gsub old_name, "#{index}"
|
126
|
+
end
|
127
|
+
|
128
|
+
map
|
129
|
+
end
|
130
|
+
|
131
|
+
public
|
132
|
+
|
133
|
+
MINUMUM_REPLACEABLE_TOKEN_SIZE = 3
|
134
|
+
MAXIMUM_DICTIONARY_SIZE = 150
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#
|
2
|
+
# This class represents a java-script source code
|
3
|
+
#
|
4
|
+
# Copyright (C) Nikolay V. Nemshilov aka St.
|
5
|
+
#
|
6
|
+
require "front_compiler/java_script/logic_compactor"
|
7
|
+
require "front_compiler/java_script/names_compactor"
|
8
|
+
require "front_compiler/java_script/self_builder"
|
9
|
+
|
10
|
+
class FrontCompiler::JavaScript < FrontCompiler::SourceCode
|
11
|
+
include LogicCompactor, NamesCompactor, SelfBuilder
|
12
|
+
|
13
|
+
def compact!
|
14
|
+
string_safely do
|
15
|
+
remove_comments!.
|
16
|
+
compact_logic!.
|
17
|
+
compact_names!.
|
18
|
+
remove_empty_lines!.
|
19
|
+
remove_trailing_spaces!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_comments!
|
24
|
+
string_safely do
|
25
|
+
gsub!(/\/\*.*?\*\//im, '')
|
26
|
+
gsub!(/\/\/.*?($)/, '\1')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_empty_lines!
|
31
|
+
string_safely do
|
32
|
+
gsub!(/\n\s*\n/m, "\n")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def remove_trailing_spaces!
|
37
|
+
string_safely do
|
38
|
+
gsub!(/\s+/im, ' ') # cutting down all spaces to the minimum
|
39
|
+
gsub!(/\s*(=|\+|\-|<|>|\?|\|\||&&|\!|\{|\}|,|\)|\(|;|\]|\[|:|\*|\/)\s*/im, '\1')
|
40
|
+
gsub!(/;(\})/, '\1') # removing unnecessary semicolons
|
41
|
+
gsub!(/([^a-z\d_\$]typeof)\s+([a-z\d\$_]+)/im, '\1(\2)') # converting the typeof calls
|
42
|
+
strip!
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
# extends the basic class to excape regular expressions as well
|
48
|
+
def string_safely(&block)
|
49
|
+
super [
|
50
|
+
/([\(=:]\s*)\/[^\*\/][^\n]*?[^\*\n\\](?!\\\/)\// # <- regexps
|
51
|
+
], &block
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#
|
2
|
+
# That's all the source-codes base class
|
3
|
+
#
|
4
|
+
# Copyright (C) Nikolay V. Nemshilov aka St.
|
5
|
+
#
|
6
|
+
class FrontCompiler::SourceCode < String
|
7
|
+
def initialize(src)
|
8
|
+
super src
|
9
|
+
end
|
10
|
+
|
11
|
+
# returns a fully compacted source
|
12
|
+
def compact!
|
13
|
+
remove_comments!.
|
14
|
+
remove_empty_lines!.
|
15
|
+
remove_trailing_spaces!
|
16
|
+
end
|
17
|
+
|
18
|
+
# removes any comments
|
19
|
+
def remove_comments!
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# removes empty lines
|
24
|
+
def remove_empty_lines!
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# removes all the trailing spaces
|
29
|
+
def remove_trailing_spaces!
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# don't use alias here, it won't work with subclasses
|
34
|
+
def compact
|
35
|
+
compact!
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
# executes the given block, safely for the strings declared in the source-code
|
40
|
+
# can apply a list of additional regexps to escape
|
41
|
+
def string_safely(additional_regexps=[], &block)
|
42
|
+
if @in_string_safe_mode
|
43
|
+
yield
|
44
|
+
return self
|
45
|
+
end
|
46
|
+
|
47
|
+
outtakes = []
|
48
|
+
|
49
|
+
[/(\A|[^\\])('|")(\2)/, # <- empty strings
|
50
|
+
/(\A|[^\\])('|").*?[^\\](\2)/ # <- usual strings
|
51
|
+
].concat(additional_regexps).each do |regexp|
|
52
|
+
gsub! regexp do |match|
|
53
|
+
replacement = "rIgAxpOrStrEEng$$$#{outtakes.length}$$$riPlOcImEnt"
|
54
|
+
start = $1.dup
|
55
|
+
outtakes << {
|
56
|
+
:replacement => replacement,
|
57
|
+
:original => match.to_s[start.size, match.to_s.size]
|
58
|
+
}
|
59
|
+
|
60
|
+
start + replacement
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@in_string_safe_mode = true
|
64
|
+
|
65
|
+
yield block
|
66
|
+
|
67
|
+
# bgingin the strings back
|
68
|
+
outtakes.reverse.each do |s|
|
69
|
+
gsub! s[:replacement], s[:original].gsub('\\','\\\\\\\\') # <- escapes reescaping
|
70
|
+
end
|
71
|
+
@in_string_safe_mode = false
|
72
|
+
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# searches for a block in the code which starts at the given position
|
77
|
+
BLOCK_CHUNKS = {"()" => ["(",")"], "{}" => ["{","}"], "[]" => ["[","]"]}
|
78
|
+
def find_block(like, offset=0, stack=nil)
|
79
|
+
left, right = BLOCK_CHUNKS[like]
|
80
|
+
src = stack || self
|
81
|
+
start_pos = src.index(left, offset)
|
82
|
+
|
83
|
+
# return an empty string if the left sign was not found or fount not at the beginning
|
84
|
+
return '' if start_pos.nil? or !src[offset, start_pos-offset].match(/\A\s*?\Z/im)
|
85
|
+
|
86
|
+
start_pos += 1 # <- include the left sign to the block
|
87
|
+
block = src[offset, start_pos-offset]
|
88
|
+
|
89
|
+
count = 0
|
90
|
+
(start_pos...src.size).each do |i|
|
91
|
+
char = src.slice(i,1)
|
92
|
+
|
93
|
+
block << char
|
94
|
+
|
95
|
+
if char == right and count == 0
|
96
|
+
break
|
97
|
+
else
|
98
|
+
count += 1 if char == left
|
99
|
+
count -= 1 if char == right
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
block
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# FrontCompiler, the front unit of the library
|
3
|
+
#
|
4
|
+
# Copyright (C) Nikolay V. Nemshilov aka St.
|
5
|
+
#
|
6
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))).uniq!
|
7
|
+
|
8
|
+
class FrontCompiler
|
9
|
+
VERSION = "1.0.6"
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@html_compactor = HTMLCompactor.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# compacts all the files out of the list
|
16
|
+
# and returns them as a single string
|
17
|
+
def compact_files(list)
|
18
|
+
list.collect do |file|
|
19
|
+
compact_file file
|
20
|
+
end.join("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
# compacts the given file (path or a File object)
|
24
|
+
def compact_file(file)
|
25
|
+
file = File.open(file, 'r') if file.is_a?(String)
|
26
|
+
|
27
|
+
case file.path.split('.').last.downcase
|
28
|
+
when 'js' then compact_js file.read
|
29
|
+
when 'css' then compact_css file.read
|
30
|
+
when 'html' then compact_html file.read
|
31
|
+
else file.read
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# compacts a JavaScript source code
|
36
|
+
def compact_js(source)
|
37
|
+
JavaScript.new(source).compact
|
38
|
+
end
|
39
|
+
|
40
|
+
# compacts a CSS source code
|
41
|
+
def compact_css(source)
|
42
|
+
CssSource.new(source).compact
|
43
|
+
end
|
44
|
+
|
45
|
+
# compacts a HTML code
|
46
|
+
def compact_html(source)
|
47
|
+
@html_compactor.minimize(source)
|
48
|
+
end
|
49
|
+
|
50
|
+
# inlines the css-sourcecode in a javascript code
|
51
|
+
def inline_css(source)
|
52
|
+
CssSource.new(source).to_javascript
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
require "front_compiler/source_code"
|
57
|
+
require "front_compiler/java_script"
|
58
|
+
require "front_compiler/css_source"
|
59
|
+
require "front_compiler/html_compactor"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# The module provides some methods which can be used
|
3
|
+
# in rails controller or something like that
|
4
|
+
#
|
5
|
+
# Generally this is just a facade for the FrontCompiler
|
6
|
+
#
|
7
|
+
# Copyright (C) Nikolay V. Nemshilov aka St.
|
8
|
+
#
|
9
|
+
|
10
|
+
module FrontCompilerHelper
|
11
|
+
protected
|
12
|
+
def compact_files(list)
|
13
|
+
front_compiler.compact_files(list)
|
14
|
+
end
|
15
|
+
|
16
|
+
def compact_file(file)
|
17
|
+
front_compiler.compact_file(file)
|
18
|
+
end
|
19
|
+
|
20
|
+
def compact_js(source)
|
21
|
+
front_compiler.compact_js(source)
|
22
|
+
end
|
23
|
+
|
24
|
+
def compact_css(source)
|
25
|
+
front_compiler.compact_css(source)
|
26
|
+
end
|
27
|
+
|
28
|
+
def compact_html(source)
|
29
|
+
front_compiler.compact_html(source)
|
30
|
+
end
|
31
|
+
|
32
|
+
def inline_css(source)
|
33
|
+
front_compiler.inline_css(source)
|
34
|
+
end
|
35
|
+
|
36
|
+
def inline_css_file(file)
|
37
|
+
file = File.open(file) if file.is_a? String
|
38
|
+
inline_css file.read
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def front_compiler
|
43
|
+
@front_compiler ||= FrontCompiler.new
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.dirname(__FILE__)+"/../../../spec_helper"
|
2
|
+
|
3
|
+
describe FrontCompiler::CssSource::NestedStyles do
|
4
|
+
def css(src)
|
5
|
+
FrontCompiler::CssSource.new(src)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should convert nested constructions" do
|
9
|
+
css(%{
|
10
|
+
div.stuff {
|
11
|
+
border: 1px solid #EEE;
|
12
|
+
|
13
|
+
div.name {
|
14
|
+
font-weight: bold;
|
15
|
+
|
16
|
+
div.id:before {
|
17
|
+
content: '#';
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
div.name, div.text {
|
22
|
+
padding: 10pt;
|
23
|
+
|
24
|
+
a.user,
|
25
|
+
a.delete {
|
26
|
+
background-position: left;
|
27
|
+
background-repeat: no-repeat;
|
28
|
+
background-image: url('user.png');
|
29
|
+
}
|
30
|
+
|
31
|
+
a.delete {
|
32
|
+
background-image: url('delete.png');
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}).should == %{
|
37
|
+
div.stuff {
|
38
|
+
border: 1px solid #EEE;
|
39
|
+
}
|
40
|
+
|
41
|
+
div.stuff div.name {
|
42
|
+
font-weight: bold;
|
43
|
+
}
|
44
|
+
|
45
|
+
div.stuff div.name div.id:before {
|
46
|
+
content: '#';
|
47
|
+
}
|
48
|
+
|
49
|
+
div.stuff div.name, div.stuff div.text {
|
50
|
+
padding: 10pt;
|
51
|
+
}
|
52
|
+
|
53
|
+
div.stuff div.name a.user, div.stuff div.text a.user,
|
54
|
+
div.stuff div.name a.delete, div.stuff div.text a.delete {
|
55
|
+
background-position: left;
|
56
|
+
background-repeat: no-repeat;
|
57
|
+
background-image: url('user.png');
|
58
|
+
}
|
59
|
+
|
60
|
+
div.stuff div.name a.delete, div.stuff div.text a.delete {
|
61
|
+
background-image: url('delete.png');
|
62
|
+
}
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|