fbomb 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDhkZDQ4ZGYyMGNkOWFhOTRiZmI0NjUyYzYxNWRhMjZlOTE1NjQzMA==
5
+ data.tar.gz: !binary |-
6
+ MGIzOTkyM2NmM2EyZTZmOGIyYTA4MDZjNzQ2ZWYyNzZhODFjMmM5MA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ OTI0MGQ2MWExNWY3ZGY5MmRhYTg2YTRiN2EwNDgzOTkzM2UxYzFiM2ZkMWQw
10
+ OTRkNzEwMzRjZjI1NjcxZTAwOGFkOWRhMDRmNGM2MjU5NTczYjhhOTE5Yzk5
11
+ YzJiNTlkNTg3MGI4MzdiODE3NzNlNGNlNDU0ZTlmZGM4NjI1MjI=
12
+ data.tar.gz: !binary |-
13
+ MTQ2NjM0MTI0Yjg5NzFmMmIyNTliMDYwNWU1MDRiZTEwYTM0NTUxNWFmNWY4
14
+ M2MwMGMzNDliOWY4YTQ5YzU5OGUwNmI4NDM3N2UzMmZlOTkzZTg1YTIwZjQx
15
+ MzI3YmY2ZWQzODVhYjQ3NWIzZGE1NDZkMGVlYzVkOGI1ODkwZjc=
data/Gemfile ADDED
@@ -0,0 +1,31 @@
1
+ source 'https://rubygems.org'
2
+
3
+
4
+ gem(*["flowdock", ">= 0.4.0"])
5
+
6
+ gem(*["eventmachine", ">= 1.0.3"])
7
+
8
+ gem(*["em-http-request", ">= 1.1.2"])
9
+
10
+ gem(*["json", ">= 1.8.1"])
11
+
12
+ gem(*["coerce", ">= 0.0.6"])
13
+
14
+ gem(*["fukung", ">= 1.1.0"])
15
+
16
+ gem(*["main", ">= 4.7.6"])
17
+
18
+ gem(*["nokogiri", ">= 1.5.0"])
19
+
20
+ gem(*["google-search", ">= 1.0.2"])
21
+
22
+ gem(*["unidecode", ">= 1.0.0"])
23
+
24
+ gem(*["systemu", ">= 2.3.0"])
25
+
26
+ gem(*["pry", ">= 0.9.6.2"])
27
+
28
+ gem(*["mechanize", ">= 2.7.3"])
29
+
30
+ gem(*["mime-types", ">= 1.16"])
31
+
@@ -0,0 +1,93 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ addressable (2.3.6)
5
+ arrayfields (4.9.2)
6
+ chronic (0.10.2)
7
+ coderay (1.1.0)
8
+ coerce (0.0.6)
9
+ chronic (>= 0.6.2)
10
+ cookiejar (0.3.2)
11
+ domain_name (0.5.18)
12
+ unf (>= 0.0.5, < 1.0.0)
13
+ em-http-request (1.1.2)
14
+ addressable (>= 2.3.4)
15
+ cookiejar
16
+ em-socksify (>= 0.3)
17
+ eventmachine (>= 1.0.3)
18
+ http_parser.rb (>= 0.6.0)
19
+ em-socksify (0.3.0)
20
+ eventmachine (>= 1.0.0.beta.4)
21
+ eventmachine (1.0.3)
22
+ fattr (2.2.2)
23
+ flowdock (0.4.0)
24
+ httparty (~> 0.7)
25
+ multi_json
26
+ fukung (1.1.0)
27
+ launchy
28
+ google-search (1.0.3)
29
+ json
30
+ http-cookie (1.0.2)
31
+ domain_name (~> 0.5)
32
+ http_parser.rb (0.6.0)
33
+ httparty (0.13.1)
34
+ json (~> 1.8)
35
+ multi_xml (>= 0.5.2)
36
+ json (1.8.1)
37
+ launchy (2.4.2)
38
+ addressable (~> 2.3)
39
+ main (6.0.0)
40
+ arrayfields (>= 4.7.4)
41
+ chronic (>= 0.6.2)
42
+ fattr (>= 2.2.0)
43
+ map (>= 5.1.0)
44
+ map (6.5.3)
45
+ mechanize (2.7.3)
46
+ domain_name (~> 0.5, >= 0.5.1)
47
+ http-cookie (~> 1.0)
48
+ mime-types (~> 2.0)
49
+ net-http-digest_auth (~> 1.1, >= 1.1.1)
50
+ net-http-persistent (~> 2.5, >= 2.5.2)
51
+ nokogiri (~> 1.4)
52
+ ntlm-http (~> 0.1, >= 0.1.1)
53
+ webrobots (>= 0.0.9, < 0.2)
54
+ method_source (0.8.2)
55
+ mime-types (2.2)
56
+ mini_portile (0.5.3)
57
+ multi_json (1.10.0)
58
+ multi_xml (0.5.5)
59
+ net-http-digest_auth (1.4)
60
+ net-http-persistent (2.9.4)
61
+ nokogiri (1.6.1)
62
+ mini_portile (~> 0.5.0)
63
+ ntlm-http (0.1.1)
64
+ pry (0.9.12.6)
65
+ coderay (~> 1.0)
66
+ method_source (~> 0.8)
67
+ slop (~> 3.4)
68
+ slop (3.5.0)
69
+ systemu (2.6.4)
70
+ unf (0.1.4)
71
+ unf_ext
72
+ unf_ext (0.0.6)
73
+ unidecode (1.0.0)
74
+ webrobots (0.1.1)
75
+
76
+ PLATFORMS
77
+ ruby
78
+
79
+ DEPENDENCIES
80
+ coerce (>= 0.0.6)
81
+ em-http-request (>= 1.1.2)
82
+ eventmachine (>= 1.0.3)
83
+ flowdock (>= 0.4.0)
84
+ fukung (>= 1.1.0)
85
+ google-search (>= 1.0.2)
86
+ json (>= 1.8.1)
87
+ main (>= 4.7.6)
88
+ mechanize (>= 2.7.3)
89
+ mime-types (>= 1.16)
90
+ nokogiri (>= 1.5.0)
91
+ pry (>= 0.9.6.2)
92
+ systemu (>= 2.3.0)
93
+ unidecode (>= 1.0.0)
@@ -0,0 +1,130 @@
1
+ NAME
2
+ ====
3
+ fbomb
4
+
5
+ SYNOPSIS
6
+ ========
7
+ fbomb is the dangerous, easily customizable flowdock robot
8
+
9
+ TL;DR;
10
+ ======
11
+ ```bash
12
+
13
+ ~> ./bin/fbomb setup
14
+
15
+ # edit your config
16
+
17
+ ~> ./bin/fbomb start
18
+
19
+ # now a daemon is running in your flowz, try typing .help in the flow!
20
+
21
+ ```
22
+
23
+ DESCRIPTION
24
+ ===========
25
+
26
+ after setting the appropriate api tokens and config you'll have a robot
27
+ running in your flow that does all sorts of irritating stuff. to find out
28
+ what he does just type _.help_
29
+
30
+ ![](http://cl.ly/VUDV/Screen%20Shot%202014-05-12%20at%2012.49.06%20PM.png)
31
+
32
+ there are two sets of built-in commands, system commands
33
+
34
+ https://github.com/ahoward/fbomb/blob/master/lib/fbomb/commands/system.rb
35
+
36
+ and a bunch of canned ones, most of which actually work ;-)
37
+
38
+ https://github.com/ahoward/fbomb/blob/master/lib/fbomb/commands/builtin.rb
39
+
40
+ the dsl for adding commands is super, super simple, you just
41
+
42
+ ```ruby
43
+
44
+ command(:my_command_name) do
45
+ call do |*args|
46
+
47
+ speak 'stuff'
48
+
49
+ paste 'stuff'
50
+
51
+ end
52
+ end
53
+
54
+ ```
55
+
56
+ commands can be loaded from a file by putting something like this in your
57
+ config
58
+
59
+ ```yaml
60
+
61
+ commands:
62
+
63
+ - system
64
+ - builtin
65
+ - ~/.fbomb/commands.rb
66
+
67
+ ```
68
+
69
+ the interpolation of these paths is the only sane one
70
+
71
+ - realtive to the libdir of the fbomb gem if bare (not starting with ~ or ./ or /)
72
+ - otherwise an expanded path in the fs
73
+ - otherwise a url (yep, it evaluates code from a url - sweet huh?)
74
+
75
+ in the config above you'll notice the '~/.fbomb' path. by default fbomb keeps
76
+ all it's state in '~/.fbomb' including it's logs, pid files, config, etc.
77
+ this is therefore a great place to keep commands on your server. if you gem
78
+ install fbomb it's the dot directory, and nothing more, you'd probably want in
79
+ your repo.
80
+
81
+ in dojo4's we have command to list our people and txt message them. here is a
82
+ litle example of that custom command(s)
83
+
84
+ https://gist.github.com/ahoward/d31fc57067a15c0387bf
85
+
86
+ the fbomb tool can be installed with
87
+
88
+ ```bash
89
+
90
+ ~> gem install fbomb
91
+
92
+ ```
93
+
94
+ and the cli is super well behaved, like all main.rb scripts
95
+
96
+
97
+
98
+ TIPS
99
+ ====
100
+
101
+ ```bash
102
+
103
+ # run in debug mode to emulate via stdin/stdout based controls
104
+
105
+ ~> FBOMB_DEBUG=42 fbomb run
106
+
107
+ ```
108
+
109
+ ```bash
110
+
111
+ # start an iteractive shell in a live flow!
112
+
113
+ ~> fbomb shell
114
+
115
+ ```
116
+
117
+ ```cron
118
+
119
+ # use cron to drop shit in your flow
120
+
121
+ * 12 * * * * fbomb speak 'time for stand-up bitches!' --tag stand-up
122
+
123
+ ```
124
+
125
+
126
+ DOCS
127
+ ====
128
+ RFTC @
129
+ - https://github.com/ahoward/fbomb/tree/master/lib
130
+ - https://github.com/ahoward/fbomb/blob/master/bin/fbomb
data/Rakefile CHANGED
@@ -3,6 +3,9 @@ This.author = "Ara T. Howard"
3
3
  This.email = "ara.t.howard@gmail.com"
4
4
  This.homepage = "https://github.com/ahoward/#{ This.lib }"
5
5
 
6
+ task :license do
7
+ open('LICENSE', 'w'){|fd| fd.puts "Ruby"}
8
+ end
6
9
 
7
10
  task :default do
8
11
  puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
@@ -29,7 +32,7 @@ def run_tests!(which = nil)
29
32
 
30
33
  test_rbs.each_with_index do |test_rb, index|
31
34
  testno = index + 1
32
- command = "#{ This.ruby } -I ./lib -I ./test/lib #{ test_rb }"
35
+ command = "#{ This.ruby } -w -I ./lib -I ./test/lib #{ test_rb }"
33
36
 
34
37
  puts
35
38
  say(div, :color => :cyan, :bold => true)
@@ -60,7 +63,7 @@ end
60
63
  task :gemspec do
61
64
  ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
62
65
  ignore_directories = ['pkg']
63
- ignore_files = ['test/log', 'a.rb'] + Dir['db/*'] + %w'db'
66
+ ignore_files = ['test/log']
64
67
 
65
68
  shiteless =
66
69
  lambda do |list|
@@ -87,9 +90,10 @@ task :gemspec do
87
90
  files = shiteless[Dir::glob("**/**")]
88
91
  executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
89
92
  #has_rdoc = true #File.exist?('doc')
90
- test_files = test(?e, "test/#{ lib }.rb") ? "test/#{ lib }.rb" : nil
93
+ test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb")
91
94
  summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass"
92
95
  description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass"
96
+ license = object.respond_to?(:license) ? object.license : "Ruby"
93
97
 
94
98
  if This.extensions.nil?
95
99
  This.extensions = []
@@ -100,7 +104,6 @@ task :gemspec do
100
104
  end
101
105
  extensions = [extensions].flatten.compact
102
106
 
103
- # TODO
104
107
  if This.dependencies.nil?
105
108
  dependencies = []
106
109
  else
@@ -127,6 +130,7 @@ task :gemspec do
127
130
  spec.platform = Gem::Platform::RUBY
128
131
  spec.summary = <%= lib.inspect %>
129
132
  spec.description = <%= description.inspect %>
133
+ spec.license = <%= license.inspect %>
130
134
 
131
135
  spec.files =\n<%= files.sort.pretty_inspect %>
132
136
  spec.executables = <%= executables.inspect %>
@@ -167,6 +171,36 @@ task :gem => [:clean, :gemspec] do
167
171
  This.gem = File.join(This.pkgdir, File.basename(gem))
168
172
  end
169
173
 
174
+ task :gemfile do
175
+ if This.dependencies.nil?
176
+ dependencies = []
177
+ else
178
+ case This.dependencies
179
+ when Hash
180
+ dependencies = This.dependencies.values
181
+ when Array
182
+ dependencies = This.dependencies
183
+ end
184
+ end
185
+
186
+ template =
187
+ if test(?e, 'gemfile.erb')
188
+ Template{ IO.read('gemfile.erb') }
189
+ else
190
+ Template {
191
+ <<-__
192
+ source 'https://rubygems.org'
193
+
194
+ <% dependencies.each do |lib_version| %>
195
+ gem(*<%= Array(lib_version).flatten.inspect %>)
196
+ <% end %>
197
+ __
198
+ }
199
+ end
200
+
201
+ IO.binwrite('Gemfile', template)
202
+ end
203
+
170
204
  task :readme do
171
205
  samples = ''
172
206
  prompt = '~ > '
@@ -188,8 +222,8 @@ task :readme do
188
222
  end
189
223
 
190
224
  template =
191
- if test(?e, 'readme.erb')
192
- Template{ IO.read('readme.erb') }
225
+ if test(?e, 'README.erb')
226
+ Template{ IO.read('README.erb') }
193
227
  else
194
228
  Template {
195
229
  <<-__
@@ -275,9 +309,7 @@ BEGIN {
275
309
  unless version
276
310
  require "./lib/#{ This.lib }"
277
311
  This.name = lib.capitalize
278
- const = Object.constants.detect{|const| const =~ /\A#{ This.name }\Z/i}
279
- abort("no module exported for #{ This.name }") unless const
280
- This.object = eval(const)
312
+ This.object = eval(This.name)
281
313
  version = This.object.send(:version)
282
314
  end
283
315
  This.version = version
data/bin/fbomb CHANGED
@@ -4,76 +4,230 @@ Main {
4
4
  ##
5
5
  #
6
6
  edit_config_file! <<-__
7
- campfire:
8
- domain: YOUR_CAMPFIRE_DOMAIN
9
- token: YOUR_CAMPFIRE_API_TOKEN
10
- room: YOUR_CAMPFIRE_ROOM_NAME
7
+ flowdock:
8
+ organizaion: YOUR_ORGANIZATION
9
+ flow: YOUR_FLOW
10
+ token: YOUR_PERSONAL_AS_OPPOSED_TO_FLOW_BASED_TOKEN
11
+
12
+ flowdock:
13
+ organizaion: YOUR_ORGANIZATION
14
+ flow: YOUR_FLOW
15
+ token: TOP_LEVEL_TOKEN_FOUND_HERE # http://cl.ly/VU6j/Screen%20Shot%202014-05-12%20at%2012.36.39%20PM.png
16
+
17
+ options:
18
+ client:
19
+ api_token: FLOW_LEVEL_API_TOKEN_FOUND_HERE # http://cl.ly/VTKt/Screen%20Shot%202014-05-12%20at%2012.37.35%20PM.png
20
+ source: arbitrary-application-name-without-spaces
21
+ external_user_name: arbitrary-roboto-name-without-spaces
22
+ from:
23
+ name: Robot First Last Name
24
+ email: robot's email (must have access!)
11
25
 
12
26
  commands:
13
27
  - system
14
28
  - builtin
15
29
  __
16
30
 
31
+
32
+ option('--force', '-f')
33
+
17
34
  ##
18
35
  #
19
36
  def run
20
37
  load_commands!
38
+ load_flow!
39
+ run_command! if argv.first =~ %r|^#{ Regexp.escape(FBomb.leader) }| unless argv.empty?
21
40
  end
22
41
 
23
42
  def load_commands!
24
- @commands = FBomb::Command.load(config[:commands])
25
- run_command! if argv.first =~ %r|^/| unless argv.empty?
43
+ FBomb::Command.load(config[:commands])
44
+ end
45
+
46
+ def load_flow!
47
+ organizaion, flow, token =
48
+ Map.for(config[:flowdock]).slice(:organizaion, :flow, :token).values
49
+
50
+ options = config.get(:flowdock, :options)
51
+
52
+ @flowdock = FBomb::Flowdock.new(organizaion, flow, token, options)
53
+
54
+ @flow = @flowdock.flow
55
+
56
+ FBomb::Command.flow = @flow
26
57
  end
27
58
 
28
59
  def run_command!
29
- path, args = argv
60
+ path, *args = argv
30
61
  commands = FBomb::Command.table
31
62
  command = commands[path] or abort("no such command #{ path }")
63
+ #FBomb::Command.flow = nil unless params['force'].given?
64
+ stdin = read_standard_input()
65
+ (args << stdin.chomp) if stdin
32
66
  command.call(*args)
33
67
  exit
34
68
  end
35
69
 
70
+ def read_standard_input
71
+ require 'io/wait'
72
+ input = nil
73
+ return input unless STDIN.ready?
74
+ loop do
75
+ begin
76
+ buf = STDIN.readpartial(8192)
77
+ input ||= ''
78
+ input << buf
79
+ rescue EOFError
80
+ break
81
+ end
82
+ end
83
+ input
84
+ end
85
+
36
86
  ##
37
87
  #
88
+ mode(:shell) do
89
+ def run
90
+ load_commands!
91
+ load_flow!
92
+
93
+ require 'irb'
94
+
95
+ $FUCKING_HACK = IRB.method(:load_modules)
96
+ $FLOWDOCK = @flowdock
97
+
98
+ def IRB.load_modules
99
+ $FUCKING_HACK.call()
100
+
101
+ prompt = "#{ $FLOWDOCK.organization }/#{ $FLOWDOCK.flow }"
102
+
103
+ IRB.conf[:PROMPT][:RO] = {
104
+ :PROMPT_I=>"#{ prompt }:%03n:%i> ",
105
+ :PROMPT_N=>"#{ prompt }:%03n:%i> ",
106
+ :PROMPT_S=>"#{ prompt }:%03n:%i%l ",
107
+ :PROMPT_C=>"#{ prompt }:%03n:%i* ",
108
+ :RETURN=>"=> %s\n"
109
+ }
110
+
111
+ IRB.conf[:PROMPT_MODE] = :RO
112
+ IRB.conf[:AUTO_INDENT] = true
113
+ end
114
+
115
+ Kernel.module_eval do
116
+ def flowdock
117
+ $FLOWDOCK
118
+ end
119
+
120
+ def flow
121
+ flowdock.flow
122
+ end
123
+ end
124
+
125
+ ARGV.clear
126
+
127
+ ::IRB.start
128
+ end
129
+
130
+ def method_missing(method, *args, &block)
131
+ ivar = "@#{ method }"
132
+ super unless instance_variable_defined?(ivar)
133
+ instance_variable_get(ivar)
134
+ end
135
+ end
136
+
137
+ ##
138
+ #
139
+ mode(:stop) do
140
+ def run
141
+ pid = IO.read(File.join(dotdir, 'pid')).to_i
142
+ Process.kill(-9, pid)
143
+ rescue
144
+ nil
145
+ end
146
+ end
147
+
148
+ mode(:pid) do
149
+ def run
150
+ pid = IO.read(File.join(dotdir, 'pid')).to_i
151
+ puts pid
152
+ rescue
153
+ nil
154
+ end
155
+ end
156
+
157
+ mode(:speak) do
158
+ option('--tag=tag', '-t')
159
+
160
+ def run
161
+ speak!
162
+ end
163
+ end
164
+
165
+ mode(:paste) do
166
+ option('--tag=tag', '-t')
167
+
168
+ def run
169
+ paste!
170
+ end
171
+ end
172
+
38
173
  mode(:run) do
174
+ option('--daemon', '-d')
175
+
39
176
  def run
177
+ if params['daemon'].given?
178
+ DATA.flock(File::LOCK_EX|File::LOCK_NB) or exit!(42)
179
+ Dir.chdir('/tmp')
180
+ exit! if fork
181
+ exit! if fork
182
+ open(File.join(dotdir, 'pid'), 'w'){|fd| fd.write($$)}
183
+ open('/dev/null', 'w+') do |fd|
184
+ STDOUT.reopen(fd)
185
+ STDERR.reopen(fd)
186
+ end
187
+ end
188
+
40
189
  load_commands!
190
+ load_flow!
41
191
  drop_fbombs!
42
192
  end
43
193
 
44
194
  def drop_fbombs!
45
- domain, token, room = config[:campfire].slice(:domain, :token, :room).values
46
- campfire = FBomb::Campfire.new(domain, :token => token)
47
- room = campfire.room_for(room)
48
- room.join
49
- at_exit{ room.leave }
50
- room.speak("fbomb in da house...")
51
- id = room.id
195
+ at_exit{ @flow.leave }
52
196
 
53
- FBomb::Command.room = room
54
- url = URI.parse("http://#{ token }:x@streaming.campfirenow.com//room/#{ id }/live.json")
197
+ @flow.speak("fbomb (beta) in da house...")
55
198
 
56
199
  trap('INT'){ exit }
57
200
 
201
+ =begin
202
+ {"event"=>"activity.user", "tags"=>[], "uuid"=>nil, "persist"=>false, "id"=>194342, "flow"=>"c6dbc029-2173-4fb6-a423-32293c373106", "content"=>{"last_activity"=>1399656686378}, "sent"=>1399657205286, "app"=>nil, "attachments"=>[], "user"=>"76002"}
203
+ =end
58
204
  loop do
59
205
  logging_errors do
60
- Yajl::HttpStream.get(url) do |message|
61
- case message['type'].to_s
62
- when 'TextMessage'
63
- body = message['body'].to_s
64
- tokens = body.scan(%r/[^\s]+/)
65
- arg, *args = tokens
66
-
67
- if arg =~ %r|^\s*/|
68
- path = arg.strip
69
- command = @commands[path]
70
- if command
71
- logging_errors do
72
- logger.info("#{ path } #{ args.join(' ') }")
73
- command.call(*args)
74
- end
206
+ @flow.stream do |flow|
207
+ #p :flow => flow
208
+ next unless flow[:event] == 'message'
209
+
210
+ content = flow[:content].to_s
211
+ tokens = content.scan(%r/[^\s]+/)
212
+ arg, *args = tokens
213
+
214
+ if arg =~ %r|^\s*#{ Regexp.escape(FBomb.leader) }|
215
+ key = arg.strip
216
+ command = FBomb::Command.table[key]
217
+
218
+ if command
219
+ user = @flow.user_for(flow[:user])
220
+ #p :user => user
221
+ FBomb::Command.user = user
222
+ begin
223
+ logging_errors do
224
+ logger.info("#{ key } #{ args.join(' ') }")
225
+ command.call(*args)
75
226
  end
227
+ ensure
228
+ FBomb::Command.user = nil
76
229
  end
230
+ end
77
231
  end
78
232
  end
79
233
 
@@ -102,6 +256,34 @@ Main {
102
256
  logger.error("#{ m }(#{ c })\n#{ b }")
103
257
  end
104
258
  end
259
+
260
+ def speak_or_paste!(which)
261
+ load_commands!
262
+ load_flow!
263
+
264
+ tags =
265
+ if params['tag'] and params['tag'].given?
266
+ Coerce.list_of_strings(params['tag'].value)
267
+ else
268
+ []
269
+ end
270
+
271
+ content = ARGV.join(' ')
272
+
273
+ if content.strip == '-'
274
+ content = STDIN.read
275
+ end
276
+
277
+ @flow.send(which, content, :tags => tags)
278
+ end
279
+
280
+ def speak!
281
+ speak_or_paste!(:speak)
282
+ end
283
+
284
+ def paste!
285
+ speak_or_paste!(:paste)
286
+ end
105
287
  }
106
288
 
107
289
  BEGIN{
@@ -110,4 +292,17 @@ BEGIN{
110
292
  libdir = File.join(srcdir, 'lib')
111
293
  lib = File.join(libdir, 'fbomb.rb')
112
294
  require(test(?s, lib) ? lib : 'fbomb')
295
+
296
+
297
+ class Map
298
+ def slice(*keys)
299
+ keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
300
+ hash = self.class.new
301
+ keys.each { |k| hash[k] = self[k] if has_key?(k) }
302
+ hash
303
+ end
304
+ end
113
305
  }
306
+
307
+
308
+ __END__