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