fbomb 1.0.0 → 2.0.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,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__