hosttag 0.12
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/.gitignore +2 -0
- data/ChangeLog +44 -0
- data/EXCLUDE +3 -0
- data/LICENCE +674 -0
- data/README +92 -0
- data/Rakefile +30 -0
- data/TODO +4 -0
- data/bin/hosttag +168 -0
- data/bin/ht +168 -0
- data/bin/htdel +164 -0
- data/bin/htdump +70 -0
- data/bin/htexport +99 -0
- data/bin/htimport +81 -0
- data/bin/htremap +93 -0
- data/bin/htset +164 -0
- data/etc/Makefile +16 -0
- data/etc/README +10 -0
- data/hosttag.gemspec +23 -0
- data/hosttag.spec +179 -0
- data/lib/hosttag.rb +402 -0
- data/lib/hosttag/server.rb +35 -0
- data/test/data_hosttag/a/centos +0 -0
- data/test/data_hosttag/a/centos5 +0 -0
- data/test/data_hosttag/a/centos5-x86_64 +0 -0
- data/test/data_hosttag/a/public +0 -0
- data/test/data_hosttag/g/SKIP +0 -0
- data/test/data_hosttag/g/centos +0 -0
- data/test/data_hosttag/g/centos4 +0 -0
- data/test/data_hosttag/g/centos4-i386 +0 -0
- data/test/data_hosttag/h/SKIP +0 -0
- data/test/data_hosttag/h/centos +0 -0
- data/test/data_hosttag/h/centos4 +0 -0
- data/test/data_hosttag/h/centos4-x86_64 +0 -0
- data/test/data_hosttag/h/public +0 -0
- data/test/data_hosttag/m/centos +0 -0
- data/test/data_hosttag/m/centos4 +0 -0
- data/test/data_hosttag/m/centos4-x86_64 +0 -0
- data/test/data_hosttag/m/public +0 -0
- data/test/data_hosttag/m/vps +0 -0
- data/test/data_hosttag/n/centos +0 -0
- data/test/data_hosttag/n/centos5 +0 -0
- data/test/data_hosttag/n/centos5-i386 +0 -0
- data/test/data_hosttag/n/laptop +0 -0
- data/test/test_hosttag_bin.rb +70 -0
- data/test/test_hosttag_lib.rb +119 -0
- data/test/test_htset_bin.rb +174 -0
- data/test/test_htset_lib.rb +183 -0
- data/test/ts_all.rb +4 -0
- metadata +132 -0
data/bin/htdel
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Hosttag update client, redis version
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
# htset <host1> [<host2> ...] <tag> [<tag2> ...]
|
7
|
+
# htdel <host1> [<host2> ...] <tag> [<tag2> ...]
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'optparse'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'pp'
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
15
|
+
require 'hosttag'
|
16
|
+
include Hosttag
|
17
|
+
|
18
|
+
# ------------------------------------------------------------------------------
|
19
|
+
# Subroutines
|
20
|
+
|
21
|
+
def die(error)
|
22
|
+
puts error
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_options(me)
|
27
|
+
options = { :all => false, :autoconfirm => false }
|
28
|
+
opts = OptionParser.new
|
29
|
+
opts.banner = "Usage: #{me} [options] <host> [<host2> ...] <tag> [<tag2>...]"
|
30
|
+
opts.on('-?', '-h', '--help') do
|
31
|
+
puts opts
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
opts.on('-h', '--help', '-?', 'Show this usage information') do
|
35
|
+
die(opts)
|
36
|
+
end
|
37
|
+
opts.on('-A', '--all', '(htdel) Delete all tags from hosts') do
|
38
|
+
options[:all] = true
|
39
|
+
end
|
40
|
+
opts.on('-y', '--yes', "(htdel) Don't ask for confirmation on delete all operations") do
|
41
|
+
options[:autoconfirm] = true
|
42
|
+
end
|
43
|
+
opts.on('-H', '--host', '--hosts', 'Treat unrecognised elements as hosts') do
|
44
|
+
options[:host_mode] = true
|
45
|
+
end
|
46
|
+
opts.on('-T', '--tag', '--tags', 'Treat unrecognised elements as tags') do
|
47
|
+
options[:tag_mode] = true
|
48
|
+
end
|
49
|
+
opts.on('--ns=STR', '--namespace=STR', String, 'Namespace into which we load hosttag data. Default: hosttag') do |val|
|
50
|
+
options[:namespace] = val
|
51
|
+
end
|
52
|
+
opts.on('-s=ARG', '--server=ARG', String, 'Server hostname to connect to') do |val|
|
53
|
+
options[:server] = val
|
54
|
+
end
|
55
|
+
opts.on('-p=ARG', '--port=ARG', Integer, 'Server port to connect to') do |val|
|
56
|
+
options[:port] = val
|
57
|
+
end
|
58
|
+
opts.on('-v', '--verbose', 'Verbose output') do
|
59
|
+
options[:verbose] = true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parse options
|
63
|
+
begin
|
64
|
+
args = opts.parse(ARGV)
|
65
|
+
rescue => e
|
66
|
+
die(opts)
|
67
|
+
end
|
68
|
+
|
69
|
+
if args.length < 2 and not options[:all]
|
70
|
+
die(opts)
|
71
|
+
end
|
72
|
+
if options[:all] and me =~ /set$/
|
73
|
+
warn "Error: --all not available with #{me}"
|
74
|
+
die(opts)
|
75
|
+
end
|
76
|
+
if options[:host_mode] and options[:tag_mode]
|
77
|
+
warn "Error: --host and --tag options are mutually exclusive"
|
78
|
+
die(opts)
|
79
|
+
end
|
80
|
+
|
81
|
+
return options, args
|
82
|
+
end
|
83
|
+
|
84
|
+
# Classify args into hosts, tags, and uncertain buckets
|
85
|
+
def classify_args(args, options)
|
86
|
+
results = { :host => [], :tag => [], :uncertain => [] }
|
87
|
+
verbose = options[:verbose]
|
88
|
+
|
89
|
+
# First arg must be host, and last tag, by definition
|
90
|
+
results[:host].push(args.shift)
|
91
|
+
last_tag = args.pop
|
92
|
+
|
93
|
+
# Classify remainder by doing lookups
|
94
|
+
while a = args.shift do
|
95
|
+
begin
|
96
|
+
tags = hosttag_lookup_hosts(a, options)
|
97
|
+
if tags.length > 0
|
98
|
+
# if 'a' is a valid host, then everything already in uncertain must be too
|
99
|
+
if results[:uncertain].length > 0
|
100
|
+
results[:host].push(*results[:uncertain])
|
101
|
+
results[:uncertain] = []
|
102
|
+
end
|
103
|
+
results[:host].push(a)
|
104
|
+
end
|
105
|
+
rescue
|
106
|
+
# 'a' is not a known host, check if a tag
|
107
|
+
begin
|
108
|
+
hosts = hosttag_lookup_tags(a, options)
|
109
|
+
if hosts.length > 0
|
110
|
+
# If 'a' is a valid tag, then everything else in args must be too
|
111
|
+
results[:tag].push(a, *args)
|
112
|
+
args = []
|
113
|
+
end
|
114
|
+
rescue
|
115
|
+
# 'a' is not a known host or tag, add to uncertain list
|
116
|
+
results[:uncertain].push(a)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
results[:tag].push(last_tag)
|
122
|
+
|
123
|
+
return results
|
124
|
+
end
|
125
|
+
|
126
|
+
# ------------------------------------------------------------------------------
|
127
|
+
# Main
|
128
|
+
|
129
|
+
mode = $0.sub(/^.*\//, '')
|
130
|
+
|
131
|
+
options, args = parse_options(mode)
|
132
|
+
|
133
|
+
# Normal mode
|
134
|
+
if (not options[:all])
|
135
|
+
results = classify_args(args, options)
|
136
|
+
if options[:verbose]
|
137
|
+
print "+ results: "
|
138
|
+
pp results
|
139
|
+
end
|
140
|
+
|
141
|
+
if results[:uncertain].length > 0
|
142
|
+
# --hosts: treat unknown elements as hosts
|
143
|
+
if options[:host_mode]
|
144
|
+
results[:host].push(*results[:uncertain])
|
145
|
+
elsif options[:tag_mode]
|
146
|
+
results[:tag].push(*results[:uncertain])
|
147
|
+
else
|
148
|
+
# TODO: do something useful here - ask the user?
|
149
|
+
die("Error: can't auto-classify '#{results[:uncertain].join(',')}' - aborting")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if (mode =~ /del$/)
|
154
|
+
hosttag_delete_tags(results[:host], results[:tag], options)
|
155
|
+
else
|
156
|
+
hosttag_add_tags(results[:host], results[:tag], options)
|
157
|
+
end
|
158
|
+
|
159
|
+
# htdel --all mode
|
160
|
+
elsif (mode =~ /del$/)
|
161
|
+
hosttag_delete_all_tags(args, options)
|
162
|
+
|
163
|
+
end
|
164
|
+
|
data/bin/htdump
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Quick and dirty script to dump hosttag datastore
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
9
|
+
require 'hosttag/server'
|
10
|
+
|
11
|
+
defaults = {}
|
12
|
+
|
13
|
+
def die(error)
|
14
|
+
puts error
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_options(options)
|
19
|
+
opts = OptionParser.new
|
20
|
+
opts.banner = "Usage: htimport [options]"
|
21
|
+
opts.on('-?', '-h', '--help') do
|
22
|
+
puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
opts.on('-h', '--help', '-?', 'Show this usage information') do
|
26
|
+
die(opts)
|
27
|
+
end
|
28
|
+
opts.on('-s=ARG', '--server=ARG', String, 'Server hostname to connect to. Default: hosttag') do |val|
|
29
|
+
options[:server] = val
|
30
|
+
end
|
31
|
+
opts.on('-p=ARG', '--port=ARG', Integer, 'Server port to connect to. Default: 6379') do |val|
|
32
|
+
options[:port] = val
|
33
|
+
end
|
34
|
+
opts.on('--ns=STR', '--namespace=STR', String, 'Namespace from which to dump hosttag data. Default: hosttag') do |val|
|
35
|
+
options[:namespace] = val
|
36
|
+
end
|
37
|
+
|
38
|
+
# Parse options
|
39
|
+
begin
|
40
|
+
args = opts.parse(ARGV)
|
41
|
+
rescue => e
|
42
|
+
puts "Error: " << e
|
43
|
+
usage(opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
if args.length > 0
|
47
|
+
puts "Error: unexpected arguments '#{args}'"
|
48
|
+
usage(opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
return options
|
52
|
+
end
|
53
|
+
|
54
|
+
def usage(opts)
|
55
|
+
puts opts
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
|
59
|
+
# ----------------------------------------------------------------------------
|
60
|
+
# Main
|
61
|
+
|
62
|
+
options, args = parse_options( defaults )
|
63
|
+
|
64
|
+
r = Hosttag::Server.new(options)
|
65
|
+
|
66
|
+
# Dump
|
67
|
+
r.keys(r.get_key('*')).sort.each do |k|
|
68
|
+
puts "#{k}: #{r.smembers(k).sort.join(' ')}"
|
69
|
+
end
|
70
|
+
|
data/bin/htexport
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Export host tags held from redis to /etc/hosttag
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
10
|
+
require 'hosttag/server'
|
11
|
+
|
12
|
+
defaults = { :datadir => '/etc/hosttag' }
|
13
|
+
|
14
|
+
# ------------------------------------------------------------------------------
|
15
|
+
# Subroutines
|
16
|
+
|
17
|
+
def die(error)
|
18
|
+
puts error
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_options(options)
|
23
|
+
opts = OptionParser.new
|
24
|
+
opts.banner = "Usage: htexport [options]"
|
25
|
+
opts.on('-?', '-h', '--help') do
|
26
|
+
puts opts
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
opts.on('-h', '--help', '-?', 'Show this usage information') do
|
30
|
+
die(opts)
|
31
|
+
end
|
32
|
+
opts.on('-s=ARG', '--server=ARG', String, 'Server hostname to connect to') do |val|
|
33
|
+
options[:server] = val
|
34
|
+
end
|
35
|
+
opts.on('-p=ARG', '--port=ARG', Integer, 'Server port to connect to') do |val|
|
36
|
+
options[:port] = val
|
37
|
+
end
|
38
|
+
opts.on('--datadir=DIR', String, 'Data directory to export data into. Default: /etc/hosttag') do |val|
|
39
|
+
options[:datadir] = val
|
40
|
+
end
|
41
|
+
opts.on('--ns=STR', '--namespace=STR', String, 'Namespace into which we load hosttag data. Default: hosttag') do |val|
|
42
|
+
options[:namespace] = val
|
43
|
+
end
|
44
|
+
opts.on('--delete', 'Delete mode - remove all directories from data dir before exporting') do
|
45
|
+
options[:delete] = true
|
46
|
+
end
|
47
|
+
opts.on('-n', '--noop', 'Noop mode - only report what would be exported') do
|
48
|
+
options[:noop] = true
|
49
|
+
end
|
50
|
+
opts.on('-v', '--verbose', 'Verbose output') do
|
51
|
+
options[:verbose] = true
|
52
|
+
end
|
53
|
+
|
54
|
+
# Parse options
|
55
|
+
begin
|
56
|
+
args = opts.parse(ARGV)
|
57
|
+
rescue => e
|
58
|
+
die(opts)
|
59
|
+
end
|
60
|
+
|
61
|
+
if args.length > 0
|
62
|
+
die(opts)
|
63
|
+
end
|
64
|
+
|
65
|
+
return options, args
|
66
|
+
end
|
67
|
+
|
68
|
+
def prune_data_directory
|
69
|
+
# TODO
|
70
|
+
die "--delete mode not implemented yet"
|
71
|
+
end
|
72
|
+
|
73
|
+
# ----------------------------------------------------------------------------
|
74
|
+
# Main
|
75
|
+
|
76
|
+
options, args = parse_options( defaults )
|
77
|
+
|
78
|
+
r = Hosttag::Server.new(options)
|
79
|
+
|
80
|
+
Dir.chdir(options[:datadir])
|
81
|
+
|
82
|
+
# If in delete mode, prune the data directory
|
83
|
+
if options[:delete] and not options[:noop]
|
84
|
+
prune_data_directory
|
85
|
+
end
|
86
|
+
|
87
|
+
r.smembers(r.get_key('all_hosts')).each do |host|
|
88
|
+
if not File.directory?(host)
|
89
|
+
puts "+ #{host}/"
|
90
|
+
Dir.mkdir(host, 0775) if not options[:noop]
|
91
|
+
end
|
92
|
+
r.smembers(r.get_key('host', host)).each do |tag|
|
93
|
+
if not File.exist?("#{host}/#{tag}")
|
94
|
+
puts "+ #{host}/#{tag}"
|
95
|
+
FileUtils.touch "#{host}/#{tag}" if not options[:noop]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
data/bin/htimport
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Import hosts and tags into datastore from a directory
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
10
|
+
require 'hosttag'
|
11
|
+
include Hosttag
|
12
|
+
|
13
|
+
defaults = { :datadir => '/etc/hosttag' }
|
14
|
+
|
15
|
+
# ------------------------------------------------------------------------------
|
16
|
+
# Subroutines
|
17
|
+
|
18
|
+
def die(error)
|
19
|
+
puts error
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def usage(opts)
|
24
|
+
puts opts
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_options(options)
|
29
|
+
opts = OptionParser.new
|
30
|
+
opts.banner = "Usage: htimport [options]"
|
31
|
+
opts.on('-?', '-h', '--help') do
|
32
|
+
puts opts
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
opts.on('-h', '--help', '-?', 'Show this usage information') do
|
36
|
+
die(opts)
|
37
|
+
end
|
38
|
+
opts.on('-s=ARG', '--server=ARG', String, 'Server hostname to connect to. Default: hosttag') do |val|
|
39
|
+
options[:server] = val
|
40
|
+
end
|
41
|
+
opts.on('-p=ARG', '--port=ARG', Integer, 'Server port to connect to. Default: 6379') do |val|
|
42
|
+
options[:port] = val
|
43
|
+
end
|
44
|
+
opts.on('--delete', 'Delete mode - remove all hosttags from the datastore before importing') do
|
45
|
+
options[:delete] = true
|
46
|
+
end
|
47
|
+
opts.on('-y', '--yes', "(--delete) Assume automatic confirmation for --delete") do
|
48
|
+
options[:autoconfirm] = true
|
49
|
+
end
|
50
|
+
opts.on('--datadir=DIR', String, 'Data directory from which to load data. Default: /etc/hosttag') do |val|
|
51
|
+
options[:datadir] = val
|
52
|
+
end
|
53
|
+
opts.on('--ns=STR', '--namespace=STR', String, 'Namespace into which we load hosttag data. Default: hosttag') do |val|
|
54
|
+
options[:namespace] = val
|
55
|
+
end
|
56
|
+
opts.on('-v', '--verbose', 'Verbose output') do
|
57
|
+
options[:verbose] = true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parse options
|
61
|
+
begin
|
62
|
+
args = opts.parse(ARGV)
|
63
|
+
rescue => e
|
64
|
+
puts "Error: " << e
|
65
|
+
usage(opts)
|
66
|
+
end
|
67
|
+
|
68
|
+
if args.length > 0
|
69
|
+
puts "Error: unexpected arguments '#{args}'"
|
70
|
+
usage(opts)
|
71
|
+
end
|
72
|
+
|
73
|
+
return options
|
74
|
+
end
|
75
|
+
|
76
|
+
# ----------------------------------------------------------------------------
|
77
|
+
# Main
|
78
|
+
|
79
|
+
options = parse_options(defaults)
|
80
|
+
hosttag_import_from_directory(options[:datadir], options)
|
81
|
+
|
data/bin/htremap
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Quick and dirty script to remap all_* keys to the new naming convention:
|
4
|
+
# - all_{hosts,tags} => all_{hosts,tags}_full,
|
5
|
+
# - all_{hosts,tags}_noskip => all_{hosts,tags}
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
11
|
+
require 'hosttag/server'
|
12
|
+
require 'hosttag'
|
13
|
+
include Hosttag
|
14
|
+
|
15
|
+
defaults = {}
|
16
|
+
|
17
|
+
def die(error)
|
18
|
+
puts error
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_options(options)
|
23
|
+
opts = OptionParser.new
|
24
|
+
opts.banner = "Usage: htimport [options]"
|
25
|
+
opts.on('-?', '-h', '--help') do
|
26
|
+
puts opts
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
opts.on('-h', '--help', '-?', 'Show this usage information') do
|
30
|
+
die(opts)
|
31
|
+
end
|
32
|
+
opts.on('-s=ARG', '--server=ARG', String, 'Server hostname to connect to. Default: hosttag') do |val|
|
33
|
+
options[:server] = val
|
34
|
+
end
|
35
|
+
opts.on('-p=ARG', '--port=ARG', Integer, 'Server port to connect to. Default: 6379') do |val|
|
36
|
+
options[:port] = val
|
37
|
+
end
|
38
|
+
opts.on('--ns=STR', '--namespace=STR', String, 'Namespace from which to dump hosttag data. Default: hosttag') do |val|
|
39
|
+
options[:namespace] = val
|
40
|
+
end
|
41
|
+
|
42
|
+
# Parse options
|
43
|
+
begin
|
44
|
+
args = opts.parse(ARGV)
|
45
|
+
rescue => e
|
46
|
+
puts "Error: " << e
|
47
|
+
usage(opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
if args.length > 0
|
51
|
+
puts "Error: unexpected arguments '#{args}'"
|
52
|
+
usage(opts)
|
53
|
+
end
|
54
|
+
|
55
|
+
return options
|
56
|
+
end
|
57
|
+
|
58
|
+
def usage(opts)
|
59
|
+
puts opts
|
60
|
+
exit
|
61
|
+
end
|
62
|
+
|
63
|
+
# ----------------------------------------------------------------------------
|
64
|
+
# Main
|
65
|
+
|
66
|
+
options = parse_options( defaults )
|
67
|
+
r = Hosttag::Server.new(options)
|
68
|
+
|
69
|
+
key = r.get_key('all_hosts_noskip')
|
70
|
+
if not r.exists(key)
|
71
|
+
puts "No 'all_hosts_noskip' key found - aborting"
|
72
|
+
exit 1
|
73
|
+
end
|
74
|
+
|
75
|
+
%w{all_hosts all_tags}.each do |all_x|
|
76
|
+
key = r.get_key(all_x)
|
77
|
+
return unless r.exists(key)
|
78
|
+
members = r.smembers(key)
|
79
|
+
new_key = r.get_key("#{all_x}_full")
|
80
|
+
members.each { |m| r.sadd(new_key, m) }
|
81
|
+
r.del(key)
|
82
|
+
end
|
83
|
+
|
84
|
+
%w{all_hosts_noskip all_tags_noskip}.each do |all_x|
|
85
|
+
key = r.get_key(all_x)
|
86
|
+
return unless r.exists(key)
|
87
|
+
members = r.smembers(key)
|
88
|
+
all_x_new = all_x.sub(/_noskip$/, '')
|
89
|
+
new_key = r.get_key(all_x_new)
|
90
|
+
members.each { |m| r.sadd(new_key, m) }
|
91
|
+
r.del(key)
|
92
|
+
end
|
93
|
+
|