front-compiler 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|