packr 1.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -1,122 +1,122 @@
1
- class Packr
2
- class RegexpGroup
3
-
4
- attr_accessor :values
5
-
6
- IGNORE = "\\0"
7
- BACK_REF = /\\(\d+)/
8
- ESCAPE_CHARS = /\\./
9
- ESCAPE_BRACKETS = /\(\?[:=!]|\[[^\]]+\]/
10
- BRACKETS = /\(/
11
- KEYS = "~"
12
-
13
- def initialize(values, flags = nil)
14
- @values = []
15
- values.each { |key, value| @values << Item.new(key, value) }
16
- if flags && flags.is_a(String)
17
- @ignore_case = !!(flags =~ /i/)
18
- end
19
- end
20
-
21
- def union(*args)
22
- values = {}
23
- @values.each { |item| values[item.expression] = item.replacement }
24
- args.each do |arg|
25
- arg.values.each { |item| values[item.expression] = item.replacement }
26
- end
27
- self.class.new(values)
28
- end
29
-
30
- def exec(string, &replacement)
31
- string = string.to_s
32
- regexp = value_of
33
-
34
- replacement ||= lambda do |match|
35
- return "" if match.nil?
36
- arguments = [match] + $~.captures + [$~.begin(0), string]
37
- offset, result = 1, ""
38
- @values.each do |item|
39
- nxt = offset + item.length + 1
40
- if arguments[offset] # do we have a result?
41
- rep = item.replacement
42
- if rep.is_a?(Proc)
43
- args = arguments[offset...nxt]
44
- index = arguments[-2]
45
- result = rep.call *(args + [index, string])
46
- else
47
- result = rep.is_a?(Numeric) ? arguments[offset + rep] : rep.to_s
48
- end
49
- end
50
- offset = nxt
51
- end
52
- result
53
- end
54
-
55
- replacement.is_a?(Proc) ? string.gsub(regexp, &replacement) :
56
- string.gsub(regexp, replacement.to_s)
57
- end
58
-
59
- def test(string)
60
- exec(string) != string
61
- end
62
-
63
- def to_s
64
- length = 0
65
- "(" + @values.map { |item|
66
- # Fix back references.
67
- ref = item.to_s.gsub(BACK_REF) { |m| "\\" + (1 + $1.to_i + length).to_s }
68
- length += item.length + 1
69
- ref
70
- }.join(")|(") + ")"
71
- end
72
-
73
- def value_of(type = nil)
74
- return self if type == Object
75
- flag = @ignore_case ? Regexp::IGNORECASE : nil
76
- Regexp.new(self.to_s, flag)
77
- end
78
-
79
- class Item
80
- attr_accessor :expression, :length, :replacement
81
-
82
- def initialize(expression, replacement)
83
- @expression = expression.is_a?(Regexp) ? expression.source : expression.to_s
84
-
85
- if replacement.is_a?(Numeric)
86
- replacement = "\\" + replacement.to_s
87
- elsif replacement.nil?
88
- replacement = ""
89
- end
90
-
91
- # does the pattern use sub-expressions?
92
- if replacement.is_a?(String) and replacement =~ /\\(\d+)/
93
- # a simple lookup? (e.g. "\2")
94
- if replacement.gsub(/\n/, " ") =~ /^\\\d+$/
95
- # store the index (used for fast retrieval of matched strings)
96
- replacement = replacement[1..-1].to_i
97
- else # a complicated lookup (e.g. "Hello \2 \1")
98
- # build a function to do the lookup
99
- q = (replacement.gsub(/\\./, "") =~ /'/) ? '"' : "'"
100
- replacement = replacement.gsub(/\r/, "\\r").gsub(/\\(\d+)/,
101
- q + "+(args[\\1]||" + q+q + ")+" + q)
102
- replacement_string = q + replacement.gsub(/(['"])\1\+(.*)\+\1\1$/, '\1') + q
103
- replacement = lambda { |*args| eval(replacement_string) }
104
- end
105
- end
106
-
107
- @length = RegexpGroup.count(@expression)
108
- @replacement = replacement
109
- end
110
-
111
- def to_s
112
- @expression
113
- end
114
- end
115
-
116
- def self.count(expression)
117
- expression = expression.to_s.gsub(ESCAPE_CHARS, "").gsub(ESCAPE_BRACKETS, "")
118
- expression.scan(BRACKETS).length
119
- end
120
-
121
- end
122
- end
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
+
@@ -1,84 +1,39 @@
1
- class Packr
2
- class Words
3
-
4
- WORDS = /\w+/
5
- attr_accessor :words
6
-
7
- def initialize(script)
8
- script.to_s.scan(WORDS).each { |word| add(word) }
9
- encode!
10
- end
11
-
12
- def add(word)
13
- @words ||= []
14
- @words << (stored_word = Item.new(word)) unless stored_word = get(word)
15
- word = stored_word
16
- word.count = word.count + 1
17
- word
18
- end
19
-
20
- def get(word)
21
- @words.find { |w| w.word == word.to_s }
22
- end
23
-
24
- def has?(word)
25
- !!(get word)
26
- end
27
-
28
- def size
29
- @words.size
30
- end
31
-
32
- def to_s
33
- @words.join("|")
34
- end
35
-
36
- private
37
-
38
- def encode!
39
- # sort by frequency
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
+