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,150 @@
1
+ class Packr
2
+ class Base62 < Encoder
3
+
4
+ WORDS = /\b[\da-zA-Z]\b|\w{2,}/
5
+
6
+ ENCODE10 = "String"
7
+ ENCODE36 = "function(c){return c.toString(36)}"
8
+ ENCODE62 = "function(c){return(c<62?'':e(parseInt(c/62)))+((c=c%62)>35?String.fromCharCode(c+29):c.toString(36))}"
9
+
10
+ UNPACK = lambda do |p,a,c,k,e,r|
11
+ "eval(function(p,a,c,k,e,r){e=#{e};if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];" +
12
+ "k=[function(e){return r[e]||e}];e=function(){return'#{r}'};c=1};while(c--)if(k[c])p=p." +
13
+ "replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('#{p}',#{a},#{c},'#{k}'.split('|'),0,{}))"
14
+ end
15
+
16
+ def encode(script)
17
+ words = search(script)
18
+ words.sort!
19
+
20
+ encoded = Collection.new # a dictionary of base62 -> base10
21
+ words.size.times { |i| encoded.put(Packr.encode62(i), i) }
22
+
23
+ replacement = lambda { |word| words.get(word).replacement }
24
+
25
+ index = 0
26
+ words.each do |word, key|
27
+ if encoded.has?(word)
28
+ word.index = encoded.get(word)
29
+ def word.to_s; ""; end
30
+ else
31
+ index += 1 while words.has?(Packr.encode62(index))
32
+ word.index = index
33
+ index += 1
34
+ if word.count == 1
35
+ def word.to_s; ""; end
36
+ end
37
+ end
38
+ word.replacement = Packr.encode62(word.index)
39
+ if word.replacement.length == word.to_s.length
40
+ def word.to_s; ""; end
41
+ end
42
+ end
43
+
44
+ # sort by encoding
45
+ words.sort! { |word1, word2| word1.index - word2.index }
46
+
47
+ # trim unencoded words
48
+ words = words.slice(0, get_key_words(words).split("|").length)
49
+
50
+ script = script.gsub(get_pattern(words), &replacement)
51
+
52
+ # build the packed script
53
+
54
+ p = escape(script)
55
+ a = "[]"
56
+ c = get_count(words)
57
+ k = get_key_words(words)
58
+ e = get_encoder(words)
59
+ d = get_decoder(words)
60
+
61
+ # the whole thing
62
+ UNPACK.call(p,a,c,k,e,d)
63
+ end
64
+
65
+ def search(script)
66
+ words = Words.new
67
+ script.scan(WORDS).each { |word| words.add(word) }
68
+ words
69
+ end
70
+
71
+ def escape(script)
72
+ # Single quotes wrap the final string so escape them.
73
+ # Also, escape new lines (required by conditional comments).
74
+ script.gsub(/([\\'])/) { |match| "\\#{$1}" }.gsub(/[\r\n]+/, "\\n")
75
+ end
76
+
77
+ def get_count(words)
78
+ size = words.size
79
+ size.zero? ? 1 : size
80
+ end
81
+
82
+ def get_decoder(words)
83
+ # returns a pattern used for fast decoding of the packed script
84
+ trim = RegexpGroup.new.
85
+ put("(\\d)(\\|\\d)+\\|(\\d)", "\\1-\\3").
86
+ put("([a-z])(\\|[a-z])+\\|([a-z])", "\\1-\\3").
87
+ put("([A-Z])(\\|[A-Z])+\\|([A-Z])", "\\1-\\3").
88
+ put("\\|", "")
89
+
90
+ pattern = trim.exec(words.map { |word, key|
91
+ word.to_s.empty? ? "" : word.replacement
92
+ }[0...62].join("|"))
93
+
94
+ return "^$" if pattern.empty?
95
+
96
+ pattern = "[#{pattern}]"
97
+
98
+ size = words.size
99
+ if size > 62
100
+ pattern = "(#{pattern}|"
101
+ c = Packr.encode62(size)[0].chr
102
+ if c > "9"
103
+ pattern += "[\\\\d"
104
+ if c >= "a"
105
+ pattern += "a"
106
+ if c >= "z"
107
+ pattern += "-z"
108
+ if c >= "A"
109
+ pattern += "A"
110
+ pattern += "-#{c}" if c > "A"
111
+ end
112
+ elsif c == "b"
113
+ pattern += "-#{c}"
114
+ end
115
+ end
116
+ pattern += "]"
117
+ elsif c == "9"
118
+ pattern += "\\\\d"
119
+ elsif c == "2"
120
+ pattern += "[12]"
121
+ elsif c == "1"
122
+ pattern += "1"
123
+ else
124
+ pattern += "[1-#{c}]"
125
+ end
126
+
127
+ pattern += "\\\\w)"
128
+ end
129
+ pattern
130
+ end
131
+
132
+ def get_encoder(words)
133
+ c = words.size
134
+ self.class.const_get("ENCODE#{c > 10 ? (c > 36 ? 62 : 36) : 10}")
135
+ end
136
+
137
+ def get_key_words(words)
138
+ words.map { |word, key| word.to_s }.join("|").gsub(/\|+$/, "")
139
+ end
140
+
141
+ def get_pattern(words)
142
+ words = words.map { |word, key| word.to_s }.join("|").gsub(/\|{2,}/, "|").gsub(/^\|+|\|+$/, "")
143
+ words = "\\x0" if words == ""
144
+ string = "\\b(#{words})\\b"
145
+ %r{#{string}}
146
+ end
147
+
148
+ end
149
+ end
150
+
@@ -0,0 +1,147 @@
1
+ class Packr
2
+ # A Map that is more array-like (accessible by index).
3
+ class Collection < Map
4
+
5
+ attr_reader :values
6
+ attr_writer :keys
7
+
8
+ def initialize(values = nil)
9
+ @keys = []
10
+ super(values)
11
+ end
12
+
13
+ def add(key, item = nil)
14
+ # Duplicates not allowed using add().
15
+ # But you can still overwrite entries using put().
16
+ return if has?(key)
17
+ put(key, item)
18
+ end
19
+
20
+ def clear
21
+ super
22
+ @keys.clear
23
+ end
24
+
25
+ def copy
26
+ copy = super
27
+ copy.keys = @keys.dup
28
+ copy
29
+ end
30
+
31
+ def each
32
+ @keys.each { |key| yield(get(key), key) }
33
+ end
34
+
35
+ def get_at(index)
36
+ index += @keys.length if index < 0 # starting from the end
37
+ key = @keys[index]
38
+ key.nil? ? nil : @values[key.to_s]
39
+ end
40
+
41
+ def get_keys
42
+ @keys.dup
43
+ end
44
+
45
+ def index_of(key)
46
+ @keys.index(key.to_s)
47
+ end
48
+
49
+ def insert_at(index, key, item = nil)
50
+ return if @keys[index].nil?
51
+ @keys.insert(index, key.to_s)
52
+ @values[key.to_s] = nil # placeholder
53
+ put(key, item)
54
+ end
55
+
56
+ def item(key_or_index)
57
+ __send__(key_or_index.is_a?(Numeric) ? :get_at : :get, key_or_index)
58
+ end
59
+
60
+ def map
61
+ @keys.map { |key| yield(get(key), key) }
62
+ end
63
+
64
+ def merge(*args)
65
+ args.each do |values|
66
+ values.is_a?(Collection) ?
67
+ values.each { |item, key| put(key, item) } :
68
+ super(values)
69
+ end
70
+ self
71
+ end
72
+
73
+ # TODO update this method
74
+ def put(key, item = nil)
75
+ item ||= key
76
+ @keys << key.to_s unless has?(key.to_s)
77
+ begin; klass = self.class::Item; rescue; end
78
+ item = self.class.create(key, item) if klass and !item.is_a?(klass)
79
+ @values[key.to_s] = item
80
+ self
81
+ end
82
+
83
+ def put_at(index, item = nil)
84
+ key = @keys[index]
85
+ return if key.nil?
86
+ put(key, item)
87
+ end
88
+
89
+ def remove(key)
90
+ if has?(key)
91
+ @keys.delete(key.to_s)
92
+ @values.delete(key.to_s)
93
+ end
94
+ end
95
+
96
+ def remove_at(index)
97
+ key = @keys.delete_at(index)
98
+ @values.delete(key)
99
+ end
100
+
101
+ def reverse!
102
+ @keys.reverse!
103
+ self
104
+ end
105
+
106
+ def size
107
+ @keys.length
108
+ end
109
+
110
+ def slice(start, fin)
111
+ sliced = copy
112
+ if start
113
+ keys, removed = @keys, @keys
114
+ sliced.keys = @keys[start...fin]
115
+ if sliced.size.nonzero?
116
+ removed = removed[0...start]
117
+ removed = removed + keys[fin..-1] if fin
118
+ end
119
+ removed.each do |remov|
120
+ sliced.values.delete(remov)
121
+ end
122
+ end
123
+ sliced
124
+ end
125
+
126
+ def sort!(&compare)
127
+ if block_given?
128
+ @keys.sort! do |key1, key2|
129
+ compare.call(@values[key1], @values[key2], key1, key2)
130
+ end
131
+ else
132
+ @keys.sort!
133
+ end
134
+ self
135
+ end
136
+
137
+ def to_s
138
+ "(#{@keys.join(',')})"
139
+ end
140
+
141
+ def self.create(key, item)
142
+ begin; klass = self::Item; rescue; end
143
+ klass ? klass.new(key, item) : item
144
+ end
145
+
146
+ end
147
+ end
@@ -0,0 +1,8 @@
1
+ class Packr
2
+
3
+ IGNORE = RegexpGroup::IGNORE
4
+ REMOVE = ""
5
+ SPACE = " "
6
+
7
+ end
8
+
@@ -0,0 +1,35 @@
1
+ class Packr
2
+ class Encoder
3
+
4
+ def initialize(pattern = nil, encoder = nil, ignore = nil)
5
+ @parser = Parser.new(ignore)
6
+ @parser.put(pattern, "") if pattern
7
+ @encoder = encoder
8
+ end
9
+
10
+ def search(script)
11
+ words = Words.new
12
+ @parser.put_at(-1, lambda { |word, *args|
13
+ words.add(word)
14
+ })
15
+ @parser.exec(script)
16
+ words
17
+ end
18
+
19
+ def encode(script)
20
+ words = search(script)
21
+ words.sort!
22
+ index = 0
23
+ words.each do |word, key|
24
+ word.encoded = @encoder.call(index)
25
+ index += 1
26
+ end
27
+ @parser.put_at(-1, lambda { |word, *args|
28
+ words.get(word).encoded
29
+ })
30
+ @parser.exec(script)
31
+ end
32
+
33
+ end
34
+ end
35
+
@@ -0,0 +1,66 @@
1
+ class Packr
2
+ # This is effectively a wrapper for Hash instances - we're including it
3
+ # to maintain similarity with the JavaScript version for easier maintainance.
4
+ class Map
5
+
6
+ def initialize(values = nil)
7
+ @values = {}
8
+ merge(values) unless values.nil?
9
+ end
10
+
11
+ def clear
12
+ @values.clear
13
+ end
14
+
15
+ def copy
16
+ self.class.new(@values)
17
+ end
18
+
19
+ def each
20
+ @values.each { |key, value| yield(value, key) }
21
+ end
22
+
23
+ def get(key)
24
+ @values[key.to_s]
25
+ end
26
+
27
+ def get_keys
28
+ @values.keys
29
+ end
30
+
31
+ def get_values
32
+ @values.values
33
+ end
34
+
35
+ def has?(key)
36
+ @values.has_key?(key.to_s)
37
+ end
38
+
39
+ def merge(*args)
40
+ args.each do |values|
41
+ values = values.get_values if values.is_a?(Map)
42
+ values.each { |key, value| put(key, value) }
43
+ end
44
+ self
45
+ end
46
+
47
+ def remove(key)
48
+ @values.delete(key.to_s)
49
+ end
50
+
51
+ def put(key, value = nil)
52
+ value ||= key
53
+ # Create the new entry (or overwrite the old entry).
54
+ @values[key.to_s] = value
55
+ end
56
+
57
+ def size
58
+ @values.length
59
+ end
60
+
61
+ def union(*values)
62
+ copy.merge(*values)
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,80 @@
1
+ class Packr
2
+ class Minifier
3
+
4
+ def self.conditional_comments
5
+ @@conditional_comments
6
+ end
7
+
8
+ def initialize
9
+ @concat = CONCAT.union(DATA)
10
+
11
+ def @concat.exec(script)
12
+ parsed = super(script)
13
+ while parsed != script
14
+ script = parsed
15
+ parsed = super(script)
16
+ end
17
+ parsed
18
+ end
19
+
20
+ @comments = DATA.union(COMMENTS)
21
+ @clean = DATA.union(CLEAN)
22
+ @whitespace = DATA.union(WHITESPACE)
23
+
24
+ @@conditional_comments = @comments.copy
25
+ @@conditional_comments.put_at(-1, " \\3")
26
+ @whitespace.remove_at(2) # conditional comments
27
+ @comments.remove_at(2)
28
+ end
29
+
30
+ def minify(script)
31
+ # packing with no additional options
32
+ script += "\n"
33
+ script = script.gsub(CONTINUE, "")
34
+ script = @comments.exec(script)
35
+ script = @clean.exec(script)
36
+ script = @whitespace.exec(script)
37
+ script = @concat.exec(script)
38
+ script
39
+ end
40
+
41
+ CONTINUE = /\\\r?\n/
42
+
43
+ CLEAN = Parser.new.
44
+ put("\\(\\s*([^;)]*)\\s*;\\s*([^;)]*)\\s*;\\s*([^;)]*)\\)", "(\\1;\\2;\\3)"). # for (;;) loops
45
+ put("throw[^};]+[};]", IGNORE). # a safari 1.3 bug
46
+ put(";+\\s*([};])", "\\1")
47
+
48
+ COMMENTS = Parser.new.
49
+ put(";;;[^\\n]*\\n", REMOVE).
50
+ put("(COMMENT1)\\n\\s*(REGEXP)?", "\n\\3").
51
+ put("(COMMENT2)\\s*(REGEXP)?", lambda do |*args|
52
+ match, comment, b, regexp = args[0..3]
53
+ if comment =~ /^\/\*@/ and comment =~ /@\*\/$/
54
+ # comments = Minifier.conditional_comments.exec(comment)
55
+ else
56
+ comment = ""
57
+ end
58
+ comment + " " + (regexp || "")
59
+ end)
60
+
61
+ CONCAT = Parser.new.
62
+ put("(STRING1)\\+(STRING1)", lambda { |*args| args[1][0...-1] + args[3][1..-1] }).
63
+ put("(STRING2)\\+(STRING2)", lambda { |*args| args[1][0...-1] + args[3][1..-1] })
64
+
65
+ WHITESPACE = Parser.new.
66
+ put("/\\/\\/@[^\\n]*\\n", IGNORE).
67
+ put("@\\s+\\b", "@ "). # protect conditional comments
68
+ put("\\b\\s+@", " @").
69
+ put("(\\d)\\s+(\\.\\s*[a-z\\$_\\[(])", "\\1 \\2"). # http://dean.edwards.name/weblog/2007/04/packer3/#comment84066
70
+ put("([+-])\\s+([+-])", "\\1 \\2"). # c = a++ +b;
71
+ put("\\b\\s+\\$\\s+\\b", " $ "). # var $ in
72
+ put("\\$\\s+\\b", "$ "). # object$ in
73
+ put("\\b\\s+\\$", " $"). # return $object
74
+ # put("\\b\\s+#", " #"). # CSS
75
+ put("\\b\\s+\\b", SPACE).
76
+ put("\\s+", REMOVE)
77
+
78
+ end
79
+ end
80
+