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,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
+