grimen-packr 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +20 -0
- data/README.rdoc +108 -0
- data/bin/packr +97 -0
- data/lib/packr.rb +55 -0
- data/lib/packr/base62.rb +150 -0
- data/lib/packr/collection.rb +147 -0
- data/lib/packr/encoder.rb +35 -0
- data/lib/packr/engine.rb +20 -0
- data/lib/packr/map.rb +66 -0
- data/lib/packr/minifier.rb +80 -0
- data/lib/packr/parser.rb +21 -0
- data/lib/packr/privates.rb +19 -0
- data/lib/packr/regexp_group.rb +122 -0
- data/lib/packr/shrinker.rb +123 -0
- data/lib/packr/words.rb +39 -0
- data/test/assets/packed/controls.js +1 -0
- data/test/assets/packed/domready.js +1 -0
- data/test/assets/packed/dragdrop.js +1 -0
- data/test/assets/packed/effects.js +1 -0
- data/test/assets/packed/prototype.js +1 -0
- data/test/assets/packed/prototype_shrunk.js +1 -0
- data/test/assets/packed/selector.js +1 -0
- data/test/assets/src/controls.js +833 -0
- data/test/assets/src/domready.js +36 -0
- data/test/assets/src/dragdrop.js +942 -0
- data/test/assets/src/effects.js +1088 -0
- data/test/assets/src/prototype.js +2515 -0
- data/test/assets/src/selector.js +666 -0
- data/test/assets/test/controls.js +1 -0
- data/test/assets/test/domready.js +1 -0
- data/test/assets/test/dragdrop.js +1 -0
- data/test/assets/test/effects.js +1 -0
- data/test/assets/test/prototype.js +1 -0
- data/test/assets/test/prototype_shrunk.js +1 -0
- data/test/assets/test/selector.js +1 -0
- data/test/test_packr.rb +156 -0
- metadata +139 -0
data/History.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
=== 3.1.1 / 2011-07-19
|
2
|
+
|
3
|
+
* Remove dependency on Hoe
|
4
|
+
|
5
|
+
=== 3.1.0 / 2009-02-22
|
6
|
+
|
7
|
+
* Project is now a gem, not a Rails plugin
|
8
|
+
* Improved local variable compression
|
9
|
+
* Private field obfuscation
|
10
|
+
* Changed variable protection, protected variables must be supplied
|
11
|
+
as options to Packr#pack.
|
12
|
+
|
13
|
+
=== 1.0.2 / 2007-12-13
|
14
|
+
|
15
|
+
* Added variable protection, protecting $super for use with Prototype
|
16
|
+
|
17
|
+
=== 1.0.0 / 2007-12-04
|
18
|
+
|
19
|
+
* Initial release, compatible with Packer 3.0
|
20
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
= PackR
|
2
|
+
|
3
|
+
* http://github.com/jcoglan/packr
|
4
|
+
* http://dean.edwards.name/packer/
|
5
|
+
* http://base2.googlecode.com
|
6
|
+
|
7
|
+
|
8
|
+
== Description
|
9
|
+
|
10
|
+
PackR is a Ruby version of Dean Edwards' JavaScript compressor.
|
11
|
+
|
12
|
+
|
13
|
+
== Features
|
14
|
+
|
15
|
+
* Whitespace and comment removal
|
16
|
+
* Compression of local variable names
|
17
|
+
* Compression and obfuscation of 'private' (_underscored) identifiers
|
18
|
+
* Base-62 encoding
|
19
|
+
|
20
|
+
|
21
|
+
== Synopsis
|
22
|
+
|
23
|
+
To call from within a Ruby program:
|
24
|
+
|
25
|
+
require 'packr'
|
26
|
+
|
27
|
+
code = File.read('my_script.js')
|
28
|
+
compressed = Packr.pack(code)
|
29
|
+
File.open('my_script.min.js', 'wb') { |f| f.write(compressed) }
|
30
|
+
|
31
|
+
This method takes a number of options to control compression, for example:
|
32
|
+
|
33
|
+
compressed = Packr.pack(code, :shrink_vars => true, :base62 => true)
|
34
|
+
|
35
|
+
The full list of available options is:
|
36
|
+
|
37
|
+
* <tt>:shrink_vars</tt> -- set to +true+ to compress local variable names
|
38
|
+
* <tt>:private</tt> -- set to +true+ to obfuscate 'private' identifiers, i.e.
|
39
|
+
names beginning with a single underscore
|
40
|
+
* <tt>:base62</tt> -- encode the program using base 62
|
41
|
+
* <tt>:protect</tt> -- an array of variable names to protect from compression, e.g.
|
42
|
+
|
43
|
+
compressed = Packr.pack(code, :shrink_vars => true,
|
44
|
+
:protect => %w[$super self])
|
45
|
+
|
46
|
+
To call from the command line (use <tt>packr --help</tt> to see available
|
47
|
+
options):
|
48
|
+
|
49
|
+
packr my_script.js > my_script.min.js
|
50
|
+
|
51
|
+
|
52
|
+
== Notes
|
53
|
+
|
54
|
+
This program is not a JavaScript parser, and rewrites your files using regular
|
55
|
+
expressions. Be sure to include semicolons and braces everywhere they are
|
56
|
+
required so that your program will work correctly when packed down to a single
|
57
|
+
line.
|
58
|
+
|
59
|
+
By far the most efficient way to serve JavaScript over the web is to use PackR
|
60
|
+
with the --shrink-vars flag, combined with gzip compression. If you don't have
|
61
|
+
access to your server config to set up mod_deflate, you can generate gzip files
|
62
|
+
using (on Unix-like systems):
|
63
|
+
|
64
|
+
packr -s my-file.js | gzip > my-file.js.gz
|
65
|
+
|
66
|
+
You can then get Apache to serve the files by putting this in your .htaccess
|
67
|
+
file:
|
68
|
+
|
69
|
+
AddEncoding gzip .gz
|
70
|
+
RewriteCond %{HTTP:Accept-encoding} gzip
|
71
|
+
RewriteCond %{HTTP_USER_AGENT} !Safari
|
72
|
+
RewriteCond %{REQUEST_FILENAME}.gz -f
|
73
|
+
RewriteRule ^(.*)$ $1.gz [QSA,L]
|
74
|
+
|
75
|
+
If you really cannot serve gzip files, use the --base62 option to further
|
76
|
+
compress your code. This mode is at its best when compressing large files with
|
77
|
+
many repeated tokens.
|
78
|
+
|
79
|
+
The --private option can be used to stop other programs calling private methods
|
80
|
+
in your code by renaming anything beginning with a single underscore. Beware
|
81
|
+
that you should not use this if the generated file contains 'private' methods
|
82
|
+
that need to be accessible by other files. Also know that all the files that
|
83
|
+
access any particular private method must be compressed together so they all get
|
84
|
+
the same rewritten name for the private method.
|
85
|
+
|
86
|
+
|
87
|
+
== License
|
88
|
+
|
89
|
+
(The MIT License)
|
90
|
+
|
91
|
+
Copyright (c) 2004-2011 Dean Edwards, James Coglan
|
92
|
+
|
93
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
94
|
+
this software and associated documentation files (the 'Software'), to deal in
|
95
|
+
the Software without restriction, including without limitation the rights to use,
|
96
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
97
|
+
Software, and to permit persons to whom the Software is furnished to do so,
|
98
|
+
subject to the following conditions:
|
99
|
+
|
100
|
+
The above copyright notice and this permission notice shall be included in all
|
101
|
+
copies or substantial portions of the Software.
|
102
|
+
|
103
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
104
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
105
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
106
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
107
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
108
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/packr
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'oyster'
|
5
|
+
require File.expand_path('../../lib/packr', __FILE__)
|
6
|
+
|
7
|
+
spec = Oyster.spec do
|
8
|
+
name "packr -- JavaScript code compressor based on Dean Edwards' Packer"
|
9
|
+
|
10
|
+
synopsis <<-EOS
|
11
|
+
packr [OPTIONS] INPUT_FILES > OUTPUT_FILE
|
12
|
+
cat INPUT_FILES | packr [OPTIONS] > OUTPUT_FILE
|
13
|
+
EOS
|
14
|
+
|
15
|
+
description <<-EOS
|
16
|
+
PackR is a program for compressing JavaScript programs. It can remove
|
17
|
+
whitespace and comments, compress local variable names, compress/obfuscate
|
18
|
+
private identifiers, and encode the program in base-62.
|
19
|
+
|
20
|
+
When invoked from the command line, it concatenates all the code in
|
21
|
+
INPUT_FILES (or from standard input) and compresses the code using the given
|
22
|
+
options, printing the result to standard output. You can pipe this output into
|
23
|
+
another file to save it.
|
24
|
+
EOS
|
25
|
+
|
26
|
+
flag :'shrink-vars', :default => true,
|
27
|
+
:desc => 'Shrink local variable names inside functions'
|
28
|
+
|
29
|
+
flag :private, :default => false,
|
30
|
+
:desc => 'Obfuscate private identifiers, i.e. names beginning with a single underscore'
|
31
|
+
|
32
|
+
flag :base62, :default => false,
|
33
|
+
:desc => 'Encode the program using base 62'
|
34
|
+
|
35
|
+
array :protect, :default => [],
|
36
|
+
:desc => 'List of variable names to protect from compression when using --shrink-vars'
|
37
|
+
|
38
|
+
notes <<-EOS
|
39
|
+
This program is not a JavaScript parser, and rewrites your files using regular
|
40
|
+
expressions. Be sure to include semicolons and braces everywhere they are
|
41
|
+
required so that your program will work correctly when packed down to a single
|
42
|
+
line.
|
43
|
+
|
44
|
+
By far the most efficient way to serve JavaScript over the web is to use PackR
|
45
|
+
with the --shrink-vars flag, combined with gzip compression. If you don't have
|
46
|
+
access to your server config to set up mod_deflate, you can generate gzip
|
47
|
+
files using (on Unix-like systems):
|
48
|
+
|
49
|
+
packr -s my-file.js | gzip > my-file.js.gz
|
50
|
+
|
51
|
+
You can then get Apache to serve the files by putting this in your .htaccess
|
52
|
+
file:
|
53
|
+
|
54
|
+
AddEncoding gzip .gz
|
55
|
+
RewriteCond %{HTTP:Accept-encoding} gzip
|
56
|
+
RewriteCond %{HTTP_USER_AGENT} !Safari
|
57
|
+
RewriteCond %{REQUEST_FILENAME}.gz -f
|
58
|
+
RewriteRule ^(.*)$ $1.gz [QSA,L]
|
59
|
+
|
60
|
+
If you really cannot serve gzip files, use the --base62 option to further
|
61
|
+
compress your code. This mode is at its best when compressing large files with
|
62
|
+
many repeated tokens.
|
63
|
+
|
64
|
+
The --private option can be used to stop other programs calling private
|
65
|
+
methods in your code by renaming anything beginning with a single underscore.
|
66
|
+
Beware that you should not use this if the generated file contains 'private'
|
67
|
+
methods that need to be accessible by other files. Also know that all the
|
68
|
+
files that access any particular private method must be compressed together so
|
69
|
+
they all get the same rewritten name for the private method.
|
70
|
+
EOS
|
71
|
+
|
72
|
+
author <<-EOS
|
73
|
+
Original JavaScript version by Dean Edwards, Ruby port by James Coglan <jcoglan@googlemail.com>
|
74
|
+
EOS
|
75
|
+
|
76
|
+
copyright <<-EOS
|
77
|
+
Copyright (c) 2004-2011 Dean Edwards, James Coglan. This program is free
|
78
|
+
software, distributed under the MIT license.
|
79
|
+
EOS
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
opts = spec.parse
|
84
|
+
|
85
|
+
inputs = opts[:unclaimed]
|
86
|
+
code = inputs.empty? ?
|
87
|
+
$stdin.read :
|
88
|
+
inputs.map { |f| File.read(f) }.join("\n")
|
89
|
+
|
90
|
+
$stdout.puts Packr.pack(code,
|
91
|
+
:shrink_vars => !!opts[:'shrink-vars'],
|
92
|
+
:protect => opts[:protect],
|
93
|
+
:private => !!opts[:private],
|
94
|
+
:base62 => !!opts[:base62])
|
95
|
+
|
96
|
+
rescue Oyster::HelpRendered
|
97
|
+
end
|
data/lib/packr.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Packr
|
2
|
+
|
3
|
+
autoload :Map, 'packr/map'
|
4
|
+
autoload :Collection, 'packr/collection'
|
5
|
+
autoload :RegexpGroup, 'packr/regexp_group'
|
6
|
+
autoload :Encoder, 'packr/encoder'
|
7
|
+
autoload :Parser, 'packr/parser'
|
8
|
+
autoload :Minifier, 'packr/minifier'
|
9
|
+
autoload :Privates, 'packr/privates'
|
10
|
+
autoload :Shrinker, 'packr/shrinker'
|
11
|
+
autoload :Words, 'packr/words'
|
12
|
+
autoload :Base62, 'packr/base62'
|
13
|
+
autoload :Engine, 'packr/engine'
|
14
|
+
|
15
|
+
IGNORE = RegexpGroup::IGNORE
|
16
|
+
REMOVE = ""
|
17
|
+
SPACE = " "
|
18
|
+
|
19
|
+
DATA = Parser.new.
|
20
|
+
put("STRING1", IGNORE).
|
21
|
+
put('STRING2', IGNORE).
|
22
|
+
put("CONDITIONAL", IGNORE). # conditional comments
|
23
|
+
put("(OPERATOR)\\s*(REGEXP)", "\\1\\2")
|
24
|
+
|
25
|
+
def self.encode62(c)
|
26
|
+
(c < 62 ? '' : encode62((c / 62.0).to_i)) +
|
27
|
+
((c = c % 62) > 35 ? (c+29).chr : c.to_s(36))
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.encode52(c)
|
31
|
+
# Base52 encoding (a-Z)
|
32
|
+
encode = lambda do |d|
|
33
|
+
(d < 52 ? '' : encode.call((d / 52.0).to_i)) +
|
34
|
+
((d = d % 52) > 25 ? (d + 39).chr : (d + 97).chr)
|
35
|
+
end
|
36
|
+
encoded = encode.call(c.to_i)
|
37
|
+
encoded = encoded[1..-1] + '0' if encoded =~ /^(do|if|in)$/
|
38
|
+
encoded
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.rescape(string)
|
42
|
+
string.gsub(/([\/()\[\]{}|*+-.,^$?\\])/) { |m| "\\#{$1}" }
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.pack(script, options = {})
|
46
|
+
@packr ||= Packr::Engine.new
|
47
|
+
@packr.pack(script, options)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.new
|
51
|
+
Packr::Engine.new
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
data/lib/packr/base62.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
module 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
|
+
module 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
|