chump 0.5.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.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +3 -0
- data/chump.gemspec +13 -0
- data/lib/chump.rb +407 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e1be23f0e3418c51c4d7f92ef46ac380670656ef17f4e5b0e2f8401654cdfc14
|
4
|
+
data.tar.gz: 80117945172f5bbe8a1e7bd5d577ea6a6b941817688e341dfeae43dda5f3e52d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8b510b9f2d850d4a2a598de5082a4491e6ff44d6df24f97a58baa485d67c9c960728684ac56508f3e9268eb3727ecfcfa8c2c311e5f558a6f8ee413e72864999
|
7
|
+
data.tar.gz: 06da95fa869d47cd80cceef849933a875b6def6199c14b20f2a3448ff5d367d3904492c4d0ef9b53922aea8a2882731de8900c77b48b03f4c2f197eeef4bc574
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 Steve Shreeve
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/chump.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "chump"
|
5
|
+
s.version = "0.5.0"
|
6
|
+
s.author = "Steve Shreeve"
|
7
|
+
s.email = "steve.shreeve@gmail.com"
|
8
|
+
s.summary = "Chump is an interactive session scripting tool"
|
9
|
+
s.description = "Chump can be used to easily script terminal interactions."
|
10
|
+
s.homepage = "https://github.com/shreeve/chump"
|
11
|
+
s.license = "MIT"
|
12
|
+
s.files = `git ls-files`.split("\n") - %w[.gitignore]
|
13
|
+
end
|
data/lib/chump.rb
ADDED
@@ -0,0 +1,407 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# =============================================================================
|
4
|
+
# chump.rb: Expect-like utility for automating interactive sessions
|
5
|
+
#
|
6
|
+
# Steve Shreeve <steve.shreeve@gmail.com>
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation; version 2 of the License.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
# =============================================================================
|
17
|
+
|
18
|
+
require 'socket'
|
19
|
+
|
20
|
+
STDIN.sync = STDOUT.sync = STDERR.sync = true
|
21
|
+
UNIX = RUBY_PLATFORM =~ /linux|darwin/
|
22
|
+
|
23
|
+
class IO
|
24
|
+
def self.socketpair(sync=true)
|
25
|
+
if UNIX
|
26
|
+
one, two = UNIXSocket.socketpair
|
27
|
+
else
|
28
|
+
tcp = TCPServer.new('127.0.0.1', 0)
|
29
|
+
one = TCPSocket.new('127.0.0.1', tcp.addr[1])
|
30
|
+
two = tcp.accept and tcp.close
|
31
|
+
end
|
32
|
+
one.sync = two.sync = true if sync
|
33
|
+
[one, two]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Chump
|
38
|
+
attr_accessor :slow
|
39
|
+
|
40
|
+
def self.connect(url, opts={})
|
41
|
+
scheme, _, target, *info = url.split('/') # scheme://target/info
|
42
|
+
target =~ /^(?:(\w+)(?::([^@]+))?@)?(?:([^:]+)?(?::(\d+))?)$/
|
43
|
+
user, pass, host, port = $1,$2,$3,$4 # user:pass@host:port
|
44
|
+
|
45
|
+
opts[:url ] = "#{scheme}//" << [user, host].compact.join('@')
|
46
|
+
opts[:url ] << ":#{port}" if host && port
|
47
|
+
opts[:url ] << ['', *info].compact.join('/')
|
48
|
+
opts[:info] = info unless info.empty?
|
49
|
+
|
50
|
+
case scheme
|
51
|
+
when 'spawn:' then spawn(target, opts)
|
52
|
+
when 'ssh:' then ssh(host, port, user, pass, opts)
|
53
|
+
when 'tcp:' then tcp(host, port, user, pass, opts)
|
54
|
+
else abort "can't parse #{url.inspect}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.spawn(cmd=nil, opts={})
|
59
|
+
io, child_io = IO.socketpair
|
60
|
+
|
61
|
+
if UNIX
|
62
|
+
require 'pty'
|
63
|
+
cmd = ENV['SHELL'].dup if !cmd || cmd.empty?
|
64
|
+
cmd << '; cat -' # hack to keep program running, anything better?
|
65
|
+
reader, writer, pid = PTY.spawn(cmd); reader.sync = writer.sync = true
|
66
|
+
Thread.new { child_io.syswrite(reader.sysread(1 << 16)) while true }
|
67
|
+
Thread.new { writer.syswrite(child_io.sysread(1 << 16)) while true }
|
68
|
+
at_exit do
|
69
|
+
Process.kill(9, pid)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
require 'win32/process'
|
73
|
+
child = Process.create(
|
74
|
+
'app_name' => "cmd /k #{cmd}",
|
75
|
+
'process_inherit' => true, #!# is this needed?
|
76
|
+
'thread_inherit' => true, #!# is this needed?
|
77
|
+
'startup_info' => {
|
78
|
+
'stdin' => child_io,
|
79
|
+
'stdout' => child_io,
|
80
|
+
'stderr' => File.open('nul', 'wb') # ignore STDERR (what about sync? close?)
|
81
|
+
}
|
82
|
+
)
|
83
|
+
at_exit do
|
84
|
+
Process.TerminateProcess(child.process_handle, child.process_id)
|
85
|
+
Process.CloseHandle(child.process_handle)
|
86
|
+
end
|
87
|
+
child_io.close
|
88
|
+
end
|
89
|
+
|
90
|
+
opts[:io] ? io : new(io, opts)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.ssh(host, port=nil, user=nil, pass=nil, cmd=nil, opts={})
|
94
|
+
io, child_io = IO.socketpair
|
95
|
+
|
96
|
+
require 'net/ssh'
|
97
|
+
ENV['HOME'] ||= ENV['USERPROFILE'] unless UNIX
|
98
|
+
options = {}; options[:pass] = pass if pass; options[:port] = port if port
|
99
|
+
ssh = Net::SSH.start(host||'localhost', user||ENV['USER']||ENV['USERNAME'], options)
|
100
|
+
ssh.open_channel do |channel|
|
101
|
+
channel.request_pty do |ch, success|
|
102
|
+
raise "can't get pty" unless success
|
103
|
+
end
|
104
|
+
channel.send_channel_request "shell" do |ch, success|
|
105
|
+
raise "can't start shell" unless success
|
106
|
+
ch.send_data "#{cmd}\r" if cmd && !cmd.empty?
|
107
|
+
Thread.new do
|
108
|
+
loop do
|
109
|
+
if select([child_io], nil, nil, 0.25)
|
110
|
+
data = child_io.sysread(1 << 16) or raise "can't read from child_io"
|
111
|
+
ch.send_data(data)
|
112
|
+
ssh.process
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
channel.on_data do |ch, data|
|
118
|
+
child_io.syswrite(data)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
Thread.new { ssh.loop }
|
122
|
+
|
123
|
+
opts[:io] ? io : new(io, opts)
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.tcp(host, port, user=nil, pass=nil, opts={})
|
127
|
+
user,opts = nil,user if user.is_a?(Hash) && pass==nil && opts.empty? # allow short calls
|
128
|
+
host ||= "127.0.0.1"
|
129
|
+
port ||= 23
|
130
|
+
pass ||= "" if user
|
131
|
+
|
132
|
+
io = TCPSocket.new(host, port.to_i)
|
133
|
+
|
134
|
+
opts[:auth] ||= {
|
135
|
+
# /\xFF\xFD(.)/ => proc { [:pure, "\xFF\xFC#{$1}" ] }, # reject these (do -> wont)
|
136
|
+
# /\xFF\xFB(.)/ => proc { [:pure, "\xFF\xFE#{$1}" ] }, # accept these (will -> dont)
|
137
|
+
/Log ?in|User ?name/i => [user],
|
138
|
+
/Pass ?word/i => pass,
|
139
|
+
:else => nil,
|
140
|
+
} if user && pass
|
141
|
+
|
142
|
+
opts[:io] ? io : new(io, opts)
|
143
|
+
end
|
144
|
+
|
145
|
+
def initialize(io, opts={})
|
146
|
+
opts.empty? or opts.each {|k,v| opts[k.to_sym] ||= v if k.is_a?(String)}
|
147
|
+
@live = opts.has_key?(:live) ? opts[:live] : true # live reads
|
148
|
+
@nocr = opts.has_key?(:nocr) ? opts[:nocr] : true # strip "\r"
|
149
|
+
@ansi = opts.has_key?(:ansi) ? opts[:ansi] : false # allow ANSI escapes => for GT.M, but checkout "U $P:(NOECHO)"
|
150
|
+
@show = opts.has_key?(:show) ? opts[:show] : false # show matches
|
151
|
+
@echo = opts.has_key?(:echo) ? opts[:echo] : false # echo sends
|
152
|
+
@wait = opts.has_key?(:wait) ? opts[:wait] : nil # sleep times
|
153
|
+
@bomb = opts.has_key?(:bomb) ? opts[:bomb] : true # bomb on slow timeout
|
154
|
+
@slow = opts.has_key?(:slow) ? opts[:slow] : 10 # slow timeout
|
155
|
+
@fast = opts.has_key?(:fast) ? opts[:fast] : 0.25 # fast timeout
|
156
|
+
@size = opts.has_key?(:size) ? opts[:size] : 1 << 16 # buffer size
|
157
|
+
@line = opts.has_key?(:line) ? opts[:line] : "\r" # line terminator
|
158
|
+
@buff = ''
|
159
|
+
|
160
|
+
@start = Time.now
|
161
|
+
@sleep = 0.0
|
162
|
+
@final = @start
|
163
|
+
|
164
|
+
@io = io.is_a?(String) ? self.class.connect(io,opts.update(:io=>true)) : io
|
165
|
+
@io.sync = true
|
166
|
+
|
167
|
+
chat(opts.delete(:auth)) if opts[:auth] # authenticate if requested
|
168
|
+
chat(opts.delete(:init)) if opts[:init] # initialize if requested
|
169
|
+
end
|
170
|
+
|
171
|
+
def chat(*list)
|
172
|
+
return self if list.empty?
|
173
|
+
item = nil
|
174
|
+
back = nil
|
175
|
+
talk = false
|
176
|
+
fast = false
|
177
|
+
list.each do |item|
|
178
|
+
loop do
|
179
|
+
case item
|
180
|
+
when false, Symbol # notifier
|
181
|
+
back = item
|
182
|
+
case back
|
183
|
+
when :redo then break
|
184
|
+
else return back
|
185
|
+
end
|
186
|
+
break
|
187
|
+
when true, nil # continuer
|
188
|
+
back = item
|
189
|
+
talk = !talk if item.nil?
|
190
|
+
break
|
191
|
+
when String, Fixnum, Float # [literal]
|
192
|
+
item = item.to_s
|
193
|
+
if talk # talker
|
194
|
+
send(item)
|
195
|
+
back = item
|
196
|
+
talk = false
|
197
|
+
break
|
198
|
+
elsif index = @buff.index(item) # comparer
|
199
|
+
@last = item # save for future reference
|
200
|
+
back = @buff.slice!(0..(index + item.size - 1))
|
201
|
+
print back.tr("\r",'') if @show
|
202
|
+
talk = true
|
203
|
+
break
|
204
|
+
end
|
205
|
+
when Regexp # matcher
|
206
|
+
if match = @buff.match(item)
|
207
|
+
@last = match[1] || match[0] # save for future reference
|
208
|
+
@buff = match.post_match
|
209
|
+
back = [match.pre_match + match.to_s, *match.to_a[1..-1]]
|
210
|
+
print back.first.tr("\r",'') if @show
|
211
|
+
talk = true
|
212
|
+
break
|
213
|
+
else
|
214
|
+
talk = false
|
215
|
+
end
|
216
|
+
when Hash # multiplexer
|
217
|
+
item.each do |key, val|
|
218
|
+
key, val = '', item[:else] if fast
|
219
|
+
case key
|
220
|
+
when :else # insurer
|
221
|
+
next
|
222
|
+
when Symbol # yielder
|
223
|
+
case val
|
224
|
+
when String, Fixnum, Float # comparer
|
225
|
+
val = val.to_s
|
226
|
+
if index = @buff.index(val)
|
227
|
+
back = @buff.slice!(0..(index + val.size - 1))
|
228
|
+
print back.tr("\r",'') if @show
|
229
|
+
back = yield(key, back) if block_given?
|
230
|
+
break
|
231
|
+
end
|
232
|
+
when Regexp # matcher
|
233
|
+
if match = @buff.match(val)
|
234
|
+
@buff = match.post_match
|
235
|
+
back = [match.pre_match + match.to_s, *match.to_a[1..-1]]
|
236
|
+
print back.first.tr("\r",'') if @show
|
237
|
+
back = yield(key, back) if block_given?
|
238
|
+
break
|
239
|
+
end
|
240
|
+
when Array, Proc, Hash # indexer
|
241
|
+
# processed elsewhere
|
242
|
+
else
|
243
|
+
raise "Hash symbols don't support #{val.class} matchers"
|
244
|
+
end
|
245
|
+
when String, Fixnum, Float, Regexp # comparer/matcher (ugly, but shares actions)
|
246
|
+
key = key.to_s unless regx = key.is_a?(Regexp)
|
247
|
+
if fast
|
248
|
+
back = :else
|
249
|
+
elsif !regx && index = @buff.index(key)
|
250
|
+
back = @buff.slice!(0..(index + key.size - 1))
|
251
|
+
print back.tr("\r",'') if @show
|
252
|
+
elsif regx && match = @buff.match(key)
|
253
|
+
@buff = match.post_match
|
254
|
+
back = [match.pre_match + match.to_s, *match.to_a[1..-1]]
|
255
|
+
print back.first.tr("\r",'') if @show
|
256
|
+
else
|
257
|
+
regx = nil
|
258
|
+
end
|
259
|
+
unless regx.nil?
|
260
|
+
case val
|
261
|
+
when String, Fixnum, Float
|
262
|
+
send(val.to_s)
|
263
|
+
when Array
|
264
|
+
back = chat(nil, *val) unless val.empty?
|
265
|
+
back = :redo if val.size <= 1
|
266
|
+
when Proc
|
267
|
+
eval("proc {|m| $~ = m}", val.binding).call($~) if $~ # infuse proc with our match variables
|
268
|
+
back = back.is_a?(String) ? val.call(back) : val.call(*back) # don't convert embedded newlines to array
|
269
|
+
case val = back
|
270
|
+
when Array
|
271
|
+
if pure = (val.first == :pure)
|
272
|
+
line, @line = @line, ""
|
273
|
+
back = chat(nil, *val[1..-1]) unless val.size == 1
|
274
|
+
@line = line
|
275
|
+
back = :redo if val.size <= 2
|
276
|
+
else
|
277
|
+
back = chat(nil, *val) unless val.empty?
|
278
|
+
back = :redo if val.size <= 1
|
279
|
+
end
|
280
|
+
end
|
281
|
+
when false, Symbol
|
282
|
+
if val == :this
|
283
|
+
back = back.first if back.is_a?(Array) # regexps store leading + matched text in back.first
|
284
|
+
else
|
285
|
+
back = val
|
286
|
+
end
|
287
|
+
when true, nil
|
288
|
+
back = val
|
289
|
+
when Hash
|
290
|
+
back = chat(val)
|
291
|
+
else
|
292
|
+
raise "Hash literals can't multiplex to #{val.class} types"
|
293
|
+
end
|
294
|
+
break
|
295
|
+
end
|
296
|
+
else
|
297
|
+
raise "Hash items can't process #{key}.class keys"
|
298
|
+
end
|
299
|
+
end and begin # read when nothing matches
|
300
|
+
fast = read(item.has_key?(:else)) == :fast
|
301
|
+
next
|
302
|
+
end
|
303
|
+
fast &&= false
|
304
|
+
talk = false
|
305
|
+
case back
|
306
|
+
when :else then break
|
307
|
+
when :redo then redo
|
308
|
+
when :skip then return :skip
|
309
|
+
when false, Symbol then return back
|
310
|
+
end
|
311
|
+
break
|
312
|
+
when Array # walker
|
313
|
+
if item.first == :pure
|
314
|
+
ansi, @ansi = @ansi, :false
|
315
|
+
line, @line = @line, ""
|
316
|
+
back = talk ? chat(nil, *item[1..-1]) : chat(*item[1..-1])
|
317
|
+
@ansi = ansi
|
318
|
+
@line = line
|
319
|
+
else
|
320
|
+
back = talk ? chat(nil, *item) : chat(*item)
|
321
|
+
end
|
322
|
+
talk = false
|
323
|
+
break
|
324
|
+
when Proc, Method # macro
|
325
|
+
item = item.to_proc if item.class == Method
|
326
|
+
eval("proc {|m| $~ = m}", item.binding).call($~) if $~ # infuse proc with our match variables
|
327
|
+
back = back.is_a?(String) ? item.call(back) : item.call(*back) # don't convert embedded newlines to array
|
328
|
+
item = back unless back == :redo
|
329
|
+
redo
|
330
|
+
else # aborter
|
331
|
+
raise "Chump doesn't handle #{item.class} objects like: #{item.inspect}"
|
332
|
+
end
|
333
|
+
read unless talk
|
334
|
+
end
|
335
|
+
case back
|
336
|
+
when :redo then break
|
337
|
+
when :skip then break
|
338
|
+
when :false then return false # same as false in parent
|
339
|
+
when :true then return true # same as true in parent
|
340
|
+
when :nil then return nil # same as nil in parent
|
341
|
+
end
|
342
|
+
end
|
343
|
+
back
|
344
|
+
rescue Object => e
|
345
|
+
exit if defined?(PTY::ChildExited) and e.class == PTY::ChildExited
|
346
|
+
warn ['', '', "==[ #{e} ]==" ] * "\n"
|
347
|
+
warn ['', e.backtrace, ''].flatten * "\n"
|
348
|
+
warn ['', "Buffer: ", @buff.inspect] * "\n"
|
349
|
+
warn ['', "Failed: ", item.inspect ] * "\n" if item
|
350
|
+
disconnect
|
351
|
+
exit
|
352
|
+
end
|
353
|
+
|
354
|
+
alias :wait :chat
|
355
|
+
alias :[] :chat
|
356
|
+
|
357
|
+
def read(fast=false)
|
358
|
+
unless select([@io], nil, nil, fast ? @fast : @slow)
|
359
|
+
return :fast if fast
|
360
|
+
raise "Timeout" if @bomb
|
361
|
+
return :slow
|
362
|
+
end
|
363
|
+
buff = @io.sysread(@size)
|
364
|
+
buff.tr!("\r",'') if @nocr
|
365
|
+
unless @ansi
|
366
|
+
# http://www.esrl.noaa.gov/gmd/dv/hats/cats/stations/qnxman/Devansi.html
|
367
|
+
# http://support.dell.com/support/edocs/systems/SC1425/en/ug/f3593ab0.htm
|
368
|
+
buff.gsub!(/\x08/,'')
|
369
|
+
buff.gsub!(/\e[=>]/,'')
|
370
|
+
buff.gsub!(/\e\[(?>[^a-z]*)[a-z]/i,'')
|
371
|
+
end
|
372
|
+
print @nocr ? buff : buff.tr("\r",'') if @live
|
373
|
+
@buff << buff
|
374
|
+
end
|
375
|
+
|
376
|
+
def unshift(str)
|
377
|
+
Thread.exclusive { @buff = str + @buff }
|
378
|
+
end
|
379
|
+
|
380
|
+
def send(item='', *list)
|
381
|
+
if back = item
|
382
|
+
select(nil, [@io], nil, @slow) or return :slow
|
383
|
+
if @wait
|
384
|
+
prior = Time.now.to_f
|
385
|
+
sleep(@wait[0] + rand * (@wait[1] - @wait[0]))
|
386
|
+
@sleep += Time.now.to_f - prior
|
387
|
+
end
|
388
|
+
back = back.to_s
|
389
|
+
@io.syswrite(back + @line) # line ending, usually "\r"
|
390
|
+
print back.tr("\r",'') if @echo
|
391
|
+
end
|
392
|
+
back = chat(*list) unless list.empty?
|
393
|
+
back
|
394
|
+
end
|
395
|
+
|
396
|
+
def peek(*list)
|
397
|
+
list.compact.inject(:else=>false) {|h,v| h[v]=:this; h}
|
398
|
+
end
|
399
|
+
|
400
|
+
def disconnect
|
401
|
+
@stop = Time.now
|
402
|
+
print @buff.tr("\r",'') if @show
|
403
|
+
@io.close
|
404
|
+
puts
|
405
|
+
end
|
406
|
+
|
407
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chump
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Shreeve
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-28 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Chump can be used to easily script terminal interactions.
|
14
|
+
email: steve.shreeve@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".ruby-version"
|
20
|
+
- Gemfile
|
21
|
+
- LICENSE
|
22
|
+
- README.md
|
23
|
+
- chump.gemspec
|
24
|
+
- lib/chump.rb
|
25
|
+
homepage: https://github.com/shreeve/chump
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubygems_version: 3.0.6
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Chump is an interactive session scripting tool
|
48
|
+
test_files: []
|