map 0.0.1
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.
- data/Rakefile +371 -0
- data/lib/map.rb +396 -0
- data/test/lib/testing.rb +68 -0
- data/test/map.rb +157 -0
- metadata +70 -0
data/Rakefile
ADDED
@@ -0,0 +1,371 @@
|
|
1
|
+
This.rubyforge_project = 'codeforpeople'
|
2
|
+
This.author = "Ara T. Howard"
|
3
|
+
This.email = "ara.t.howard@gmail.com"
|
4
|
+
This.homepage = "http://github.com/ahoward/#{ This.lib }/tree/master"
|
5
|
+
|
6
|
+
|
7
|
+
task :default do
|
8
|
+
puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
|
9
|
+
end
|
10
|
+
|
11
|
+
task :test do
|
12
|
+
run_tests!
|
13
|
+
end
|
14
|
+
|
15
|
+
namespace :test do
|
16
|
+
task(:unit){ run_tests!(:unit) }
|
17
|
+
task(:functional){ run_tests!(:functional) }
|
18
|
+
task(:integration){ run_tests!(:integration) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_tests!(which = nil)
|
22
|
+
which ||= '**'
|
23
|
+
test_dir = File.join(This.dir, "test")
|
24
|
+
test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
|
25
|
+
test_rbs = Dir.glob(test_glob).sort
|
26
|
+
|
27
|
+
div = ('=' * 119)
|
28
|
+
line = ('-' * 119)
|
29
|
+
helper = "-r ./test/helper.rb" if test(?e, "./test/helper.rb")
|
30
|
+
|
31
|
+
test_rbs.each_with_index do |test_rb, index|
|
32
|
+
testno = index + 1
|
33
|
+
command = "#{ This.ruby } -I ./lib -I ./test/lib #{ helper } #{ test_rb }"
|
34
|
+
|
35
|
+
puts
|
36
|
+
say(div, :color => :cyan, :bold => true)
|
37
|
+
say("@#{ testno } => ", :bold => true, :method => :print)
|
38
|
+
say(command, :color => :cyan, :bold => true)
|
39
|
+
say(line, :color => :cyan, :bold => true)
|
40
|
+
|
41
|
+
system(command)
|
42
|
+
|
43
|
+
say(line, :color => :cyan, :bold => true)
|
44
|
+
|
45
|
+
status = $?.exitstatus
|
46
|
+
|
47
|
+
if status.zero?
|
48
|
+
say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
|
49
|
+
say("SUCCESS", :color => :green, :bold => true)
|
50
|
+
else
|
51
|
+
say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
|
52
|
+
say("FAILURE", :color => :red, :bold => true)
|
53
|
+
end
|
54
|
+
say(line, :color => :cyan, :bold => true)
|
55
|
+
|
56
|
+
exit(status) unless status.zero?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
task :gemspec do
|
62
|
+
ignore_extensions = 'git', 'svn', 'tmp', /sw./, 'bak', 'gem'
|
63
|
+
ignore_directories = 'pkg'
|
64
|
+
ignore_files = 'test/log'
|
65
|
+
|
66
|
+
shiteless =
|
67
|
+
lambda do |list|
|
68
|
+
list.delete_if do |entry|
|
69
|
+
next unless test(?e, entry)
|
70
|
+
extension = File.basename(entry).split(%r/[.]/).last
|
71
|
+
ignore_extensions.any?{|ext| ext === extension}
|
72
|
+
end
|
73
|
+
list.delete_if do |entry|
|
74
|
+
next unless test(?d, entry)
|
75
|
+
dirname = File.expand_path(entry)
|
76
|
+
ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
|
77
|
+
end
|
78
|
+
list.delete_if do |entry|
|
79
|
+
next unless test(?f, entry)
|
80
|
+
filename = File.expand_path(entry)
|
81
|
+
ignore_files.any?{|file| File.expand_path(file) == filename}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
lib = This.lib
|
86
|
+
object = This.object
|
87
|
+
version = This.version
|
88
|
+
files = shiteless[Dir::glob("**/**")]
|
89
|
+
executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
|
90
|
+
has_rdoc = true #File.exist?('doc')
|
91
|
+
test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb")
|
92
|
+
summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass"
|
93
|
+
description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass"
|
94
|
+
|
95
|
+
if This.extensions.nil?
|
96
|
+
This.extensions = []
|
97
|
+
extensions = This.extensions
|
98
|
+
%w( Makefile configure extconf.rb ).each do |ext|
|
99
|
+
extensions << ext if File.exists?(ext)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
extensions = [extensions].flatten.compact
|
103
|
+
|
104
|
+
template =
|
105
|
+
if test(?e, 'gemspec.erb')
|
106
|
+
Template{ IO.read('gemspec.erb') }
|
107
|
+
else
|
108
|
+
Template {
|
109
|
+
<<-__
|
110
|
+
## #{ lib }.gemspec
|
111
|
+
#
|
112
|
+
|
113
|
+
Gem::Specification::new do |spec|
|
114
|
+
spec.name = #{ lib.inspect }
|
115
|
+
spec.version = #{ version.inspect }
|
116
|
+
spec.platform = Gem::Platform::RUBY
|
117
|
+
spec.summary = #{ lib.inspect }
|
118
|
+
spec.description = #{ description.inspect }
|
119
|
+
|
120
|
+
spec.files = #{ files.inspect }
|
121
|
+
spec.executables = #{ executables.inspect }
|
122
|
+
|
123
|
+
spec.require_path = "lib"
|
124
|
+
|
125
|
+
spec.has_rdoc = #{ has_rdoc.inspect }
|
126
|
+
spec.test_files = #{ test_files.inspect }
|
127
|
+
#spec.add_dependency 'lib', '>= version'
|
128
|
+
#spec.add_dependency 'fattr'
|
129
|
+
|
130
|
+
spec.extensions.push(*#{ extensions.inspect })
|
131
|
+
|
132
|
+
spec.rubyforge_project = #{ This.rubyforge_project.inspect }
|
133
|
+
spec.author = #{ This.author.inspect }
|
134
|
+
spec.email = #{ This.email.inspect }
|
135
|
+
spec.homepage = #{ This.homepage.inspect }
|
136
|
+
end
|
137
|
+
__
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
Fu.mkdir_p(This.pkgdir)
|
142
|
+
gemspec = File.join(This.pkgdir, "#{ lib }.gemspec")
|
143
|
+
open(gemspec, "w"){|fd| fd.puts(template)}
|
144
|
+
This.gemspec = gemspec
|
145
|
+
end
|
146
|
+
|
147
|
+
task :gem => [:clean, :gemspec] do
|
148
|
+
Fu.mkdir_p(This.pkgdir)
|
149
|
+
before = Dir['*.gem']
|
150
|
+
cmd = "gem build #{ This.gemspec }"
|
151
|
+
`#{ cmd }`
|
152
|
+
after = Dir['*.gem']
|
153
|
+
gem = ((after - before).first || after.first) or abort('no gem!')
|
154
|
+
Fu.mv(gem, This.pkgdir)
|
155
|
+
This.gem = File.join(This.pkgdir, File.basename(gem))
|
156
|
+
end
|
157
|
+
|
158
|
+
task :readme do
|
159
|
+
samples = ''
|
160
|
+
prompt = '~ > '
|
161
|
+
lib = This.lib
|
162
|
+
version = This.version
|
163
|
+
|
164
|
+
Dir['sample*/*'].sort.each do |sample|
|
165
|
+
samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
|
166
|
+
|
167
|
+
cmd = "cat #{ sample }"
|
168
|
+
samples << Util.indent(prompt + cmd, 2) << "\n\n"
|
169
|
+
samples << Util.indent(`#{ cmd }`, 4) << "\n"
|
170
|
+
|
171
|
+
cmd = "ruby #{ sample }"
|
172
|
+
samples << Util.indent(prompt + cmd, 2) << "\n\n"
|
173
|
+
|
174
|
+
cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
|
175
|
+
samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n"
|
176
|
+
end
|
177
|
+
|
178
|
+
template =
|
179
|
+
if test(?e, 'readme.erb')
|
180
|
+
Template{ IO.read('readme.erb') }
|
181
|
+
else
|
182
|
+
Template {
|
183
|
+
<<-__
|
184
|
+
NAME
|
185
|
+
#{ lib }
|
186
|
+
|
187
|
+
DESCRIPTION
|
188
|
+
|
189
|
+
INSTALL
|
190
|
+
gem install #{ lib }
|
191
|
+
|
192
|
+
SAMPLES
|
193
|
+
#{ samples }
|
194
|
+
__
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
open("README", "w"){|fd| fd.puts template}
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
task :clean do
|
203
|
+
Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)}
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
task :release => [:clean, :gemspec, :gem] do
|
208
|
+
gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
|
209
|
+
raise "which one? : #{ gems.inspect }" if gems.size > 1
|
210
|
+
raise "no gems?" if gems.size < 1
|
211
|
+
|
212
|
+
cmd = "gem push #{ This.gem }"
|
213
|
+
puts cmd
|
214
|
+
puts
|
215
|
+
system(cmd)
|
216
|
+
abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
|
217
|
+
|
218
|
+
cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }"
|
219
|
+
puts cmd
|
220
|
+
puts
|
221
|
+
system(cmd)
|
222
|
+
abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
BEGIN {
|
230
|
+
# support for this rakefile
|
231
|
+
#
|
232
|
+
$VERBOSE = nil
|
233
|
+
|
234
|
+
require 'ostruct'
|
235
|
+
require 'erb'
|
236
|
+
require 'fileutils'
|
237
|
+
require 'rbconfig'
|
238
|
+
|
239
|
+
# fu shortcut
|
240
|
+
#
|
241
|
+
Fu = FileUtils
|
242
|
+
|
243
|
+
# cache a bunch of stuff about this rakefile/environment
|
244
|
+
#
|
245
|
+
This = OpenStruct.new
|
246
|
+
|
247
|
+
This.file = File.expand_path(__FILE__)
|
248
|
+
This.dir = File.dirname(This.file)
|
249
|
+
This.pkgdir = File.join(This.dir, 'pkg')
|
250
|
+
|
251
|
+
# grok lib
|
252
|
+
#
|
253
|
+
lib = ENV['LIB']
|
254
|
+
unless lib
|
255
|
+
lib = File.basename(Dir.pwd).sub(/[-].*$/, '')
|
256
|
+
end
|
257
|
+
This.lib = lib
|
258
|
+
|
259
|
+
# grok version
|
260
|
+
#
|
261
|
+
version = ENV['VERSION']
|
262
|
+
unless version
|
263
|
+
require "./lib/#{ This.lib }"
|
264
|
+
This.name = lib.capitalize
|
265
|
+
This.object = eval(This.name)
|
266
|
+
version = This.object.send(:version)
|
267
|
+
end
|
268
|
+
This.version = version
|
269
|
+
|
270
|
+
# we need to know the name of the lib an it's version
|
271
|
+
#
|
272
|
+
abort('no lib') unless This.lib
|
273
|
+
abort('no version') unless This.version
|
274
|
+
|
275
|
+
# discover full path to this ruby executable
|
276
|
+
#
|
277
|
+
c = Config::CONFIG
|
278
|
+
bindir = c["bindir"] || c['BINDIR']
|
279
|
+
ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
|
280
|
+
ruby_ext = c['EXEEXT'] || ''
|
281
|
+
ruby = File.join(bindir, (ruby_install_name + ruby_ext))
|
282
|
+
This.ruby = ruby
|
283
|
+
|
284
|
+
# some utils
|
285
|
+
#
|
286
|
+
module Util
|
287
|
+
def indent(s, n = 2)
|
288
|
+
s = unindent(s)
|
289
|
+
ws = ' ' * n
|
290
|
+
s.gsub(%r/^/, ws)
|
291
|
+
end
|
292
|
+
|
293
|
+
def unindent(s)
|
294
|
+
indent = nil
|
295
|
+
s.each do |line|
|
296
|
+
next if line =~ %r/^\s*$/
|
297
|
+
indent = line[%r/^\s*/] and break
|
298
|
+
end
|
299
|
+
indent ? s.gsub(%r/^#{ indent }/, "") : s
|
300
|
+
end
|
301
|
+
extend self
|
302
|
+
end
|
303
|
+
|
304
|
+
# template support
|
305
|
+
#
|
306
|
+
class Template
|
307
|
+
def initialize(&block)
|
308
|
+
@block = block
|
309
|
+
@template = block.call.to_s
|
310
|
+
end
|
311
|
+
def expand(b=nil)
|
312
|
+
ERB.new(Util.unindent(@template)).result(b||@block)
|
313
|
+
end
|
314
|
+
alias_method 'to_s', 'expand'
|
315
|
+
end
|
316
|
+
def Template(*args, &block) Template.new(*args, &block) end
|
317
|
+
|
318
|
+
# colored console output support
|
319
|
+
#
|
320
|
+
This.ansi = {
|
321
|
+
:clear => "\e[0m",
|
322
|
+
:reset => "\e[0m",
|
323
|
+
:erase_line => "\e[K",
|
324
|
+
:erase_char => "\e[P",
|
325
|
+
:bold => "\e[1m",
|
326
|
+
:dark => "\e[2m",
|
327
|
+
:underline => "\e[4m",
|
328
|
+
:underscore => "\e[4m",
|
329
|
+
:blink => "\e[5m",
|
330
|
+
:reverse => "\e[7m",
|
331
|
+
:concealed => "\e[8m",
|
332
|
+
:black => "\e[30m",
|
333
|
+
:red => "\e[31m",
|
334
|
+
:green => "\e[32m",
|
335
|
+
:yellow => "\e[33m",
|
336
|
+
:blue => "\e[34m",
|
337
|
+
:magenta => "\e[35m",
|
338
|
+
:cyan => "\e[36m",
|
339
|
+
:white => "\e[37m",
|
340
|
+
:on_black => "\e[40m",
|
341
|
+
:on_red => "\e[41m",
|
342
|
+
:on_green => "\e[42m",
|
343
|
+
:on_yellow => "\e[43m",
|
344
|
+
:on_blue => "\e[44m",
|
345
|
+
:on_magenta => "\e[45m",
|
346
|
+
:on_cyan => "\e[46m",
|
347
|
+
:on_white => "\e[47m"
|
348
|
+
}
|
349
|
+
def say(phrase, *args)
|
350
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
351
|
+
options[:color] = args.shift.to_s.to_sym unless args.empty?
|
352
|
+
keys = options.keys
|
353
|
+
keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
|
354
|
+
|
355
|
+
color = options[:color]
|
356
|
+
bold = options.has_key?(:bold)
|
357
|
+
|
358
|
+
parts = [phrase]
|
359
|
+
parts.unshift(This.ansi[color]) if color
|
360
|
+
parts.unshift(This.ansi[:bold]) if bold
|
361
|
+
parts.push(This.ansi[:clear]) if parts.size > 1
|
362
|
+
|
363
|
+
method = options[:method] || :puts
|
364
|
+
|
365
|
+
Kernel.send(method, parts.join)
|
366
|
+
end
|
367
|
+
|
368
|
+
# always run out of the project dir
|
369
|
+
#
|
370
|
+
Dir.chdir(This.dir)
|
371
|
+
}
|
data/lib/map.rb
ADDED
@@ -0,0 +1,396 @@
|
|
1
|
+
class Map < Hash
|
2
|
+
Version = '0.0.1' unless defined?(Version)
|
3
|
+
Load = Kernel.method(:load) unless defined?(Load)
|
4
|
+
|
5
|
+
class << Map
|
6
|
+
def version
|
7
|
+
Map::Version
|
8
|
+
end
|
9
|
+
|
10
|
+
# class constructor
|
11
|
+
#
|
12
|
+
def new(*args, &block)
|
13
|
+
map = super(&block)
|
14
|
+
|
15
|
+
case args.size
|
16
|
+
when 0
|
17
|
+
return map
|
18
|
+
|
19
|
+
when 1
|
20
|
+
case arg = args.first
|
21
|
+
when Hash
|
22
|
+
new_from_hash(arg, map)
|
23
|
+
when Array
|
24
|
+
new_from_array(arg, map)
|
25
|
+
else
|
26
|
+
new_from_hash(arg.to_hash, map)
|
27
|
+
end
|
28
|
+
|
29
|
+
else
|
30
|
+
new_from_array(args, map)
|
31
|
+
end
|
32
|
+
|
33
|
+
map
|
34
|
+
end
|
35
|
+
|
36
|
+
def new_from_hash(hash, map = new)
|
37
|
+
map.update(hash)
|
38
|
+
map
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_from_array(array, map = new)
|
42
|
+
each_pair(array){|key, val| map[key] = val}
|
43
|
+
map
|
44
|
+
end
|
45
|
+
|
46
|
+
# iterate over arguments in pairs smartly.
|
47
|
+
#
|
48
|
+
def each_pair(*args)
|
49
|
+
size = args.size
|
50
|
+
parity = size % 2 == 0 ? :even : :odd
|
51
|
+
first = args.first
|
52
|
+
|
53
|
+
return args if size == 0
|
54
|
+
|
55
|
+
if size == 1 and first.respond_to?(:each_pair)
|
56
|
+
first.each_pair do |key, val|
|
57
|
+
yield(key, val)
|
58
|
+
end
|
59
|
+
return args
|
60
|
+
end
|
61
|
+
|
62
|
+
if size == 1 and first.respond_to?(:each_slice)
|
63
|
+
first.each_slice(2) do |key, val|
|
64
|
+
yield(key, val)
|
65
|
+
end
|
66
|
+
return args
|
67
|
+
end
|
68
|
+
|
69
|
+
array_of_pairs = args.all?{|a| a.is_a?(Array) and a.size == 2}
|
70
|
+
|
71
|
+
if array_of_pairs
|
72
|
+
args.each do |pair|
|
73
|
+
key, val, *ignored = pair
|
74
|
+
yield(key, val)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
0.step(args.size - 1, 2) do |a|
|
78
|
+
key = args[a]
|
79
|
+
val = args[a + 1]
|
80
|
+
yield(key, val)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
args
|
85
|
+
end
|
86
|
+
|
87
|
+
alias_method '[]', 'new'
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# instance constructor
|
92
|
+
#
|
93
|
+
attr_accessor :keys
|
94
|
+
|
95
|
+
def initialize(*args, &block)
|
96
|
+
super
|
97
|
+
@keys = []
|
98
|
+
end
|
99
|
+
|
100
|
+
# support methods
|
101
|
+
#
|
102
|
+
def map
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
def map_for(hash)
|
107
|
+
map = Map.new(hash)
|
108
|
+
map.default = hash.default
|
109
|
+
map
|
110
|
+
end
|
111
|
+
|
112
|
+
def convert_key(key)
|
113
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
114
|
+
end
|
115
|
+
|
116
|
+
def convert_val(val)
|
117
|
+
case val
|
118
|
+
when Hash
|
119
|
+
map_for(val)
|
120
|
+
when Array
|
121
|
+
val.collect{|v| Hash === v ? map_for(v) : v}
|
122
|
+
else
|
123
|
+
val
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def convert(key, val)
|
128
|
+
[convert_key(key), convert_val(val)]
|
129
|
+
end
|
130
|
+
|
131
|
+
# maps are aggressive with copy operations. they are all deep copies. make a
|
132
|
+
# new one if you really want a shallow copy
|
133
|
+
#
|
134
|
+
def copy
|
135
|
+
default = self.default
|
136
|
+
self.default = nil
|
137
|
+
copy = Marshal.load(Marshal.dump(self))
|
138
|
+
copy.default = default
|
139
|
+
copy
|
140
|
+
ensure
|
141
|
+
self.default = default
|
142
|
+
end
|
143
|
+
|
144
|
+
def dup
|
145
|
+
copy
|
146
|
+
end
|
147
|
+
|
148
|
+
def clone
|
149
|
+
copy
|
150
|
+
end
|
151
|
+
|
152
|
+
def default(key = nil)
|
153
|
+
key.is_a?(Symbol) && include?(key = key.to_s) ? self[key] : super
|
154
|
+
end
|
155
|
+
|
156
|
+
# writer/reader methods
|
157
|
+
#
|
158
|
+
alias_method '__set__', '[]=' unless method_defined?('__set__')
|
159
|
+
alias_method '__get__', '[]' unless method_defined?('__get__')
|
160
|
+
alias_method '__update__', 'update' unless method_defined?('__update__')
|
161
|
+
|
162
|
+
def set(key, val)
|
163
|
+
key, val = convert(key, val)
|
164
|
+
@keys.push(key) unless has_key?(key)
|
165
|
+
__set__(key, val)
|
166
|
+
end
|
167
|
+
alias_method 'store', 'set'
|
168
|
+
alias_method '[]=', 'set'
|
169
|
+
|
170
|
+
def get(key)
|
171
|
+
__get__(key)
|
172
|
+
end
|
173
|
+
alias_method '[]', 'get'
|
174
|
+
|
175
|
+
def fetch(key, *args, &block)
|
176
|
+
super(convert_key(key), *args, &block)
|
177
|
+
end
|
178
|
+
|
179
|
+
def key?(key)
|
180
|
+
super(convert_key(key))
|
181
|
+
end
|
182
|
+
alias_method 'include?', 'key?'
|
183
|
+
alias_method 'has_key?', 'key?'
|
184
|
+
alias_method 'member?', 'key?'
|
185
|
+
|
186
|
+
def update(*args)
|
187
|
+
Map.each_pair(*args) do |key, val|
|
188
|
+
set(key, val)
|
189
|
+
end
|
190
|
+
self
|
191
|
+
end
|
192
|
+
alias_method 'merge!', 'update'
|
193
|
+
|
194
|
+
def merge(*args)
|
195
|
+
copy.update(*args)
|
196
|
+
end
|
197
|
+
|
198
|
+
def reverse_merge(hash)
|
199
|
+
map = copy
|
200
|
+
hash.each{|key, val| map[key] = val unless map.key?(key)}
|
201
|
+
map
|
202
|
+
end
|
203
|
+
|
204
|
+
def reverse_merge!(hash)
|
205
|
+
replace(reverse_merge(hash))
|
206
|
+
end
|
207
|
+
|
208
|
+
def values
|
209
|
+
array = []
|
210
|
+
@keys.each{|key| array.push(self[key])}
|
211
|
+
array
|
212
|
+
end
|
213
|
+
alias_method 'vals', 'values'
|
214
|
+
|
215
|
+
def values_at(*keys)
|
216
|
+
keys.map{|key| self[key]}
|
217
|
+
end
|
218
|
+
|
219
|
+
def first
|
220
|
+
[@keys.first, self[@keys.first]]
|
221
|
+
end
|
222
|
+
|
223
|
+
def last
|
224
|
+
[@keys.last, self[@keys.last]]
|
225
|
+
end
|
226
|
+
|
227
|
+
# iterator methods
|
228
|
+
#
|
229
|
+
def each_with_index
|
230
|
+
@keys.each_with_index{|key, index| yield([key, self[key]], index)}
|
231
|
+
self
|
232
|
+
end
|
233
|
+
|
234
|
+
def each_key
|
235
|
+
@keys.each{|key| yield(key)}
|
236
|
+
self
|
237
|
+
end
|
238
|
+
|
239
|
+
def each_value
|
240
|
+
@keys.each{|key| yield self[key]}
|
241
|
+
self
|
242
|
+
end
|
243
|
+
|
244
|
+
def each
|
245
|
+
@keys.each{|key| yield(key, self[key])}
|
246
|
+
self
|
247
|
+
end
|
248
|
+
alias_method 'each_pair', 'each'
|
249
|
+
|
250
|
+
# mutators
|
251
|
+
#
|
252
|
+
def delete(key)
|
253
|
+
key = convert_key(key)
|
254
|
+
@keys.delete(key)
|
255
|
+
super(key)
|
256
|
+
end
|
257
|
+
|
258
|
+
def clear
|
259
|
+
@keys = []
|
260
|
+
super
|
261
|
+
end
|
262
|
+
|
263
|
+
def delete_if
|
264
|
+
to_delete = []
|
265
|
+
@keys.each{|key| to_delete.push(key) if yield(key)}
|
266
|
+
to_delete.each{|key| delete(key)}
|
267
|
+
map
|
268
|
+
end
|
269
|
+
|
270
|
+
def replace(hash)
|
271
|
+
clear
|
272
|
+
update(hash)
|
273
|
+
end
|
274
|
+
|
275
|
+
# ordered container specific methods
|
276
|
+
#
|
277
|
+
def shift
|
278
|
+
unless empty?
|
279
|
+
key = @keys.first
|
280
|
+
val = delete(key)
|
281
|
+
[key, val]
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def unshift(*args)
|
286
|
+
Map.each_pair(*args) do |key, val|
|
287
|
+
if key?(key)
|
288
|
+
delete(key)
|
289
|
+
else
|
290
|
+
@keys.unshift(key)
|
291
|
+
end
|
292
|
+
__set__(key, val)
|
293
|
+
end
|
294
|
+
self
|
295
|
+
end
|
296
|
+
|
297
|
+
def push(*args)
|
298
|
+
Map.each_pair(*args) do |key, val|
|
299
|
+
if key?(key)
|
300
|
+
delete(key)
|
301
|
+
else
|
302
|
+
@keys.push(key)
|
303
|
+
end
|
304
|
+
__set__(key, val)
|
305
|
+
end
|
306
|
+
self
|
307
|
+
end
|
308
|
+
|
309
|
+
def pop
|
310
|
+
unless empty?
|
311
|
+
key = @keys.last
|
312
|
+
val = delete(key)
|
313
|
+
[key, val]
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# misc
|
318
|
+
#
|
319
|
+
def ==(hash)
|
320
|
+
return false if @keys != hash.keys
|
321
|
+
super hash
|
322
|
+
end
|
323
|
+
|
324
|
+
def invert
|
325
|
+
inverted = Map.new
|
326
|
+
inverted.default = self.default
|
327
|
+
@keys.each{|key| inverted[self[key]] = key }
|
328
|
+
inverted
|
329
|
+
end
|
330
|
+
|
331
|
+
def reject(&block)
|
332
|
+
dup.delete_if(&block)
|
333
|
+
end
|
334
|
+
|
335
|
+
def reject!(&block)
|
336
|
+
hash = reject(&block)
|
337
|
+
self == hash ? nil : hash
|
338
|
+
end
|
339
|
+
|
340
|
+
def select
|
341
|
+
array = []
|
342
|
+
each{|key, val| array << [key,val] if yield(key, val)}
|
343
|
+
array
|
344
|
+
end
|
345
|
+
|
346
|
+
def inspect
|
347
|
+
array = []
|
348
|
+
each{|key, val| array << (key.inspect + "=>" + val.inspect)}
|
349
|
+
string = '{' + array.join(", ") + '}'
|
350
|
+
end
|
351
|
+
|
352
|
+
# converions
|
353
|
+
#
|
354
|
+
def to_hash
|
355
|
+
hash = Hash.new(default)
|
356
|
+
each do |key, val|
|
357
|
+
val = val.to_hash if val.respond_to?(:to_hash)
|
358
|
+
hash[key] = val
|
359
|
+
end
|
360
|
+
hash
|
361
|
+
end
|
362
|
+
|
363
|
+
def to_yaml(*args, &block)
|
364
|
+
to_hash.to_yaml(*args, &block)
|
365
|
+
end
|
366
|
+
|
367
|
+
def to_array
|
368
|
+
array = []
|
369
|
+
each{|*pair| array.push(pair)}
|
370
|
+
array
|
371
|
+
end
|
372
|
+
alias_method 'to_a', 'to_array'
|
373
|
+
|
374
|
+
def to_s
|
375
|
+
to_array.to_s
|
376
|
+
end
|
377
|
+
|
378
|
+
def stringify_keys!; self end
|
379
|
+
def symbolize_keys!; self end
|
380
|
+
def to_options!; self end
|
381
|
+
|
382
|
+
def class
|
383
|
+
Hash
|
384
|
+
end
|
385
|
+
|
386
|
+
def __class__
|
387
|
+
Map
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
module Kernel
|
392
|
+
private
|
393
|
+
def Map(*args, &block)
|
394
|
+
Map.new(*args, &block)
|
395
|
+
end
|
396
|
+
end
|
data/test/lib/testing.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# simple testing support
|
2
|
+
#
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
def Testing(*args, &block)
|
6
|
+
Class.new(Test::Unit::TestCase) do
|
7
|
+
def self.slug_for(*args)
|
8
|
+
string = args.flatten.compact.join('-')
|
9
|
+
words = string.to_s.scan(%r/\w+/)
|
10
|
+
words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
|
11
|
+
words.delete_if{|word| word.nil? or word.strip.empty?}
|
12
|
+
words.join('-').downcase
|
13
|
+
end
|
14
|
+
|
15
|
+
@@testing_subclass_count = 0 unless defined?(@@testing_subclass_count)
|
16
|
+
@@testing_subclass_count += 1
|
17
|
+
slug = slug_for(*args).gsub(%r/-/,'_')
|
18
|
+
name = ['TESTING', '%03d' % @@testing_subclass_count, slug].delete_if{|part| part.empty?}.join('_')
|
19
|
+
name = name.upcase!
|
20
|
+
const_set(:Name, name)
|
21
|
+
def self.name() const_get(:Name) end
|
22
|
+
|
23
|
+
def self.testno()
|
24
|
+
'%05d' % (@testno ||= 0)
|
25
|
+
ensure
|
26
|
+
@testno += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.testing(*args, &block)
|
30
|
+
method = ["test", testno, slug_for(*args)].delete_if{|part| part.empty?}.join('_')
|
31
|
+
define_method("test_#{ testno }_#{ slug_for(*args) }", &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method '__assert__', 'assert'
|
35
|
+
|
36
|
+
def assert(*args, &block)
|
37
|
+
if block
|
38
|
+
label = "assert(#{ args.join ' ' })"
|
39
|
+
result = nil
|
40
|
+
assert_nothing_raised{ result = block.call }
|
41
|
+
__assert__(result, label)
|
42
|
+
result
|
43
|
+
else
|
44
|
+
result = args.shift
|
45
|
+
label = "assert(#{ args.join ' ' })"
|
46
|
+
__assert__(result, label)
|
47
|
+
result
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def subclass_of exception
|
52
|
+
class << exception
|
53
|
+
def ==(other) super or self > other end
|
54
|
+
end
|
55
|
+
exception
|
56
|
+
end
|
57
|
+
|
58
|
+
alias_method '__assert_raises__', 'assert_raises'
|
59
|
+
|
60
|
+
def assert_raises(*args, &block)
|
61
|
+
args.push(subclass_of(Exception)) if args.empty?
|
62
|
+
__assert_raises__(*args, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
module_eval &block
|
66
|
+
self
|
67
|
+
end
|
68
|
+
end
|
data/test/map.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'testing'
|
2
|
+
require 'map'
|
3
|
+
|
4
|
+
Testing Map do
|
5
|
+
testing 'that bare constructor werks' do
|
6
|
+
assert{ Map.new }
|
7
|
+
end
|
8
|
+
|
9
|
+
testing 'that the contructor accepts a hash' do
|
10
|
+
assert{ Map.new(hash = {}) }
|
11
|
+
end
|
12
|
+
|
13
|
+
testing 'that the constructor accepts the empty array' do
|
14
|
+
array = []
|
15
|
+
assert{ Map.new(array) }
|
16
|
+
assert{ Map.new(*array) }
|
17
|
+
end
|
18
|
+
|
19
|
+
testing 'that the contructor accepts an even sized array' do
|
20
|
+
arrays = [
|
21
|
+
[ %w( k v ), %w( key val ) ],
|
22
|
+
[ %w( k v ), %w( key val ), %w( a b ) ],
|
23
|
+
[ %w( k v ), %w( key val ), %w( a b ), %w( x y ) ]
|
24
|
+
]
|
25
|
+
arrays.each do |array|
|
26
|
+
assert{ Map.new(array) }
|
27
|
+
assert{ Map.new(*array) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
testing 'that the contructor accepts an odd sized array' do
|
32
|
+
arrays = [
|
33
|
+
[ %w( k v ) ],
|
34
|
+
[ %w( k v ), %w( key val ), %w( a b ) ]
|
35
|
+
]
|
36
|
+
arrays.each do |array|
|
37
|
+
assert{ Map.new(array) }
|
38
|
+
assert{ Map.new(*array) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
testing 'that the constructor accepts arrays of pairs' do
|
43
|
+
arrays = [
|
44
|
+
[],
|
45
|
+
[ %w( k v ) ],
|
46
|
+
[ %w( k v ), %w( key val ) ],
|
47
|
+
[ %w( k v ), %w( key val ), %w( a b ) ]
|
48
|
+
]
|
49
|
+
arrays.each do |array|
|
50
|
+
assert{ Map.new(array) }
|
51
|
+
assert{ Map.new(*array) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
testing 'that "[]" is a synonym for "new"' do
|
56
|
+
list = [
|
57
|
+
[],
|
58
|
+
[{}],
|
59
|
+
[[:key, :val]],
|
60
|
+
[:key, :val]
|
61
|
+
]
|
62
|
+
list.each do |args|
|
63
|
+
map = assert{ Map[*args] }
|
64
|
+
assert{ map.is_a?(Map) }
|
65
|
+
assert{ Map.new(*args) == map }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
testing 'that #each yields pairs in order' do
|
70
|
+
map = new_int_map
|
71
|
+
i = 0
|
72
|
+
map.each do |key, val|
|
73
|
+
assert{ key == i.to_s }
|
74
|
+
assert{ val == i }
|
75
|
+
i += 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
testing 'that keys and values are ordered' do
|
80
|
+
n = 2048
|
81
|
+
map = new_int_map(n)
|
82
|
+
values = Array.new(n){|i| i}
|
83
|
+
keys = values.map{|value| value.to_s}
|
84
|
+
assert{ map.keys.size == n }
|
85
|
+
assert{ map.keys == keys}
|
86
|
+
assert{ map.values == values}
|
87
|
+
end
|
88
|
+
|
89
|
+
testing 'that maps are string/symbol indifferent for simple look-ups' do
|
90
|
+
map = Map.new
|
91
|
+
map[:k] = :v
|
92
|
+
map['a'] = 'b'
|
93
|
+
assert{ map[:k] == :v }
|
94
|
+
assert{ map[:k.to_s] == :v }
|
95
|
+
assert{ map[:a] == 'b' }
|
96
|
+
assert{ map[:a.to_s] == 'b' }
|
97
|
+
end
|
98
|
+
|
99
|
+
testing 'that maps are string/symbol indifferent for recursive look-ups' do
|
100
|
+
map = Map.new
|
101
|
+
assert{ map[:a] = Map[:b, 42] }
|
102
|
+
assert{ map[:a][:b] = Map[:c, 42] }
|
103
|
+
assert{ map[:a][:b][:c] == 42 }
|
104
|
+
assert{ map['a'][:b][:c] == 42 }
|
105
|
+
assert{ map['a']['b'][:c] == 42 }
|
106
|
+
assert{ map['a']['b']['c'] == 42 }
|
107
|
+
assert{ map[:a]['b'][:c] == 42 }
|
108
|
+
assert{ map[:a]['b']['c'] == 42 }
|
109
|
+
assert{ map[:a][:b]['c'] == 42 }
|
110
|
+
assert{ map['a'][:b]['c'] == 42 }
|
111
|
+
end
|
112
|
+
|
113
|
+
testing 'that maps support shift like a good ordered container' do
|
114
|
+
map = Map.new
|
115
|
+
10.times do |i|
|
116
|
+
key, val = i.to_s, i
|
117
|
+
assert{ map.unshift(key, val) }
|
118
|
+
assert{ map[key] == val }
|
119
|
+
assert{ map.keys.first.to_s == key.to_s}
|
120
|
+
assert{ map.values.first.to_s == val.to_s}
|
121
|
+
end
|
122
|
+
|
123
|
+
map = Map.new
|
124
|
+
args = []
|
125
|
+
10.times do |i|
|
126
|
+
key, val = i.to_s, i
|
127
|
+
args.unshift([key, val])
|
128
|
+
end
|
129
|
+
map.unshift(*args)
|
130
|
+
10.times do |i|
|
131
|
+
key, val = i.to_s, i
|
132
|
+
assert{ map[key] == val }
|
133
|
+
assert{ map.keys[i].to_s == key.to_s}
|
134
|
+
assert{ map.values[i].to_s == val.to_s}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
def new_int_map(n = 1024)
|
140
|
+
map = Map.new
|
141
|
+
n.times{|i| map[i.to_s] = i}
|
142
|
+
map
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
|
149
|
+
BEGIN {
|
150
|
+
testdir = File.dirname(File.expand_path(__FILE__))
|
151
|
+
testlibdir = File.join(testdir, 'lib')
|
152
|
+
rootdir = File.dirname(testdir)
|
153
|
+
libdir = File.join(rootdir, 'lib')
|
154
|
+
|
155
|
+
$LOAD_PATH.push(libdir)
|
156
|
+
$LOAD_PATH.push(testlibdir)
|
157
|
+
}
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: map
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ara T. Howard
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-03 00:00:00 -06:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: "description: map kicks the ass"
|
23
|
+
email: ara.t.howard@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/map.rb
|
32
|
+
- Rakefile
|
33
|
+
- test/lib/testing.rb
|
34
|
+
- test/map.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/ahoward/map/tree/master
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
hash: 3
|
50
|
+
segments:
|
51
|
+
- 0
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project: codeforpeople
|
65
|
+
rubygems_version: 1.3.7
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: map
|
69
|
+
test_files:
|
70
|
+
- test/map.rb
|