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,16 @@
1
+ === 3.1.0 / 2009-02-22
2
+
3
+ * Project is now a gem, not a Rails plugin
4
+ * Improved local variable compression
5
+ * Private field obfuscation
6
+ * Changed variable protection, protected variables must be supplied
7
+ as options to Packr#pack.
8
+
9
+ === 1.0.2 / 2007-12-13
10
+
11
+ * Added variable protection, protecting $super for use with Prototype
12
+
13
+ === 1.0.0 / 2007-12-04
14
+
15
+ * Initial release, compatible with Packer 3.0
16
+
@@ -0,0 +1,19 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/packr
6
+ lib/packr.rb
7
+ lib/string.rb
8
+ lib/packr/map.rb
9
+ lib/packr/collection.rb
10
+ lib/packr/regexp_group.rb
11
+ lib/packr/constants.rb
12
+ lib/packr/encoder.rb
13
+ lib/packr/minifier.rb
14
+ lib/packr/parser.rb
15
+ lib/packr/privates.rb
16
+ lib/packr/shrinker.rb
17
+ lib/packr/words.rb
18
+ lib/packr/base62.rb
19
+ test/test_packr.rb
@@ -0,0 +1,112 @@
1
+ = PackR
2
+
3
+ * http://github.com/jcoglan/packr
4
+ * http://dean.edwards.name/packer/
5
+ * http://base2.googlecode.com
6
+
7
+ == Description
8
+
9
+ PackR is a Ruby version of Dean Edwards' JavaScript compressor.
10
+
11
+ == Features
12
+
13
+ * Whitespace and comment removal
14
+ * Compression of local variable names
15
+ * Compression and obfuscation of 'private' (_underscored) identifiers
16
+ * Base-62 encoding
17
+
18
+ == Synopsis
19
+
20
+ To call from within a Ruby program:
21
+
22
+ require 'rubygems'
23
+ require 'packr'
24
+
25
+ code = File.read('my_script.js')
26
+ compressed = Packr.pack(code)
27
+ File.open('my_script.min.js', 'wb') { |f| f.write(compressed) }
28
+
29
+ This method takes a number of options to control compression, for example:
30
+
31
+ compressed = Packr.pack(code, :shrink_vars => true, :base62 => true)
32
+
33
+ The full list of available options is:
34
+
35
+ * <tt>:shrink_vars</tt> -- set to +true+ to compress local variable names
36
+ * <tt>:private</tt> -- set to +true+ to obfuscate 'private' identifiers, i.e.
37
+ names beginning with a single underscore
38
+ * <tt>:base62</tt> -- encode the program using base 62
39
+ * <tt>:protect</tt> -- an array of variable names to protect from compression, e.g.
40
+
41
+ compressed = Packr.pack(code, :shrink_vars => true,
42
+ :protect => %w[$super self])
43
+
44
+ To call from the command line (use <tt>packr --help</tt> to see available options):
45
+
46
+ packr my_script.js > my_script.min.js
47
+
48
+ == Notes
49
+
50
+ This program is not a JavaScript parser, and rewrites your files using regular
51
+ expressions. Be sure to include semicolons and braces everywhere they are required
52
+ so that your program will work correctly when packed down to a single line.
53
+
54
+ By far the most efficient way to serve JavaScript over the web is to use PackR
55
+ with the --shrink-vars flag, combined with gzip compression. If you don't have access
56
+ to your server config to set up mod_deflate, you can generate gzip files using
57
+ (on Unix-like systems):
58
+
59
+ packr -s my-file.js | gzip > my-file.js.gz
60
+
61
+ You can then get Apache to serve the files by putting this in your .htaccess file:
62
+
63
+ AddEncoding gzip .gz
64
+ RewriteCond %{HTTP:Accept-encoding} gzip
65
+ RewriteCond %{HTTP_USER_AGENT} !Safari
66
+ RewriteCond %{REQUEST_FILENAME}.gz -f
67
+ RewriteRule ^(.*)$ $1.gz [QSA,L]
68
+
69
+ If you really cannot serve gzip files, use the --base62 option to further compress
70
+ your code. This mode is at its best when compressing large files with many repeated
71
+ tokens.
72
+
73
+ The --private option can be used to stop other programs calling private methods
74
+ in your code by renaming anything beginning with a single underscore. Beware that
75
+ you should not use this if the generated file contains 'private' methods that need
76
+ to be accessible by other files. Also know that all the files that access any
77
+ particular private method must be compressed together so they all get the same
78
+ rewritten name for the private method.
79
+
80
+ == Requirements
81
+
82
+ * Rubygems
83
+ * Oyster (installed automatically)
84
+
85
+ == Installation
86
+
87
+ sudo gem install packr -y
88
+
89
+ == License
90
+
91
+ (The MIT License)
92
+
93
+ Copyright (c) 2004-2009 Dean Edwards, James Coglan
94
+
95
+ Permission is hereby granted, free of charge, to any person obtaining
96
+ a copy of this software and associated documentation files (the
97
+ 'Software'), to deal in the Software without restriction, including
98
+ without limitation the rights to use, copy, modify, merge, publish,
99
+ distribute, sublicense, and/or sell copies of the Software, and to
100
+ permit persons to whom the Software is furnished to do so, subject to
101
+ the following conditions:
102
+
103
+ The above copyright notice and this permission notice shall be
104
+ included in all copies or substantial portions of the Software.
105
+
106
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
107
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
108
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
109
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
110
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
111
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
112
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/packr.rb'
6
+
7
+ Hoe.new('packr', Packr::VERSION) do |p|
8
+ p.developer('James Coglan', 'jcoglan@googlemail.com')
9
+ p.extra_deps = %w(oyster)
10
+ end
11
+
12
+ # vim: syntax=Ruby
@@ -0,0 +1,91 @@
1
+ require 'rubygems'
2
+ require 'oyster'
3
+ require 'packr'
4
+
5
+ spec = Oyster.spec do
6
+ name "packr -- JavaScript code compressor based on Dean Edwards' Packer"
7
+
8
+ synopsis <<-EOS
9
+ packr [OPTIONS] INPUT_FILES > OUTPUT_FILE
10
+ cat INPUT_FILES | packr [OPTIONS] > OUTPUT_FILE
11
+ EOS
12
+
13
+ description <<-EOS
14
+ PackR is a program for compressing JavaScript programs. It can remove whitespace
15
+ and comments, compress local variable names, compress/obfuscate private identifiers,
16
+ and encode the program in base-62.
17
+
18
+ When invoked from the command line, it concatenates all the code in INPUT_FILES (or
19
+ from standard input) and compresses the code using the given options, printing the
20
+ result to standard output. You can pipe this output into another file to save it.
21
+ EOS
22
+
23
+ flag :'shrink-vars', :default => true,
24
+ :desc => 'Shrink local variable names inside functions'
25
+
26
+ flag :private, :default => false,
27
+ :desc => 'Obfuscate private identifiers, i.e. names beginning with a single underscore'
28
+
29
+ flag :base62, :default => false,
30
+ :desc => 'Encode the program using base 62'
31
+
32
+ array :protect, :default => [],
33
+ :desc => 'List of variable names to protect from compression when using --shrink-vars'
34
+
35
+ notes <<-EOS
36
+ This program is not a JavaScript parser, and rewrites your files using regular
37
+ expressions. Be sure to include semicolons and braces everywhere they are required
38
+ so that your program will work correctly when packed down to a single line.
39
+
40
+ By far the most efficient way to serve JavaScript over the web is to use PackR
41
+ with the --shrink-vars flag, combined with gzip compression. If you don't have access
42
+ to your server config to set up mod_deflate, you can generate gzip files using
43
+ (on Unix-like systems):
44
+
45
+ packr -s my-file.js | gzip > my-file.js.gz
46
+
47
+ You can then get Apache to serve the files by putting this in your .htaccess file:
48
+
49
+ AddEncoding gzip .gz
50
+ RewriteCond %{HTTP:Accept-encoding} gzip
51
+ RewriteCond %{HTTP_USER_AGENT} !Safari
52
+ RewriteCond %{REQUEST_FILENAME}.gz -f
53
+ RewriteRule ^(.*)$ $1.gz [QSA,L]
54
+
55
+ If you really cannot serve gzip files, use the --base62 option to further compress
56
+ your code. This mode is at its best when compressing large files with many repeated
57
+ tokens.
58
+
59
+ The --private option can be used to stop other programs calling private methods
60
+ in your code by renaming anything beginning with a single underscore. Beware that
61
+ you should not use this if the generated file contains 'private' methods that need
62
+ to be accessible by other files. Also know that all the files that access any
63
+ particular private method must be compressed together so they all get the same
64
+ rewritten name for the private method.
65
+ EOS
66
+
67
+ author <<-EOS
68
+ Original JavaScript version by Dean Edwards, Ruby port by James Coglan <jcoglan@googlemail.com>
69
+ EOS
70
+
71
+ copyright <<-EOS
72
+ Copyright (c) 2004-2008 Dean Edwards, James Coglan. This program is free software,
73
+ distributed under the MIT license.
74
+ EOS
75
+ end
76
+
77
+ begin; opts = spec.parse
78
+ rescue Oyster::HelpRendered; exit
79
+ end
80
+
81
+ inputs = opts[:unclaimed]
82
+ code = inputs.empty? ?
83
+ $stdin.read :
84
+ inputs.map { |f| File.read(f) }.join("\n")
85
+
86
+ $stdout.puts Packr.pack(code,
87
+ :shrink_vars => !!opts[:'shrink-vars'],
88
+ :protect => opts[:protect],
89
+ :private => !!opts[:private],
90
+ :base62 => !!opts[:base62])
91
+
@@ -1,240 +1,68 @@
1
- # PackR -- a Ruby port of Packer by Dean Edwards
2
- # Packer version 3.0 (final) - copyright 2004-2007, Dean Edwards
3
- # http://www.opensource.org/licenses/mit-license
4
-
5
- require File.dirname(__FILE__) + '/string'
6
- require File.dirname(__FILE__) + '/packr/regexp_group'
7
- require File.dirname(__FILE__) + '/packr/words'
8
-
9
- class Packr
10
-
11
- PROTECTED_NAMES = %w($super)
12
-
13
- class << self
14
- def protect_vars(*args)
15
- @packr ||= self.new
16
- @packr.protect_vars(*args)
17
- end
18
-
19
- def minify(script)
20
- @packr ||= self.new
21
- @packr.minify(script)
22
- end
23
-
24
- def pack(script, options = {})
25
- @packr ||= self.new
26
- @packr.pack(script, options)
27
- end
28
-
29
- def pack_file(path, options = {})
30
- @packr ||= self.new
31
- @packr.pack_file(path, options)
32
- end
33
- end
34
-
35
- IGNORE = RegexpGroup::IGNORE
36
- REMOVE = ""
37
- SPACE = " "
38
- WORDS = /\w+/
39
-
40
- CONTINUE = /\\\r?\n/
41
-
42
- ENCODE10 = "String"
43
- ENCODE36 = "function(c){return c.toString(a)}"
44
- ENCODE62 = "function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}"
45
-
46
- UNPACK = lambda do |p,a,c,k,e,r|
47
- "eval(function(p,a,c,k,e,r){e=#{e};if(!''.replace(/^/,String)){while(c--)r[#{r}]=k[c]" +
48
- "||#{r};k=[function(e){return r[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p." +
49
- "replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('#{p}',#{a},#{c},'#{k}'.split('|'),0,{}))"
50
- end
51
-
52
- CLEAN = RegexpGroup.new(
53
- "\\(\\s*;\\s*;\\s*\\)" => "(;;)", # for (;;) loops
54
- "throw[^};]+[};]" => IGNORE, # a safari 1.3 bug
55
- ";+\\s*([};])" => "\\1"
56
- )
57
-
58
- DATA = RegexpGroup.new(
59
- # strings
60
- "STRING1" => IGNORE,
61
- 'STRING2' => IGNORE,
62
- "CONDITIONAL" => IGNORE, # conditional comments
63
- "(COMMENT1)\\n\\s*(REGEXP)?" => "\n\\3",
64
- "(COMMENT2)\\s*(REGEXP)?" => " \\3",
65
- "([\\[(\\^=,{}:;&|!*?])\\s*(REGEXP)" => "\\1\\2"
66
- )
67
-
68
- JAVASCRIPT = RegexpGroup.new(
69
- :COMMENT1 => /(\/\/|;;;)[^\n]*/.source,
70
- :COMMENT2 => /\/\*[^*]*\*+([^\/][^*]*\*+)*\//.source,
71
- :CONDITIONAL => /\/\*@|@\*\/|\/\/@[^\n]*\n/.source,
72
- :REGEXP => /\/(\\[\/\\]|[^*\/])(\\.|[^\/\n\\])*\/[gim]*/.source,
73
- :STRING1 => /'(\\.|[^'\\])*'/.source,
74
- :STRING2 => /"(\\.|[^"\\])*"/.source
75
- )
76
-
77
- WHITESPACE = RegexpGroup.new(
78
- "(\\d)\\s+(\\.\\s*[a-z\\$_\\[(])" => "\\1 \\2", # http://dean.edwards.name/weblog/2007/04/packer3/#comment84066
79
- "([+-])\\s+([+-])" => "\\1 \\2", # c = a++ +b;
80
- "\\b\\s+\\$\\s+\\b" => " $ ", # var $ in
81
- "\\$\\s+\\b" => "$ ", # object$ in
82
- "\\b\\s+\\$" => " $", # return $object
83
- "\\b\\s+\\b" => SPACE,
84
- "\\s+" => REMOVE
85
- )
86
-
87
- def initialize
88
- @data = {}
89
- DATA.values.each { |item| @data[JAVASCRIPT.exec(item.expression)] = item.replacement }
90
- @data = RegexpGroup.new(@data)
91
- @whitespace = @data.union(WHITESPACE)
92
- @clean = @data.union(CLEAN)
93
- @protected_names = PROTECTED_NAMES
94
- end
95
-
96
- def protect_vars(*args)
97
- args = args.map { |arg| arg.to_s.strip }.select { |arg| arg =~ /^[a-z\_\$][a-z0-9\_\$]*$/i }
98
- @protected_names = (@protected_names + args).uniq
99
- end
100
-
101
- def minify(script)
102
- script = script.gsub(CONTINUE, "")
103
- script = @data.exec(script)
104
- script = @whitespace.exec(script)
105
- script = @clean.exec(script)
106
- script
107
- end
108
-
109
- def pack(script, options = {})
110
- script = minify(script + "\n")
111
- script = shrink_variables(script) if options[:shrink_vars]
112
- script = base62_encode(script) if options[:base62]
113
- script
114
- end
115
-
116
- def pack_file(path, options = {})
117
- path = path.gsub(Regexp.new("^((#{RAILS_ROOT.gsub(/\./, "\\.")})?/)?"), RAILS_ROOT + '/')
118
- script = File.read(path)
119
- script = pack(script, options)
120
- File.open(path, 'wb') { |f| f.write(script) }
121
- end
122
-
123
- private
124
-
125
- def base62_encode(script)
126
- words = Words.new(script)
127
- encode = lambda { |word| words.get(word).encoded }
128
-
129
- # build the packed script
130
-
131
- p = escape(script.gsub(Words::WORDS, &encode))
132
- a = [[words.size, 2].max, 62].min
133
- c = words.size
134
- k = words.to_s
135
- e = self.class.const_get("ENCODE#{a > 10 ? (a > 36 ? 62 : 36) : 10}")
136
- r = a > 10 ? "e(c)" : "c"
137
-
138
- # the whole thing
139
- UNPACK.call(p,a,c,k,e,r)
140
- end
141
-
142
- def escape(script)
143
- # single quotes wrap the final string so escape them
144
- # also escape new lines required by conditional comments
145
- script.gsub(/([\\'])/) { |match| "\\#{$1}" }.gsub(/[\r\n]+/, "\\n")
146
- end
147
-
148
- def shrink_variables(script)
149
- data = [] # encoded strings and regular expressions
150
- regexp= /^[^'"]\//
151
- store = lambda do |string|
152
- replacement = "##{data.length}"
153
- if string =~ regexp
154
- replacement = string[0].chr + replacement
155
- string = string[1..-1]
156
- end
157
- data << string
158
- replacement
159
- end
160
-
161
- # Base52 encoding (a-Z)
162
- encode52 = lambda do |c|
163
- (c < 52 ? '' : encode52.call((c.to_f / 52).to_i) ) +
164
- ((c = c % 52) > 25 ? (c + 39).chr : (c + 97).chr)
165
- end
166
-
167
- # identify blocks, particularly identify function blocks (which define scope)
168
- __block = /(function\s*[\w$]*\s*\(\s*([^\)]*)\s*\)\s*)?(\{([^{}]*)\})/
169
- __var = /var\s+/
170
- __var_name = /var\s+[\w$]+/
171
- __comma = /\s*,\s*/
172
- blocks = [] # store program blocks (anything between braces {})
173
-
174
- # decoder for program blocks
175
- encoded = /~(\d+)~/
176
- decode = lambda do |script|
177
- script = script.gsub(encoded) { |match| blocks[$1.to_i] } while script =~ encoded
178
- script
179
- end
180
-
181
- # encoder for program blocks
182
- encode = lambda do |match|
183
- block, func, args = match, $1, $2
184
- if func # the block is a function block
185
-
186
- # decode the function block (THIS IS THE IMPORTANT BIT)
187
- # We are retrieving all sub-blocks and will re-parse them in light
188
- # of newly shrunk variables
189
- block = decode.call(block)
190
-
191
- # create the list of variable and argument names
192
- vars = block.scan(__var_name).join(",").gsub(__var, "")
193
- ids = (args.split(__comma) + vars.split(__comma)).uniq
194
-
195
- #process each identifier
196
- count = 0
197
- ids.each do |id|
198
- id = id.strip
199
- if id and id.length > 1 and !@protected_names.include?(id) # > 1 char
200
- id = id.rescape
201
- # find the next free short name (check everything in the current scope)
202
- short_id = encode52.call(count)
203
- while block =~ Regexp.new("[^\\w$.]#{short_id}[^\\w$:]")
204
- count += 1
205
- short_id = encode52.call(count)
206
- end
207
- # replace the long name with the short name
208
- reg = Regexp.new("([^\\w$.])#{id}([^\\w$:])")
209
- block = block.gsub(reg, "\\1#{short_id}\\2") while block =~ reg
210
- reg = Regexp.new("([^{,\\w$.])#{id}:")
211
- block = block.gsub(reg, "\\1#{short_id}:")
212
- end
213
- end
214
- end
215
- replacement = "~#{blocks.length}~"
216
- blocks << block
217
- replacement
218
- end
219
-
220
- # encode strings and regular expressions
221
- script = @data.exec(script, &store)
222
-
223
- # remove closures (this is for base2 namespaces only)
224
- script = script.gsub(/new function\(_\)\s*\{/, "{;#;")
225
-
226
- # encode blocks, as we encode we replace variable and argument names
227
- script = script.gsub(__block, &encode) while script =~ __block
228
-
229
- # put the blocks back
230
- script = decode.call(script)
231
-
232
- # put back the closure (for base2 namespaces only)
233
- script = script.gsub(/\{;#;/, "new function(_){")
234
-
235
- # put strings and regular expressions back
236
- script = script.gsub(/#(\d+)/) { |match| data[$1.to_i] }
237
-
238
- script
239
- end
240
- end
1
+ # PackR -- a Ruby port of Packer by Dean Edwards
2
+ # Packer version 3.1 copyright 2004-2009, Dean Edwards
3
+ # http://www.opensource.org/licenses/mit-license
4
+
5
+ [ '/string',
6
+ '/packr/map',
7
+ '/packr/collection',
8
+ '/packr/regexp_group',
9
+ '/packr/constants',
10
+ '/packr/encoder',
11
+ '/packr/parser',
12
+ '/packr/minifier',
13
+ '/packr/privates',
14
+ '/packr/shrinker',
15
+ '/packr/words',
16
+ '/packr/base62'
17
+ ].each do |path|
18
+ require File.dirname(__FILE__) + path
19
+ end
20
+
21
+ class Packr
22
+
23
+ VERSION = '3.1.0'
24
+
25
+ DATA = Parser.new.
26
+ put("STRING1", IGNORE).
27
+ put('STRING2', IGNORE).
28
+ put("CONDITIONAL", IGNORE). # conditional comments
29
+ put("(OPERATOR)\\s*(REGEXP)", "\\1\\2")
30
+
31
+ def self.encode62(c)
32
+ (c < 62 ? '' : encode62((c / 62.0).to_i)) +
33
+ ((c = c % 62) > 35 ? (c+29).chr : c.to_s(36))
34
+ end
35
+
36
+ def self.encode52(c)
37
+ # Base52 encoding (a-Z)
38
+ encode = lambda do |d|
39
+ (d < 52 ? '' : encode.call((d / 52.0).to_i)) +
40
+ ((d = d % 52) > 25 ? (d + 39).chr : (d + 97).chr)
41
+ end
42
+ encoded = encode.call(c.to_i)
43
+ encoded = encoded[1..-1] + '0' if encoded =~ /^(do|if|in)$/
44
+ encoded
45
+ end
46
+
47
+ def self.pack(script, options = {})
48
+ @packr ||= self.new
49
+ @packr.pack(script, options)
50
+ end
51
+
52
+ def initialize
53
+ @minifier = Minifier.new
54
+ @shrinker = Shrinker.new
55
+ @privates = Privates.new
56
+ @base62 = Base62.new
57
+ end
58
+
59
+ def pack(script, options = {})
60
+ script = @minifier.minify(script)
61
+ script = @shrinker.shrink(script, options[:protect]) if options[:shrink_vars]
62
+ script = @privates.encode(script) if options[:private]
63
+ script = @base62.encode(script) if options[:base62]
64
+ script
65
+ end
66
+
67
+ end
68
+