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.
@@ -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