cheetah 0.1.0 → 0.2.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.
- data/CHANGELOG +12 -0
- data/README.md +91 -2
- data/VERSION +1 -1
- data/lib/cheetah.rb +195 -158
- metadata +21 -7
data/CHANGELOG
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
0.2.0 (2012-04-05)
|
2
|
+
------------------
|
3
|
+
|
4
|
+
* Logger can be set globally.
|
5
|
+
* Use :info and :error levels for logging instead of :debug.
|
6
|
+
* Added API documentation.
|
7
|
+
* Added proper README.md.
|
8
|
+
* Updated gem description.
|
9
|
+
* Rewrote tests into RSpec.
|
10
|
+
* Improved performance for commands with big outputs.
|
11
|
+
* Internal code improvements.
|
12
|
+
|
1
13
|
0.1.0 (2012-03-23)
|
2
14
|
------------------
|
3
15
|
|
data/README.md
CHANGED
@@ -1,6 +1,95 @@
|
|
1
1
|
Cheetah
|
2
2
|
=======
|
3
3
|
|
4
|
-
Cheetah is a simple library for executing external commands safely and conveniently.
|
4
|
+
Cheetah is a simple library for executing external commands safely and conveniently.
|
5
5
|
|
6
|
-
|
6
|
+
Examples
|
7
|
+
--------
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Run a command and capture its output
|
11
|
+
files = Cheetah.run("ls", "-la", :capture => :stdout)
|
12
|
+
|
13
|
+
# Run a command and handle errors
|
14
|
+
begin
|
15
|
+
Cheetah.run("rm", "/etc/passwd")
|
16
|
+
rescue Cheetah::ExecutionFailed => e
|
17
|
+
puts e.message
|
18
|
+
puts "Standard output: #{e.stdout}"
|
19
|
+
puts "Error ouptut: #{e.stderr}"
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
Features
|
24
|
+
--------
|
25
|
+
|
26
|
+
* Easy passing of command input
|
27
|
+
* Easy capturing of command output (standard, error, or both)
|
28
|
+
* 100% secure (shell expansion is impossible by design)
|
29
|
+
* Raises exceptions on errors (no more manual status code checks)
|
30
|
+
* Optional logging for easy debugging
|
31
|
+
|
32
|
+
Non-features
|
33
|
+
------------
|
34
|
+
|
35
|
+
* Handling of commands producing big outputs
|
36
|
+
* Handling of interactive commands
|
37
|
+
|
38
|
+
Installation
|
39
|
+
------------
|
40
|
+
|
41
|
+
$ gem install cheetah
|
42
|
+
|
43
|
+
Usage
|
44
|
+
-----
|
45
|
+
|
46
|
+
First, require the library:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
require "cheetah"
|
50
|
+
```
|
51
|
+
|
52
|
+
You can now use the `Cheetah.run` method to run commands, pass them an input and capture their output:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Run a command with arguments
|
56
|
+
Cheetah.run("tar", "xzf", "foo.tar.gz")
|
57
|
+
|
58
|
+
# Pass an input
|
59
|
+
Cheetah.run("python", :stdin => source_code)
|
60
|
+
|
61
|
+
# Capture standard output
|
62
|
+
files = Cheetah.run("ls", "-la", :capture => :stdout)
|
63
|
+
|
64
|
+
# Capture both standard and error output
|
65
|
+
results, errors = Cheetah.run("grep", "-r", "User", ".", :capture => [:stdout, :stderr))
|
66
|
+
```
|
67
|
+
|
68
|
+
If the command can't be executed for some reason or returns a non-zero exit status, the method raises an exception with detailed information about the failure:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# Run a command and handle errors
|
72
|
+
begin
|
73
|
+
Cheetah.run("rm", "/etc/passwd")
|
74
|
+
rescue Cheetah::ExecutionFailed => e
|
75
|
+
puts e.message
|
76
|
+
puts "Standard output: #{e.stdout}"
|
77
|
+
puts "Error ouptut: #{e.stderr}"
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
For debugging purposes, you can also use a logger. Cheetah will log the command, its status, input and both outputs to it. The `:info` level will be used for normal messages, the `:error` level for messages about errors (non-zero exit status or non-empty error output):
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# Log the execution
|
85
|
+
Cheetah.run("ls -l", :logger => logger)
|
86
|
+
|
87
|
+
# Or, if you're tired of passing the :logger option all the time...
|
88
|
+
Cheetah.logger = my_logger
|
89
|
+
Cheetah.run("./configure")
|
90
|
+
Cheetah.run("make")
|
91
|
+
Cheetah.run("make", "install")
|
92
|
+
Cheetah.logger = nil
|
93
|
+
```
|
94
|
+
|
95
|
+
For more information, see the [API documentation](http://rubydoc.info/github/openSUSE/cheetah/frames).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/cheetah.rb
CHANGED
@@ -1,11 +1,58 @@
|
|
1
|
-
#
|
1
|
+
# A simple library for executing external commands safely and conveniently.
|
2
|
+
#
|
3
|
+
# ## Features
|
4
|
+
#
|
5
|
+
# * Easy passing of command input
|
6
|
+
# * Easy capturing of command output (standard, error, or both)
|
7
|
+
# * 100% secure (shell expansion is impossible by design)
|
8
|
+
# * Raises exceptions on errors (no more manual status code checks)
|
9
|
+
# * Optional logging for easy debugging
|
10
|
+
#
|
11
|
+
# ## Non-features
|
12
|
+
#
|
13
|
+
# * Handling of commands producing big outputs
|
14
|
+
# * Handling of interactive commands
|
15
|
+
#
|
16
|
+
# @example Run a command and capture its output
|
17
|
+
# files = Cheetah.run("ls", "-la", :capture => :stdout)
|
18
|
+
#
|
19
|
+
# @example Run a command and handle errors
|
20
|
+
# begin
|
21
|
+
# Cheetah.run("rm", "/etc/passwd")
|
22
|
+
# rescue Cheetah::ExecutionFailed => e
|
23
|
+
# puts e.message
|
24
|
+
# puts "Standard output: #{e.stdout}"
|
25
|
+
# puts "Error ouptut: #{e.stderr}"
|
26
|
+
# end
|
2
27
|
module Cheetah
|
28
|
+
# Cheetah version (uses [semantic versioning](http://semver.org/)).
|
3
29
|
VERSION = File.read(File.dirname(__FILE__) + "/../VERSION").strip
|
4
30
|
|
5
31
|
# Exception raised when a command execution fails.
|
6
32
|
class ExecutionFailed < StandardError
|
7
|
-
|
33
|
+
# @return [String] the executed command
|
34
|
+
attr_reader :command
|
8
35
|
|
36
|
+
# @return [Array<String>] the executed command arguments
|
37
|
+
attr_reader :args
|
38
|
+
|
39
|
+
# @return [Process::Status] the executed command exit status
|
40
|
+
attr_reader :status
|
41
|
+
|
42
|
+
# @return [String] the output the executed command wrote to stdout
|
43
|
+
attr_reader :stdout
|
44
|
+
|
45
|
+
# @return [String] the output the executed command wrote to stderr
|
46
|
+
attr_reader :stderr
|
47
|
+
|
48
|
+
# Initializes a new {ExecutionFailed} instance.
|
49
|
+
#
|
50
|
+
# @param [String] command the executed command
|
51
|
+
# @param [Array<String>] args the executed command arguments
|
52
|
+
# @param [Process::Status] status the executed command exit status
|
53
|
+
# @param [String] stdout the output the executed command wrote to stdout
|
54
|
+
# @param [String] stderr the output the executed command wrote to stderr
|
55
|
+
# @param [String, nil] message the exception message
|
9
56
|
def initialize(command, args, status, stdout, stderr, message = nil)
|
10
57
|
super(message)
|
11
58
|
@command = command
|
@@ -16,142 +63,135 @@ module Cheetah
|
|
16
63
|
end
|
17
64
|
end
|
18
65
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# Examples:
|
26
|
-
#
|
27
|
-
# # Run a command, grab its output and handle failures.
|
28
|
-
# files = nil
|
29
|
-
# begin
|
30
|
-
# files = Cheetah.run("ls", "-la", :capture => :stdout)
|
31
|
-
# rescue Cheetah::ExecutionFailed => e
|
32
|
-
# puts "Command #{e.command} failed with status #{e.status}."
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# # Log the executed command, it's status, input and both outputs into
|
36
|
-
# # user-supplied logger.
|
37
|
-
# Cheetah.run("qemu-kvm", "foo.raw", :logger => my_logger)
|
38
|
-
#
|
39
|
-
# The first parameter specifies the command to run, the remaining parameters
|
40
|
-
# specify its arguments. It is also possible to specify both the command and
|
41
|
-
# arguments in the first parameter using an array. If the last parameter is a
|
42
|
-
# hash, it specifies options.
|
43
|
-
#
|
44
|
-
# For security reasons, the command never goes through shell expansion even if
|
45
|
-
# only one parameter is specified (i.e. the method does do not adhere to the
|
46
|
-
# convention used by other Ruby methods for launching external commands, e.g.
|
47
|
-
# Kernel#system).
|
48
|
-
#
|
49
|
-
# If the command execution succeeds, the returned value depends on the
|
50
|
-
# value of the :capture option (see below). If it fails (the command is not
|
51
|
-
# executed for some reason or returns a non-zero exit status), the method
|
52
|
-
# raises a ExecutionFailed exception with detailed information about the
|
53
|
-
# failure.
|
54
|
-
#
|
55
|
-
# Options:
|
56
|
-
#
|
57
|
-
# :capture - configures which output(s) the method captures and returns, the
|
58
|
-
# valid values are:
|
59
|
-
#
|
60
|
-
# - nil - no output is captured and returned
|
61
|
-
# (the default)
|
62
|
-
# - :stdout - standard output is captured and
|
63
|
-
# returned as a string
|
64
|
-
# - :stderr - error output is captured and returned
|
65
|
-
# as a string
|
66
|
-
# - [:stdout, :stderr] - both outputs are captured and returned
|
67
|
-
# as a two-element array of strings
|
68
|
-
#
|
69
|
-
# :stdin - if specified, it is a string sent to command's standard input
|
70
|
-
#
|
71
|
-
# :logger - if specified, the method will log the command, its status, input
|
72
|
-
# and both outputs to passed logger at the "debug" level
|
73
|
-
#
|
74
|
-
def self.run(command, *args)
|
75
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
76
|
-
|
77
|
-
capture = options[:capture]
|
78
|
-
stdin = options[:stdin] || ""
|
79
|
-
logger = options[:logger]
|
80
|
-
|
81
|
-
if command.is_a?(Array)
|
82
|
-
args = command[1..-1]
|
83
|
-
command = command.first
|
84
|
-
end
|
66
|
+
class << self
|
67
|
+
# The global logger or `nil` if none is set (the default). This logger is
|
68
|
+
# used by {Cheetah.run} unless overridden by the `:logger` option.
|
69
|
+
#
|
70
|
+
# @return [Logger, nil] the global logger
|
71
|
+
attr_accessor :logger
|
85
72
|
|
86
|
-
|
87
|
-
|
73
|
+
# Runs an external command with specified arguments, optionally passing it
|
74
|
+
# an input and capturing its output.
|
75
|
+
#
|
76
|
+
# If the command execution succeeds, the returned value depends on the value
|
77
|
+
# of the `:capture` option (see below). If the command can't be executed for
|
78
|
+
# some reason or returns a non-zero exit status, the method raises an
|
79
|
+
# {ExecutionFailed} exception with detailed information about the failure.
|
80
|
+
#
|
81
|
+
# The command and its arguments never undergo shell expansion — they are
|
82
|
+
# passed directly to the operating system. While this may create some
|
83
|
+
# inconvenience in certain cases, it eliminates a whole class of security
|
84
|
+
# bugs.
|
85
|
+
#
|
86
|
+
# The command execution can be logged using a logger. It can be set globally
|
87
|
+
# (using the {Cheetah.logger} attribute) or locally (using the `:logger`
|
88
|
+
# option). The local setting overrides the global one. If a logger is set,
|
89
|
+
# the method will log the command, its status, input and both outputs to it.
|
90
|
+
# The `:info` level will be used for normal messages, the `:error` level for
|
91
|
+
# messages about errors (non-zero exit status or non-empty error output).
|
92
|
+
#
|
93
|
+
# @overload run(command, *args, options = {})
|
94
|
+
# @param [String] command the command to execute
|
95
|
+
# @param [Array<String>] args the command arguments
|
96
|
+
# @param [Hash] options the options to execute the command with
|
97
|
+
# @option options [String] :stdin ('') command's input
|
98
|
+
# @option options [String] :capture (nil) configures which output(s) to
|
99
|
+
# capture, the valid values are:
|
100
|
+
#
|
101
|
+
# * `nil` — no output is captured and returned
|
102
|
+
# * `:stdout` — standard output is captured and returned as a string
|
103
|
+
# * `:stderr` — error output is captured and returned as a string
|
104
|
+
# * `[:stdout, :stderr]` — both outputs are captured and returned as a
|
105
|
+
# two-element array of strings
|
106
|
+
# @option options [Logger, nil] :logger (nil) logger to log the command
|
107
|
+
# execution; if set, overrides the global logger (set by
|
108
|
+
# {Cheetah.logger})
|
109
|
+
#
|
110
|
+
# @overload run(command_and_args, options = {})
|
111
|
+
# This variant is useful mainly when building the command and its
|
112
|
+
# arguments programmatically.
|
113
|
+
#
|
114
|
+
# @param [Array<String>] command_and_args the command to execute (first
|
115
|
+
# element of the array) and its arguments (remaining elements)
|
116
|
+
# @param [Hash] options the options to execute the command with, same as
|
117
|
+
# in the first variant
|
118
|
+
#
|
119
|
+
# @raise [ExecutionFailed] when the command can't be executed for some
|
120
|
+
# reason or returns a non-zero exit status
|
121
|
+
#
|
122
|
+
# @example Run a command and capture its output
|
123
|
+
# files = Cheetah.run("ls", "-la", :capture => :stdout)
|
124
|
+
#
|
125
|
+
# @example Run a command and handle errors
|
126
|
+
# begin
|
127
|
+
# Cheetah.run("rm", "/etc/passwd")
|
128
|
+
# rescue Cheetah::ExecutionFailed => e
|
129
|
+
# puts e.message
|
130
|
+
# puts "Standard output: #{e.stdout}"
|
131
|
+
# puts "Error ouptut: #{e.stderr}"
|
132
|
+
# end
|
133
|
+
def run(command, *args)
|
134
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
88
135
|
|
89
|
-
|
90
|
-
|
136
|
+
stdin = options[:stdin] || ""
|
137
|
+
logger = options[:logger] || @logger
|
91
138
|
|
92
|
-
|
93
|
-
|
139
|
+
if command.is_a?(Array)
|
140
|
+
args = command[1..-1]
|
141
|
+
command = command.first
|
142
|
+
end
|
94
143
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
144
|
+
pipe_stdin_read, pipe_stdin_write = IO.pipe
|
145
|
+
pipe_stdout_read, pipe_stdout_write = IO.pipe
|
146
|
+
pipe_stderr_read, pipe_stderr_write = IO.pipe
|
99
147
|
|
100
|
-
|
101
|
-
|
102
|
-
|
148
|
+
if logger
|
149
|
+
logger.info "Executing command #{command.inspect} with #{describe_args(args)}."
|
150
|
+
logger.info "Standard input: " + (stdin.empty? ? "(none)" : stdin)
|
151
|
+
end
|
152
|
+
|
153
|
+
pid = fork do
|
154
|
+
begin
|
103
155
|
pipe_stdin_write.close
|
104
156
|
STDIN.reopen(pipe_stdin_read)
|
105
157
|
pipe_stdin_read.close
|
106
|
-
else
|
107
|
-
STDIN.reopen("/dev/null", "r")
|
108
|
-
end
|
109
158
|
|
110
|
-
if capture_stdout
|
111
159
|
pipe_stdout_read.close
|
112
160
|
STDOUT.reopen(pipe_stdout_write)
|
113
161
|
pipe_stdout_write.close
|
114
|
-
else
|
115
|
-
STDOUT.reopen("/dev/null", "w")
|
116
|
-
end
|
117
162
|
|
118
|
-
if capture_stderr
|
119
163
|
pipe_stderr_read.close
|
120
164
|
STDERR.reopen(pipe_stderr_write)
|
121
165
|
pipe_stderr_write.close
|
122
|
-
else
|
123
|
-
STDERR.reopen("/dev/null", "w")
|
124
|
-
end
|
125
166
|
|
126
|
-
|
127
|
-
|
128
|
-
|
167
|
+
# All file descriptors from 3 above should be closed here, but since I
|
168
|
+
# don't know about any way how to detect the maximum file descriptor
|
169
|
+
# number portably in Ruby, I didn't implement it. Patches welcome.
|
129
170
|
|
130
|
-
|
131
|
-
|
132
|
-
|
171
|
+
exec([command, command], *args)
|
172
|
+
rescue SystemCallError => e
|
173
|
+
exit!(127)
|
174
|
+
end
|
133
175
|
end
|
134
|
-
end
|
135
|
-
|
136
|
-
[pipe_stdin_read, pipe_stdout_write, pipe_stderr_write].each { |p| p.close if p }
|
137
176
|
|
138
|
-
|
139
|
-
# Because otherwise we could end up with a deadlock.
|
140
|
-
#
|
141
|
-
# Imagine if we first read the whole standard output and then the whole
|
142
|
-
# error output, but the executed command would write lot of data but only to
|
143
|
-
# the error output. Sooner or later it would fill the buffer and block,
|
144
|
-
# while we would be blocked on reading the standard output -- classic
|
145
|
-
# deadlock.
|
146
|
-
#
|
147
|
-
# Similar issues can happen with standard input vs. one of the outputs.
|
148
|
-
if pass_stdin || capture_stdout || capture_stderr
|
149
|
-
stdout = ""
|
150
|
-
stderr = ""
|
177
|
+
[pipe_stdin_read, pipe_stdout_write, pipe_stderr_write].each { |p| p.close }
|
151
178
|
|
179
|
+
# We write the command's input and read its output using a select loop.
|
180
|
+
# Why? Because otherwise we could end up with a deadlock.
|
181
|
+
#
|
182
|
+
# Imagine if we first read the whole standard output and then the whole
|
183
|
+
# error output, but the executed command would write lot of data but only
|
184
|
+
# to the error output. Sooner or later it would fill the buffer and block,
|
185
|
+
# while we would be blocked on reading the standard output -- classic
|
186
|
+
# deadlock.
|
187
|
+
#
|
188
|
+
# Similar issues can happen with standard input vs. one of the outputs.
|
189
|
+
outputs = { pipe_stdout_read => "", pipe_stderr_read => "" }
|
190
|
+
pipes_readable = [pipe_stdout_read, pipe_stderr_read]
|
191
|
+
pipes_writable = [pipe_stdin_write]
|
152
192
|
loop do
|
153
|
-
pipes_readable
|
154
|
-
pipes_writable
|
193
|
+
pipes_readable.reject!(&:closed?)
|
194
|
+
pipes_writable.reject!(&:closed?)
|
155
195
|
|
156
196
|
break if pipes_readable.empty? && pipes_writable.empty?
|
157
197
|
|
@@ -162,61 +202,58 @@ module Cheetah
|
|
162
202
|
raise IOError, "Error when communicating with executed program."
|
163
203
|
end
|
164
204
|
|
165
|
-
|
205
|
+
ios_read.each do |pipe|
|
166
206
|
begin
|
167
|
-
|
207
|
+
outputs[pipe] << pipe.readpartial(4096)
|
168
208
|
rescue EOFError
|
169
|
-
|
209
|
+
pipe.close
|
170
210
|
end
|
171
211
|
end
|
172
212
|
|
173
|
-
|
174
|
-
|
175
|
-
stderr += pipe_stderr_read.readpartial(4096)
|
176
|
-
rescue EOFError
|
177
|
-
pipe_stderr_read.close
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
if ios_write.include?(pipe_stdin_write)
|
182
|
-
n = pipe_stdin_write.syswrite(stdin)
|
213
|
+
ios_write.each do |pipe|
|
214
|
+
n = pipe.syswrite(stdin)
|
183
215
|
stdin = stdin[n..-1]
|
184
|
-
|
216
|
+
pipe.close if stdin.empty?
|
185
217
|
end
|
186
218
|
end
|
187
|
-
end
|
188
219
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
220
|
+
stdout = outputs[pipe_stdout_read]
|
221
|
+
stderr = outputs[pipe_stderr_read]
|
222
|
+
|
223
|
+
pid, status = Process.wait2(pid)
|
224
|
+
begin
|
225
|
+
if !status.success?
|
226
|
+
raise ExecutionFailed.new(command, args, status, stdout, stderr,
|
227
|
+
"Execution of command #{command.inspect} " +
|
228
|
+
"with #{describe_args(args)} " +
|
229
|
+
"failed with status #{status.exitstatus}.")
|
230
|
+
end
|
231
|
+
ensure
|
232
|
+
if logger
|
233
|
+
logger.log status.success? ? Logger::INFO : Logger::ERROR,
|
234
|
+
"Status: #{status.exitstatus}"
|
235
|
+
logger.info "Standard output: " + (stdout.empty? ? "(none)" : stdout)
|
236
|
+
logger.log stderr.empty? ? Logger::INFO : Logger::ERROR,
|
237
|
+
"Error output: " + (stderr.empty? ? "(none)" : stderr)
|
238
|
+
end
|
198
239
|
end
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
240
|
+
|
241
|
+
case options[:capture]
|
242
|
+
when nil
|
243
|
+
nil
|
244
|
+
when :stdout
|
245
|
+
stdout
|
246
|
+
when :stderr
|
247
|
+
stderr
|
248
|
+
when [:stdout, :stderr]
|
249
|
+
[stdout, stderr]
|
204
250
|
end
|
205
251
|
end
|
206
252
|
|
207
|
-
|
208
|
-
when nil
|
209
|
-
nil
|
210
|
-
when :stdout
|
211
|
-
stdout
|
212
|
-
when :stderr
|
213
|
-
stderr
|
214
|
-
when [:stdout, :stderr]
|
215
|
-
[stdout, stderr]
|
216
|
-
end
|
217
|
-
end
|
253
|
+
private
|
218
254
|
|
219
|
-
|
220
|
-
|
255
|
+
def describe_args(args)
|
256
|
+
args.empty? ? "no arguments" : "arguments #{args.map(&:inspect).join(", ")}"
|
257
|
+
end
|
221
258
|
end
|
222
259
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cheetah
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- David Majda
|
@@ -15,11 +15,11 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-04-05 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: rspec
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
@@ -33,7 +33,7 @@ dependencies:
|
|
33
33
|
type: :development
|
34
34
|
version_requirements: *id001
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
|
-
name:
|
36
|
+
name: redcarpet
|
37
37
|
prerelease: false
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
@@ -46,7 +46,21 @@ dependencies:
|
|
46
46
|
version: "0"
|
47
47
|
type: :development
|
48
48
|
version_requirements: *id002
|
49
|
-
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: yard
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
description: Cheetah is a simple library for executing external commands safely and conveniently.
|
50
64
|
email: dmajda@suse.de
|
51
65
|
executables: []
|
52
66
|
|