front-compiler 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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