packr 1.0.2 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +16 -0
- data/Manifest.txt +19 -0
- data/README.txt +112 -0
- data/Rakefile +12 -0
- data/bin/packr +91 -0
- data/lib/packr.rb +68 -240
- data/lib/packr/base62.rb +150 -0
- data/lib/packr/collection.rb +147 -0
- data/lib/packr/constants.rb +8 -0
- data/lib/packr/encoder.rb +35 -0
- data/lib/packr/map.rb +66 -0
- data/lib/packr/minifier.rb +80 -0
- data/lib/packr/parser.rb +21 -0
- data/lib/packr/privates.rb +19 -0
- data/lib/packr/regexp_group.rb +122 -122
- data/lib/packr/shrinker.rb +123 -0
- data/lib/packr/words.rb +39 -84
- data/lib/string.rb +6 -5
- data/test/test_packr.rb +140 -0
- metadata +79 -51
- data/README +0 -118
- data/test/assets/packed/controls.js +0 -1
- data/test/assets/packed/dragdrop.js +0 -1
- data/test/assets/packed/effects.js +0 -1
- data/test/assets/packed/prototype.js +0 -1
- data/test/assets/packed/prototype_shrunk.js +0 -1
- data/test/assets/src/controls.js +0 -833
- data/test/assets/src/dragdrop.js +0 -942
- data/test/assets/src/effects.js +0 -1088
- data/test/assets/src/prototype.js +0 -2515
- data/test/packr_test.rb +0 -68
data/lib/packr/parser.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class Packr
|
2
|
+
class Parser < RegexpGroup
|
3
|
+
|
4
|
+
def put(expression, replacement)
|
5
|
+
expression = DICTIONARY.exec(expression) if expression.is_a?(String)
|
6
|
+
super(expression, replacement)
|
7
|
+
end
|
8
|
+
|
9
|
+
# STRING1 requires backslashes to fix concat bug
|
10
|
+
DICTIONARY = RegexpGroup.new.
|
11
|
+
put(:OPERATOR, /return|typeof|[\[(\^=,{}:;&|!*?]/.source).
|
12
|
+
put(:CONDITIONAL, /\/\*@\w*|\w*@\*\/|\/\/@\w*|@\w+/.source).
|
13
|
+
put(:COMMENT1, /\/\/[^\n]*/.source).
|
14
|
+
put(:COMMENT2, /\/\*[^*]*\*+([^\/][^*]*\*+)*\//.source).
|
15
|
+
put(:REGEXP, /\/(\\[\/\\]|[^*\/])(\\.|[^\/\n\\])*\/[gim]*/.source).
|
16
|
+
put(:STRING1, /\'(\\.|[^\'\\])*\'/.source).
|
17
|
+
put(:STRING2, /"(\\.|[^"\\])*"/.source)
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Packr
|
2
|
+
class Privates < Encoder
|
3
|
+
|
4
|
+
IGNORE = {
|
5
|
+
:CONDITIONAL => Packr::IGNORE,
|
6
|
+
"(OPERATOR)(REGXEP)" => Packr::IGNORE
|
7
|
+
}
|
8
|
+
|
9
|
+
PATTERN = /\b_[\da-zA-Z$][\w$]*\b/
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super(PATTERN, lambda { |index|
|
13
|
+
"_" + Packr.encode62(index)
|
14
|
+
}, IGNORE)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
data/lib/packr/regexp_group.rb
CHANGED
@@ -1,122 +1,122 @@
|
|
1
|
-
class Packr
|
2
|
-
class RegexpGroup
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
|
64
|
-
length
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
#
|
99
|
-
q
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
expression
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
1
|
+
class Packr
|
2
|
+
class RegexpGroup < Collection
|
3
|
+
|
4
|
+
IGNORE = "\\0"
|
5
|
+
BACK_REF = /\\(\d+)/
|
6
|
+
ESCAPE_CHARS = /\\./
|
7
|
+
ESCAPE_BRACKETS = /\(\?[:=!]|\[[^\]]+\]/
|
8
|
+
BRACKETS = /\(/
|
9
|
+
LOOKUP = /\\(\d+)/
|
10
|
+
LOOKUP_SIMPLE = /^\\\d+$/
|
11
|
+
|
12
|
+
def initialize(values = nil, ignore_case = false)
|
13
|
+
super(values)
|
14
|
+
@ignore_case = !!ignore_case
|
15
|
+
end
|
16
|
+
|
17
|
+
def exec(string, override = nil)
|
18
|
+
string = string.to_s # type-safe
|
19
|
+
return string if @keys.empty?
|
20
|
+
override = 0 if override == IGNORE
|
21
|
+
string.gsub(Regexp.new(self.to_s, @ignore_case && Regexp::IGNORECASE)) do |match|
|
22
|
+
offset, i, result = 1, 0, match
|
23
|
+
arguments = [match] + $~.captures + [$~.begin(0), string]
|
24
|
+
# Loop through the items.
|
25
|
+
each do |item, key|
|
26
|
+
nxt = offset + item.length + 1
|
27
|
+
if arguments[offset] # do we have a result?
|
28
|
+
replacement = override.nil? ? item.replacement : override
|
29
|
+
case replacement
|
30
|
+
when Proc
|
31
|
+
result = replacement.call(*arguments[offset...nxt])
|
32
|
+
when Numeric
|
33
|
+
result = arguments[offset + replacement]
|
34
|
+
else
|
35
|
+
result = replacement
|
36
|
+
end
|
37
|
+
end
|
38
|
+
offset = nxt
|
39
|
+
end
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def insert_at(index, expression, replacement)
|
45
|
+
expression = expression.is_a?(Regexp) ? expression.source : expression.to_s
|
46
|
+
super(index, expression, replacement)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test(string)
|
50
|
+
exec(string) != string
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
offset = 1
|
55
|
+
"(" + map { |item, key|
|
56
|
+
# Fix back references.
|
57
|
+
expression = item.to_s.gsub(BACK_REF) { |m| "\\" + (offset + $1.to_i) }
|
58
|
+
offset += item.length + 1
|
59
|
+
expression
|
60
|
+
}.join(")|(") + ")"
|
61
|
+
end
|
62
|
+
|
63
|
+
class Item
|
64
|
+
attr_accessor :expression, :length, :replacement
|
65
|
+
|
66
|
+
def initialize(expression, replacement = nil)
|
67
|
+
@expression = expression
|
68
|
+
|
69
|
+
if replacement.nil?
|
70
|
+
replacement = IGNORE
|
71
|
+
elsif replacement.respond_to?(:replacement)
|
72
|
+
replacement = replacement.replacement
|
73
|
+
elsif !replacement.is_a?(Proc)
|
74
|
+
replacement = replacement.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
# does the pattern use sub-expressions?
|
78
|
+
if replacement.is_a?(String) and replacement =~ LOOKUP
|
79
|
+
# a simple lookup? (e.g. "\2")
|
80
|
+
if replacement.gsub(/\n/, " ") =~ LOOKUP_SIMPLE
|
81
|
+
# store the index (used for fast retrieval of matched strings)
|
82
|
+
replacement = replacement[1..-1].to_i
|
83
|
+
else # a complicated lookup (e.g. "Hello \2 \1")
|
84
|
+
# build a function to do the lookup
|
85
|
+
# Improved version by Alexei Gorkov:
|
86
|
+
q = '"'
|
87
|
+
replacement_string = replacement.
|
88
|
+
gsub(/\\/, "\\\\").
|
89
|
+
gsub(/"/, "\\x22").
|
90
|
+
gsub(/\n/, "\\n").
|
91
|
+
gsub(/\r/, "\\r").
|
92
|
+
gsub(/\\(\d+)/, q + "+(args[\\1]||" + q+q + ")+" + q).
|
93
|
+
gsub(/(['"])\1\+(.*)\+\1\1$/, '\1')
|
94
|
+
replacement = lambda { |*args| eval(q + replacement_string + q) }
|
95
|
+
|
96
|
+
# My old crappy version:
|
97
|
+
# q = (replacement.gsub(/\\./, "") =~ /'/) ? '"' : "'"
|
98
|
+
# replacement = replacement.gsub(/\r/, "\\r").gsub(/\\(\d+)/,
|
99
|
+
# q + "+(args[\\1]||" + q+q + ")+" + q)
|
100
|
+
# replacement_string = q + replacement.gsub(/(['"])\1\+(.*)\+\1\1$/, '\1') + q
|
101
|
+
# replacement = lambda { |*args| eval(replacement_string) }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
@length = RegexpGroup.count(@expression)
|
106
|
+
@replacement = replacement
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
@expression.respond_to?(:source) ? @expression.source : @expression.to_s
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.count(expression)
|
115
|
+
# Count the number of sub-expressions in a Regexp/RegexpGroup::Item.
|
116
|
+
expression = expression.to_s.gsub(ESCAPE_CHARS, "").gsub(ESCAPE_BRACKETS, "")
|
117
|
+
expression.scan(BRACKETS).length
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
class Packr
|
2
|
+
class Shrinker
|
3
|
+
|
4
|
+
ENCODED_DATA = /~\^(\d+)\^~/
|
5
|
+
PREFIX = '@'
|
6
|
+
SHRUNK = /\@\d+\b/
|
7
|
+
|
8
|
+
def decode_data(script)
|
9
|
+
# put strings and regular expressions back
|
10
|
+
script.gsub(ENCODED_DATA) { |match| @strings[$1.to_i] }
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode_data(script)
|
14
|
+
# encode strings and regular expressions
|
15
|
+
@strings = [] # encoded strings and regular expressions
|
16
|
+
DATA.exec(script, lambda { |match, *args|
|
17
|
+
operator, regexp = args[0].to_s, args[1].to_s
|
18
|
+
replacement = "~^#{@strings.length}^~"
|
19
|
+
unless regexp.empty?
|
20
|
+
replacement = operator + replacement
|
21
|
+
match = regexp
|
22
|
+
end
|
23
|
+
@strings << match
|
24
|
+
replacement
|
25
|
+
})
|
26
|
+
end
|
27
|
+
|
28
|
+
def shrink(script, protected_names = [])
|
29
|
+
script = encode_data(script)
|
30
|
+
protected_names ||= []
|
31
|
+
protected_names = protected_names.map { |s| s.to_s }
|
32
|
+
|
33
|
+
# identify blocks, particularly identify function blocks (which define scope)
|
34
|
+
__block = /((catch|do|if|while|with|function)\b[^~{};]*(\(\s*[^{};]*\s*\))\s*)?(\{[^{}]*\})/
|
35
|
+
__brackets = /\{[^{}]*\}|\[[^\[\]]*\]|\([^\(\)]*\)|~[^~]+~/
|
36
|
+
__encoded_block = /~#?(\d+)~/
|
37
|
+
__identifier = /[a-zA-Z_$][\w\$]*/
|
38
|
+
__scoped = /~#(\d+)~/
|
39
|
+
__var = /\bvar\b/
|
40
|
+
__vars = /\bvar\s+[\w$]+[^;#]*|\bfunction\s+[\w$]+/
|
41
|
+
__var_tidy = /\b(var|function)\b|\sin\s+[^;]+/
|
42
|
+
__var_equal = /\s*=[^,;]*/
|
43
|
+
|
44
|
+
blocks = [] # store program blocks (anything between braces {})
|
45
|
+
total = 0
|
46
|
+
# decoder for program blocks
|
47
|
+
decode_blocks = lambda do |script, encoded|
|
48
|
+
script = script.gsub(encoded) { |match| blocks[$1.to_i] } while script =~ encoded
|
49
|
+
script
|
50
|
+
end
|
51
|
+
|
52
|
+
# encoder for program blocks
|
53
|
+
encode_blocks = lambda do |match|
|
54
|
+
prefix, block_type, args, block = $1 || "", $2, $3, $4
|
55
|
+
if block_type == 'function'
|
56
|
+
# decode the function block (THIS IS THE IMPORTANT BIT)
|
57
|
+
# We are retrieving all sub-blocks and will re-parse them in light
|
58
|
+
# of newly shrunk variables
|
59
|
+
block = args + decode_blocks.call(block, __scoped)
|
60
|
+
prefix = prefix.gsub(__brackets, "")
|
61
|
+
|
62
|
+
# create the list of variable and argument names
|
63
|
+
args = args[1...-1]
|
64
|
+
|
65
|
+
if args != '_no_shrink_'
|
66
|
+
vars = block.scan(__vars).join(";").gsub(__var, ";var")
|
67
|
+
vars = vars.gsub(__brackets, "") while vars =~ __brackets
|
68
|
+
vars = vars.gsub(__var_tidy, "").gsub(__var_equal, "")
|
69
|
+
end
|
70
|
+
block = decode_blocks.call(block, __encoded_block)
|
71
|
+
|
72
|
+
# process each identifier
|
73
|
+
if args != '_no_shrink_'
|
74
|
+
count, short_id = 0, nil
|
75
|
+
ids = [args, vars].join(",").scan(__identifier)
|
76
|
+
processed = {}
|
77
|
+
ids.each do |id|
|
78
|
+
if !processed['#' + id] and !protected_names.include?(id)
|
79
|
+
processed['#' + id] = true
|
80
|
+
id = id.rescape
|
81
|
+
# encode variable names
|
82
|
+
count += 1 while block =~ Regexp.new("#{PREFIX}#{count}\\b")
|
83
|
+
reg = Regexp.new("([^\\w$.])#{id}([^\\w$:])")
|
84
|
+
block = block.gsub(reg, "\\1#{PREFIX}#{count}\\2") while block =~ reg
|
85
|
+
reg = Regexp.new("([^{,\\w$.])#{id}:")
|
86
|
+
block = block.gsub(reg, "\\1#{PREFIX}#{count}:")
|
87
|
+
count += 1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
total = [total, count].max
|
91
|
+
end
|
92
|
+
replacement = "#{prefix}~#{blocks.length}~"
|
93
|
+
blocks << block
|
94
|
+
else
|
95
|
+
replacement = "~##{blocks.length}~"
|
96
|
+
blocks << (prefix + block)
|
97
|
+
end
|
98
|
+
replacement
|
99
|
+
end
|
100
|
+
|
101
|
+
# encode blocks, as we encode we replace variable and argument names
|
102
|
+
script = script.gsub(__block, &encode_blocks) while script =~ __block
|
103
|
+
|
104
|
+
# put the blocks back
|
105
|
+
script = decode_blocks.call(script, __encoded_block)
|
106
|
+
|
107
|
+
short_id, count = nil, 0
|
108
|
+
shrunk = Encoder.new(SHRUNK, lambda { |object|
|
109
|
+
# find the next free short name
|
110
|
+
begin
|
111
|
+
short_id = Packr.encode52(count)
|
112
|
+
count += 1
|
113
|
+
end while script =~ Regexp.new("[^\\w$.]#{short_id}[^\\w$:]")
|
114
|
+
short_id
|
115
|
+
})
|
116
|
+
script = shrunk.encode(script)
|
117
|
+
|
118
|
+
decode_data(script)
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
data/lib/packr/words.rb
CHANGED
@@ -1,84 +1,39 @@
|
|
1
|
-
class Packr
|
2
|
-
class Words
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@words = @words.sort_by { |word| word.count }.reverse
|
41
|
-
|
42
|
-
a = 62
|
43
|
-
e = lambda do |c|
|
44
|
-
(c < a ? '' : e.call((c.to_f / a).to_i) ) +
|
45
|
-
((c = c % a) > 35 ? (c+29).chr : c.to_s(36))
|
46
|
-
end
|
47
|
-
|
48
|
-
# a dictionary of base62 -> base10
|
49
|
-
encoded = (0...(@words.size)).map { |i| e.call(i) }
|
50
|
-
|
51
|
-
index = 0
|
52
|
-
@words.each do |word|
|
53
|
-
if x = encoded.index(word.word)
|
54
|
-
word.index = x
|
55
|
-
def word.to_s; ""; end
|
56
|
-
else
|
57
|
-
index += 1 while has?(e.call(index))
|
58
|
-
word.index = index
|
59
|
-
index += 1
|
60
|
-
end
|
61
|
-
word.encoded = e.call(word.index)
|
62
|
-
end
|
63
|
-
|
64
|
-
# sort by encoding
|
65
|
-
@words = @words.sort_by { |word| word.index }
|
66
|
-
end
|
67
|
-
|
68
|
-
class Item
|
69
|
-
attr_accessor :word, :count, :encoded, :index
|
70
|
-
|
71
|
-
def initialize(word)
|
72
|
-
@word = word
|
73
|
-
@count = 0
|
74
|
-
@encoded = ""
|
75
|
-
@index = -1
|
76
|
-
end
|
77
|
-
|
78
|
-
def to_s
|
79
|
-
@word
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
1
|
+
class Packr
|
2
|
+
class Words < Collection
|
3
|
+
|
4
|
+
def add(word)
|
5
|
+
super unless has?(word)
|
6
|
+
word = get(word)
|
7
|
+
word.index = size if word.index.zero?
|
8
|
+
word.count = word.count + 1
|
9
|
+
word
|
10
|
+
end
|
11
|
+
|
12
|
+
def sort!(&sorter)
|
13
|
+
return super if block_given?
|
14
|
+
super do |word1, word2|
|
15
|
+
# sort by frequency
|
16
|
+
count = word2.count - word1.count
|
17
|
+
index = word1.index - word2.index
|
18
|
+
count.nonzero? ? count : (index.nonzero? ? index : 0)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Item
|
23
|
+
attr_accessor :index, :count, :encoded, :replacement
|
24
|
+
|
25
|
+
def initialize(word, item)
|
26
|
+
@word = word
|
27
|
+
@index = 0
|
28
|
+
@count = 0
|
29
|
+
@encoded = ""
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
@word
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|