packr 1.0.2 → 3.1.0
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/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
|
+
|