angry_shell 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/.gitignore +4 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +16 -0
- data/Rakefile +2 -0
- data/Readme.md +51 -0
- data/angry_shell.gemspec +21 -0
- data/examples/eg.helper.rb +8 -0
- data/examples/usage.eg.rb +66 -0
- data/lib/angry_shell/version.rb +3 -0
- data/lib/angry_shell.rb +402 -0
- metadata +72 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# AngryShell
|
2
|
+
|
3
|
+
`AngryShell` makes you less angry about running shell commands from ruby.
|
4
|
+
|
5
|
+
# Usage
|
6
|
+
require 'angry_shell'
|
7
|
+
include AngryShell::ShellMethods
|
8
|
+
|
9
|
+
sh("echo Hello World").to_s #=> 'Hello World'
|
10
|
+
sh("echo Hello World").ok? #=> true
|
11
|
+
sh("echo Hello World").run #=> nil
|
12
|
+
|
13
|
+
# the command 'schmortle' doesn't exist :)
|
14
|
+
sh("schmortle").to_s #=> ''
|
15
|
+
sh("schmortle").ok? #=> false
|
16
|
+
sh("schmortle").run #=> raises Errno::ENOENT, since schmortle doesn't exist
|
17
|
+
|
18
|
+
begin
|
19
|
+
sh("sh -c 'echo hello && exit 1'").run
|
20
|
+
rescue AngryShell::ShellError
|
21
|
+
$!.result.stdout #=> "hello\n"
|
22
|
+
$!.result.ok? #=> false
|
23
|
+
end
|
24
|
+
|
25
|
+
## Duck punching
|
26
|
+
|
27
|
+
require 'angry_shell'
|
28
|
+
"echo Hello World".sh.to_s #=> 'Hello World'
|
29
|
+
|
30
|
+
## License
|
31
|
+
|
32
|
+
Copyright (c) 2010 Lachie Cox
|
33
|
+
|
34
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
35
|
+
of this software and associated documentation files (the "Software"), to deal
|
36
|
+
in the Software without restriction, including without limitation the rights
|
37
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
38
|
+
copies of the Software, and to permit persons to whom the Software is
|
39
|
+
furnished to do so, subject to the following conditions:
|
40
|
+
|
41
|
+
The above copyright notice and this permission notice shall be included in
|
42
|
+
all copies or substantial portions of the Software.
|
43
|
+
|
44
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
45
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
46
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
47
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
48
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
49
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
50
|
+
THE SOFTWARE.
|
51
|
+
|
data/angry_shell.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# $:.push File.expand_path("../lib", __FILE__)
|
3
|
+
# require "angry_shell/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "angry_shell"
|
7
|
+
s.version = '0.0.1' #AngryShell::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Lachie Cox"]
|
10
|
+
s.email = ["lachie.cox@plus2.com.au"]
|
11
|
+
s.homepage = "http://rubygems.org/gems/angry_shell"
|
12
|
+
s.summary = %q{Shell}
|
13
|
+
s.description = %q{}
|
14
|
+
|
15
|
+
s.rubyforge_project = "angry_shell"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'eg.helper'
|
2
|
+
|
3
|
+
eg 'popen4 - normal, exahust io' do
|
4
|
+
out = nil
|
5
|
+
AngryShell::Shell.new.popen4(:cmd => 'echo Hello') do |cid,ipc|
|
6
|
+
out = ipc.stdout.read
|
7
|
+
end
|
8
|
+
|
9
|
+
Assert(out == "Hello\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
eg 'popen4 - normal, stream io' do
|
13
|
+
out = nil
|
14
|
+
AngryShell::Shell.new.popen4(:cmd => 'echo Hello', :stream => true) do |cid,ipc|
|
15
|
+
out = ipc.stdout.read
|
16
|
+
end
|
17
|
+
|
18
|
+
Assert(out == "Hello\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
eg 'popen4 - error' do
|
22
|
+
begin
|
23
|
+
AngryShell::Shell.new.popen4(:cmd => 'casplortleecho Hello') do |cid,ipc|
|
24
|
+
end
|
25
|
+
raised = false
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
raised = true
|
28
|
+
end
|
29
|
+
|
30
|
+
Assert(raised)
|
31
|
+
end
|
32
|
+
|
33
|
+
eg 'run' do
|
34
|
+
AngryShell::Shell.new("echo Whats happening").run
|
35
|
+
Assert( :didnt_raise )
|
36
|
+
end
|
37
|
+
|
38
|
+
eg 'ok?' do
|
39
|
+
Assert( AngryShell::Shell.new("echo Whats happening").ok? )
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
eg 'to_s' do
|
44
|
+
Assert( AngryShell::Shell.new("echo -n Whats happening").to_s == "Whats happening" )
|
45
|
+
end
|
46
|
+
|
47
|
+
eg.helpers do
|
48
|
+
include AngryShell::ShellMethods
|
49
|
+
end
|
50
|
+
|
51
|
+
eg 'helper' do
|
52
|
+
Assert( sh("echo Something").to_s == "Something" )
|
53
|
+
end
|
54
|
+
|
55
|
+
eg 'helper - error' do
|
56
|
+
raised = false
|
57
|
+
begin
|
58
|
+
sh("sh -c 'echo hello && exit 1'").run
|
59
|
+
rescue AngryShell::ShellError
|
60
|
+
raised = true
|
61
|
+
Assert( $!.result.stdout == "hello\n" )
|
62
|
+
Assert( ! $!.result.ok? )
|
63
|
+
end
|
64
|
+
|
65
|
+
Assert( raised )
|
66
|
+
end
|
data/lib/angry_shell.rb
ADDED
@@ -0,0 +1,402 @@
|
|
1
|
+
## AngryShell
|
2
|
+
# `AngryShell` makes you less angry about running shell commands.
|
3
|
+
#
|
4
|
+
# AngryShell is extracted from YesMaster's CommonMob. Contains code adapted from Chef and open4.
|
5
|
+
|
6
|
+
require 'fcntl'
|
7
|
+
require 'stringio'
|
8
|
+
require 'pp'
|
9
|
+
|
10
|
+
module AngryShell
|
11
|
+
class ShellError < StandardError
|
12
|
+
attr_accessor :result
|
13
|
+
|
14
|
+
def initialize(msg,result)
|
15
|
+
@result = result
|
16
|
+
super(msg)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ShellMethods
|
21
|
+
def sh(*args,&blk)
|
22
|
+
AngryShell::Shell.new(*args,&blk)
|
23
|
+
end
|
24
|
+
|
25
|
+
# call ruby, or a ruby command *without* the environment being cleaned of bundler spooge
|
26
|
+
def bundler_sh(*args,&blk)
|
27
|
+
args.options.without_cleaning_bundler = true
|
28
|
+
AngryShell::Shell.new(*args,&blk)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Shell
|
33
|
+
def debug(*msg)
|
34
|
+
puts "sh: #{msg * ' '}"
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :options
|
38
|
+
|
39
|
+
def initialize(*args,&block)
|
40
|
+
@block = block
|
41
|
+
@options = if Hash === args.last then args.pop else {} end
|
42
|
+
|
43
|
+
unless args.empty?
|
44
|
+
@options[:cmd] = args
|
45
|
+
end
|
46
|
+
|
47
|
+
@options[:stream] = false unless @options.key?(:stream)
|
48
|
+
end
|
49
|
+
|
50
|
+
def execute
|
51
|
+
error,out = nil,nil
|
52
|
+
|
53
|
+
rv = popen4(options) {|pid,ipc|
|
54
|
+
out = ipc.stdout.read
|
55
|
+
error = ipc.stderr.read
|
56
|
+
}
|
57
|
+
|
58
|
+
rv.stderr = error
|
59
|
+
rv.stdout = out
|
60
|
+
|
61
|
+
rv
|
62
|
+
end
|
63
|
+
|
64
|
+
# runs the command, raising if it doesn't return success.
|
65
|
+
def run
|
66
|
+
execute.ensure_ok!
|
67
|
+
end
|
68
|
+
|
69
|
+
# runs the command, returning true if it returns success.
|
70
|
+
def ok?
|
71
|
+
execute.ok?
|
72
|
+
end
|
73
|
+
|
74
|
+
# runs the command, returning its `stdout`. If the command doesn't return success, return a blank string.
|
75
|
+
def to_s
|
76
|
+
result = execute
|
77
|
+
if result.ok?
|
78
|
+
result.stdout.chomp
|
79
|
+
else
|
80
|
+
''
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# We encapsulate the shell's result, including the Process::Status, stdout and stderr.
|
85
|
+
class ShellResult < Struct.new(:process_result, :options, :stderr, :stdout)
|
86
|
+
def ok?
|
87
|
+
process_result.success?
|
88
|
+
end
|
89
|
+
|
90
|
+
def ensure_ok!
|
91
|
+
unless ok?
|
92
|
+
raise ShellError.new("unable to run command\ncommand=#{options[:cmd]}\noptions=#{options.pretty_inspect}\noutput=#{stdout}\nerror=#{stderr}",self)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class IPCState < Struct.new(:write,:read,:error,:exception)
|
98
|
+
def initialize
|
99
|
+
super(IO.pipe, IO.pipe, IO.pipe, IO.pipe)
|
100
|
+
end
|
101
|
+
|
102
|
+
def before_fork!
|
103
|
+
exception.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
104
|
+
end
|
105
|
+
|
106
|
+
def child_after_fork!
|
107
|
+
write.last.close
|
108
|
+
STDIN.reopen write.first
|
109
|
+
write.first.close
|
110
|
+
|
111
|
+
self.write = STDIN
|
112
|
+
|
113
|
+
read.first.close
|
114
|
+
STDOUT.reopen read.last
|
115
|
+
read.last.close
|
116
|
+
|
117
|
+
self.read = STDOUT
|
118
|
+
|
119
|
+
error.first.close
|
120
|
+
STDERR.reopen error.last
|
121
|
+
error.last.close
|
122
|
+
|
123
|
+
self.error = STDERR
|
124
|
+
|
125
|
+
exception.first.close
|
126
|
+
self.exception = exception.last
|
127
|
+
|
128
|
+
STDOUT.sync = STDERR.sync = true
|
129
|
+
end
|
130
|
+
|
131
|
+
def parent_after_fork!
|
132
|
+
[write.first, read.last, error.last, exception.last].each {|fd| fd.close}
|
133
|
+
|
134
|
+
self.write = write.last
|
135
|
+
self.read = read.first
|
136
|
+
self.error = error.first
|
137
|
+
self.exception = exception.first
|
138
|
+
end
|
139
|
+
|
140
|
+
alias :stdout :read
|
141
|
+
alias :stderr :error
|
142
|
+
|
143
|
+
def close_all
|
144
|
+
[ read, write, error, exception ].flatten.compact.each {|fd| fd.close unless fd.closed?}
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
# This is taken from Chef and rewritten.
|
150
|
+
#
|
151
|
+
# Chef's preamble:
|
152
|
+
# This is taken directly from Ara T Howard's Open4 library, and then
|
153
|
+
# modified to suit the needs of Chef. Any bugs here are most likely
|
154
|
+
# my own, and not Ara's.
|
155
|
+
#
|
156
|
+
# The original appears in external/open4.rb in its unmodified form.
|
157
|
+
#
|
158
|
+
# Thanks Ara!
|
159
|
+
def popen4(args={}, &blk)
|
160
|
+
popen4_normalise_args(args)
|
161
|
+
|
162
|
+
|
163
|
+
# We pass and manipulate all IPC pipes around inside this object.
|
164
|
+
ipc = IPCState.new
|
165
|
+
|
166
|
+
verbose = $VERBOSE
|
167
|
+
cid = begin
|
168
|
+
$VERBOSE = nil
|
169
|
+
ipc.before_fork!
|
170
|
+
|
171
|
+
fork {
|
172
|
+
popen4_proceed_as_child(args,ipc)
|
173
|
+
}
|
174
|
+
ensure
|
175
|
+
$VERBOSE = verbose
|
176
|
+
end
|
177
|
+
|
178
|
+
popen4_proceed_as_parent(cid,args,ipc,&blk)
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
def popen4_proceed_as_parent(cid,args,ipc,&blk)
|
183
|
+
ipc.parent_after_fork!
|
184
|
+
|
185
|
+
# The first thing a parent does after forking is look for an Marshalled exception on the exception pipe.
|
186
|
+
begin
|
187
|
+
e = Marshal.load ipc.exception
|
188
|
+
raise(Exception === e ? e : "unknown failure!")
|
189
|
+
rescue EOFError # If we get an EOF error, then the exec was successful
|
190
|
+
42
|
191
|
+
ensure
|
192
|
+
ipc.exception.close
|
193
|
+
end
|
194
|
+
|
195
|
+
ipc.write.sync = true
|
196
|
+
|
197
|
+
if block_given?
|
198
|
+
begin
|
199
|
+
if args[:stream]
|
200
|
+
# hand the block the pipes inside ipc to manipulate manually
|
201
|
+
yield(cid, ipc)
|
202
|
+
ShellResult.new(Process.waitpid2(cid).last, args)
|
203
|
+
else
|
204
|
+
popen4_parent_exhaust_io(cid,args,ipc,&blk)
|
205
|
+
end
|
206
|
+
ensure
|
207
|
+
ipc.close_all
|
208
|
+
end
|
209
|
+
else
|
210
|
+
# Return the pipes. The User needs to clean up after themselves.
|
211
|
+
[cid, ipc]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Use select to read the entire contents of the pipes into StringIOs.
|
216
|
+
# This is the main change to come from Chef vs the original open4.
|
217
|
+
def popen4_parent_exhaust_io(cid,args,ipc,&blk)
|
218
|
+
output = StringIO.new
|
219
|
+
error = StringIO.new
|
220
|
+
|
221
|
+
if args[:input]
|
222
|
+
ipc.write.puts args[:input]
|
223
|
+
end
|
224
|
+
|
225
|
+
ipc.write.close
|
226
|
+
|
227
|
+
stdout = ipc.read
|
228
|
+
stderr = ipc.error
|
229
|
+
|
230
|
+
stdout.sync = true
|
231
|
+
stderr.sync = true
|
232
|
+
|
233
|
+
stdout.fcntl(Fcntl::F_SETFL, stdout.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
|
234
|
+
stderr.fcntl(Fcntl::F_SETFL, stderr.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
|
235
|
+
|
236
|
+
stdout_finished = false
|
237
|
+
stderr_finished = false
|
238
|
+
|
239
|
+
results = nil
|
240
|
+
|
241
|
+
while !stdout_finished && !stderr_finished
|
242
|
+
begin
|
243
|
+
channels_to_watch = []
|
244
|
+
channels_to_watch << stdout if !stdout_finished
|
245
|
+
channels_to_watch << stderr if !stderr_finished
|
246
|
+
|
247
|
+
ready = IO.select(channels_to_watch, nil, nil, 1.0)
|
248
|
+
rescue Errno::EAGAIN
|
249
|
+
results = Process.waitpid2(cid, Process::WNOHANG)
|
250
|
+
|
251
|
+
if results
|
252
|
+
stdout_finished = true
|
253
|
+
stderr_finished = true
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
if ready && ready.first.include?(stdout)
|
258
|
+
line = results ? stdout.gets(nil) : stdout.gets
|
259
|
+
if line
|
260
|
+
output.write(line)
|
261
|
+
else
|
262
|
+
stdout_finished = true
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
if ready && ready.first.include?(stderr)
|
267
|
+
line = results ? stderr.gets(nil) : stderr.gets
|
268
|
+
if line
|
269
|
+
error.write(line)
|
270
|
+
else
|
271
|
+
stderr_finished = true
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
results = Process.waitpid2(cid) unless results
|
277
|
+
|
278
|
+
output.rewind
|
279
|
+
error.rewind
|
280
|
+
|
281
|
+
ipc.read = output
|
282
|
+
ipc.error = error
|
283
|
+
|
284
|
+
blk[cid, ipc]
|
285
|
+
|
286
|
+
ShellResult.new(results.last, args)
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
def popen4_proceed_as_child(args,ipc)
|
291
|
+
ipc.child_after_fork!
|
292
|
+
|
293
|
+
if args[:group]
|
294
|
+
Process.egid = args[:group]
|
295
|
+
Process.gid = args[:group]
|
296
|
+
end
|
297
|
+
|
298
|
+
if args[:user]
|
299
|
+
Process.euid = args[:user]
|
300
|
+
Process.uid = args[:user]
|
301
|
+
end
|
302
|
+
|
303
|
+
# Copy the specified environment across to the child's environment.
|
304
|
+
# Keys with `nil` values are deleted from the environment.
|
305
|
+
args[:environment].each do |key,value|
|
306
|
+
if value.nil?
|
307
|
+
ENV.delete(key.to_s)
|
308
|
+
else
|
309
|
+
ENV[key.to_s] = value
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
if args[:umask]
|
314
|
+
umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
|
315
|
+
File.umask(umask)
|
316
|
+
end
|
317
|
+
|
318
|
+
if args[:cwd]
|
319
|
+
Dir.chdir args[:cwd]
|
320
|
+
end
|
321
|
+
|
322
|
+
begin
|
323
|
+
cmd = args[:cmd]
|
324
|
+
if cmd.kind_of?(Array)
|
325
|
+
exec(*cmd)
|
326
|
+
else
|
327
|
+
exec(cmd)
|
328
|
+
end
|
329
|
+
|
330
|
+
raise 'forty-two'
|
331
|
+
rescue Object => e
|
332
|
+
Marshal.dump(e, ipc.exception)
|
333
|
+
ipc.exception.flush
|
334
|
+
end
|
335
|
+
|
336
|
+
ipc.exception.close unless (ipc.exception.closed?)
|
337
|
+
exit!
|
338
|
+
end
|
339
|
+
|
340
|
+
def popen4_normalise_args(args)
|
341
|
+
# Do we wait for the child process to die before we yield
|
342
|
+
# to the block, or after?
|
343
|
+
#
|
344
|
+
# By default, we are waiting before we yield the block.
|
345
|
+
args[:stream] ||= false
|
346
|
+
|
347
|
+
|
348
|
+
args[:user] ||= nil
|
349
|
+
unless args[:user].kind_of?(Integer)
|
350
|
+
args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
|
351
|
+
end
|
352
|
+
|
353
|
+
args[:group] ||= nil
|
354
|
+
unless args[:group].kind_of?(Integer)
|
355
|
+
args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
|
356
|
+
end
|
357
|
+
|
358
|
+
args[:environment] ||= {}
|
359
|
+
|
360
|
+
# Default on C locale so parsing commands output can be done
|
361
|
+
# independently of the node's default locale.
|
362
|
+
# "LC_ALL" could be set to nil, in which case we also must ignore it.
|
363
|
+
unless args[:environment].has_key?("LC_ALL")
|
364
|
+
args[:environment]["LC_ALL"] = "C"
|
365
|
+
end
|
366
|
+
|
367
|
+
unless TrueClass === args[:without_cleaning_bundler]
|
368
|
+
args[:environment].update('RUBYOPT' => nil, 'BUNDLE_GEMFILE' => nil, 'GEM_HOME' => nil, 'GEM_PATH' => nil)
|
369
|
+
end
|
370
|
+
|
371
|
+
# `:as` - run the command as another user, via sudo,
|
372
|
+
if user = args[:as]
|
373
|
+
if (evars = args[:environment].reject {|k,v| v.nil?}.map {|k,v| "#{k}=#{v}"}) && !evars.empty?
|
374
|
+
env = "env #{evars.join(' ')}"
|
375
|
+
else
|
376
|
+
env = ''
|
377
|
+
end
|
378
|
+
|
379
|
+
args[:cmd] = "sudo -H -u #{user} #{env} #{args[:cmd]}"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def massaged_args args
|
384
|
+
args.dup.tap do |args_to_print|
|
385
|
+
args_to_print[:environment] = e = args[:environment].dup
|
386
|
+
|
387
|
+
%w{LC_ALL GEM_HOME GEM_PATH RUBYOPT BUNDLE_GEMFILE}.each {|env| e.delete(env)}
|
388
|
+
|
389
|
+
args_to_print['cwd'] = args_to_print['cwd'].to_s if args_to_print['cwd']
|
390
|
+
|
391
|
+
args_to_print.delete_if{|k,v| v.blank?}
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
class String
|
399
|
+
def sh(options={})
|
400
|
+
AngryShell::Shell.new(self,options)
|
401
|
+
end
|
402
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: angry_shell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Lachie Cox
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-24 00:00:00 +11:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: ""
|
22
|
+
email:
|
23
|
+
- lachie.cox@plus2.com.au
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- Gemfile
|
33
|
+
- Gemfile.lock
|
34
|
+
- Rakefile
|
35
|
+
- Readme.md
|
36
|
+
- angry_shell.gemspec
|
37
|
+
- examples/eg.helper.rb
|
38
|
+
- examples/usage.eg.rb
|
39
|
+
- lib/angry_shell.rb
|
40
|
+
- lib/angry_shell/version.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://rubygems.org/gems/angry_shell
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project: angry_shell
|
67
|
+
rubygems_version: 1.3.6
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Shell
|
71
|
+
test_files: []
|
72
|
+
|