syscmd 0.0.3
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/LICENSE +20 -0
- data/README.rdoc +27 -0
- data/VERSION.yml +4 -0
- data/lib/syscmd.rb +109 -0
- data/lib/syscmd/popen.rb +58 -0
- data/spec/command_exec_spec.rb +82 -0
- data/spec/command_spec.rb +29 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/syscmd_spec.rb +20 -0
- data/spec/tester.rb +74 -0
- metadata +66 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Till Salzer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
= syscmd
|
2
|
+
|
3
|
+
A simple wrapper to execute system commands, which provides stdout, stderr and
|
4
|
+
exit code.
|
5
|
+
|
6
|
+
== Usage
|
7
|
+
|
8
|
+
require 'syscmd'
|
9
|
+
cmd = Syscmd.exec!('command') # => Syscmd::Command object
|
10
|
+
|
11
|
+
cmd.stdout # => String
|
12
|
+
cmd.stderr # => String
|
13
|
+
cmd.exitcode # => Integer
|
14
|
+
|
15
|
+
Note that <code>Syscmd.exec!</code> immediately executes the given command.
|
16
|
+
You will get the Syscmd::Command object executed and can examine it.
|
17
|
+
|
18
|
+
|
19
|
+
== Heritage/Acknoledgement
|
20
|
+
|
21
|
+
The <code>Syscmd::popen</code> code is mostly taken from the original Ruby 1.8
|
22
|
+
implementation of Open3::open3 (author: Yukihiro Matsumoto: documentation: Konrad Meyer).
|
23
|
+
|
24
|
+
|
25
|
+
== Copyright
|
26
|
+
|
27
|
+
Copyright (c) 2009 Till Salzer. See LICENSE for details.
|
data/VERSION.yml
ADDED
data/lib/syscmd.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'syscmd/popen'
|
2
|
+
|
3
|
+
# Module.
|
4
|
+
module Syscmd
|
5
|
+
# Error raise if a command object is executed twice.
|
6
|
+
class AlreadyExecutedError < RuntimeError ; end
|
7
|
+
|
8
|
+
# A command to execute.
|
9
|
+
# You usually just use Syscmd::exec! to create and execute the command.
|
10
|
+
# A command object can be executed only once.
|
11
|
+
class Command
|
12
|
+
# +true+ if the command was already executed, otherwise +false+.
|
13
|
+
attr_reader :executed
|
14
|
+
|
15
|
+
# the command to execute.
|
16
|
+
attr_reader :cmd
|
17
|
+
# array of arguments.
|
18
|
+
attr_reader :args
|
19
|
+
# the constructed command line.
|
20
|
+
attr_reader :cmdline
|
21
|
+
|
22
|
+
# standard output of the executed command.
|
23
|
+
attr_reader :stdout
|
24
|
+
# standard error of the executed command.
|
25
|
+
attr_reader :stderr
|
26
|
+
# exit code of the executed command.
|
27
|
+
# Note that the exit code is just a Byte value ranged 0..255 on
|
28
|
+
# Unix platforms, so don't expect any negative values or values greated
|
29
|
+
# than 255 here.
|
30
|
+
attr_reader :exitcode
|
31
|
+
# exit status of the executed command.
|
32
|
+
attr_reader :process_status
|
33
|
+
|
34
|
+
# execute a system command.
|
35
|
+
# cmd: the command to execute
|
36
|
+
# args: command line arguments
|
37
|
+
def initialize(cmd, *args)
|
38
|
+
@cmd = cmd
|
39
|
+
@args = *args
|
40
|
+
@process_status = nil
|
41
|
+
@executed = false
|
42
|
+
end
|
43
|
+
|
44
|
+
# execute the command.
|
45
|
+
# raises AlreadyExecutedError if the command is already executed.
|
46
|
+
def exec!
|
47
|
+
raise AlreadyExecutedError.new("already executed with status #{process_status}") if self.executed?
|
48
|
+
@process_status, pread, perr = Syscmd::popen(self.cmdline)
|
49
|
+
@stdout = pread.read
|
50
|
+
@stderr = perr.read
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# get the stdout as lines.
|
55
|
+
def stdout_lines
|
56
|
+
if @stdout_lines.nil?
|
57
|
+
stdout = self.stdout
|
58
|
+
return nil unless stdout
|
59
|
+
@stdout_lines = stdout.split(/\n/)
|
60
|
+
end
|
61
|
+
@stdout_lines
|
62
|
+
end
|
63
|
+
|
64
|
+
# get the stdout as lines.
|
65
|
+
def stderr_lines
|
66
|
+
if @stderr_lines.nil?
|
67
|
+
stderr = self.stderr
|
68
|
+
return nil unless stderr
|
69
|
+
@stderr_lines = stderr.split(/\n/)
|
70
|
+
end
|
71
|
+
@stderr_lines
|
72
|
+
end
|
73
|
+
|
74
|
+
# the exitcode of the executed command.
|
75
|
+
# returns nil
|
76
|
+
def exitcode
|
77
|
+
@process_status ? @process_status.exitstatus : nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# build the command line for this command.
|
81
|
+
# Once the command line is created, it will be cached.
|
82
|
+
def cmdline
|
83
|
+
if @cmdline.nil?
|
84
|
+
cmdline = "#{@cmd}"
|
85
|
+
@args.each do |arg|
|
86
|
+
cmdline << (arg.to_s.index(" ") ? " \"#{arg}\"" : " #{arg}")
|
87
|
+
end if @args
|
88
|
+
@cmdline = cmdline
|
89
|
+
end
|
90
|
+
@cmdline
|
91
|
+
end
|
92
|
+
|
93
|
+
# check if the command was executed.
|
94
|
+
def executed
|
95
|
+
@process_status.nil? ? false : true
|
96
|
+
end
|
97
|
+
alias executed? executed
|
98
|
+
end
|
99
|
+
|
100
|
+
# execute a system command.
|
101
|
+
# cmd:: the command to execute
|
102
|
+
# args:: command line arguments
|
103
|
+
# returns:: executed Syscmd::Command object
|
104
|
+
def exec!(cmd, *args)
|
105
|
+
cmd = Command.new(cmd, args)
|
106
|
+
cmd.exec!
|
107
|
+
end
|
108
|
+
module_function :'exec!'
|
109
|
+
end
|
data/lib/syscmd/popen.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Syscmd
|
2
|
+
# Taken from the original Ruby 1.8 implementation of Open3::open3 and slightly
|
3
|
+
# tweaked to return an exit status.
|
4
|
+
# This function is meant as in internal method of the Syscmd module; do not use
|
5
|
+
# it on it's own, as the interface may change anytime.
|
6
|
+
#
|
7
|
+
# cmd:: the command to execute
|
8
|
+
# returns:: status, stdout, stderr
|
9
|
+
#
|
10
|
+
# orignal author:: Yukihiro Matsumoto
|
11
|
+
# orignal socumentation:: Konrad Meyer
|
12
|
+
def popen(*cmd)
|
13
|
+
pw = IO::pipe # pipe[0] for read, pipe[1] for write
|
14
|
+
pr = IO::pipe
|
15
|
+
pe = IO::pipe
|
16
|
+
status = 0 # status of the inner fork
|
17
|
+
|
18
|
+
pid = fork do
|
19
|
+
# child
|
20
|
+
gcpid = fork do
|
21
|
+
# grandchild
|
22
|
+
pw[1].close
|
23
|
+
STDIN.reopen(pw[0])
|
24
|
+
pw[0].close
|
25
|
+
|
26
|
+
pr[0].close
|
27
|
+
STDOUT.reopen(pr[1])
|
28
|
+
pr[1].close
|
29
|
+
|
30
|
+
pe[0].close
|
31
|
+
STDERR.reopen(pe[1])
|
32
|
+
pe[1].close
|
33
|
+
|
34
|
+
exec(*cmd)
|
35
|
+
end
|
36
|
+
gcpid, gcstatus = Process.wait2(gcpid)
|
37
|
+
exit!(gcstatus.exitstatus)
|
38
|
+
end
|
39
|
+
|
40
|
+
pw[0].close
|
41
|
+
pr[1].close
|
42
|
+
pe[1].close
|
43
|
+
pid, status = Process.wait2(pid)
|
44
|
+
|
45
|
+
#pi = [status.exitstatus, pw[1], pr[0], pe[0]]
|
46
|
+
pi = [status, pr[0], pe[0]]
|
47
|
+
pw[1].sync = true
|
48
|
+
if defined? yield
|
49
|
+
begin
|
50
|
+
return yield(*pi)
|
51
|
+
ensure
|
52
|
+
pi.each{|p| p.close unless p.closed?}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
pi
|
56
|
+
end
|
57
|
+
module_function :popen
|
58
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe Syscmd::Command, ".exec! for -s 'Hello World'" do
|
4
|
+
subject { Syscmd::Command.new(TESTER, '-s', 'Hello World').exec! }
|
5
|
+
|
6
|
+
it "should have stdout 'Hello World\\n'" do
|
7
|
+
subject.stdout.should == "Hello World\n"
|
8
|
+
end
|
9
|
+
it "should have stdout_lines ['Hello World']" do
|
10
|
+
subject.stdout_lines.should == ["Hello World"]
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
it "should have stderr \"\"" do
|
15
|
+
subject.stderr.should == ""
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have exitcode 0" do
|
19
|
+
subject.exitcode.should == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Syscmd::Command, ".exec! for multiline output" do
|
24
|
+
subject { Syscmd::Command.new(TESTER,
|
25
|
+
'-s', 'Hello World', '-S', 2,
|
26
|
+
'-e', 'Bye Bye World', '-E', 2).exec! }
|
27
|
+
|
28
|
+
it "should have stdout 'Hello World\\nHello World\\n'" do
|
29
|
+
subject.stdout.should == "Hello World\nHello World\n"
|
30
|
+
end
|
31
|
+
it "should have two strings in stdout_lines" do
|
32
|
+
subject.stdout_lines.should == ["Hello World", "Hello World"]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should have stderr 'Bye Bye World\\nBye Bye World\\n'" do
|
36
|
+
subject.stderr.should == "Bye Bye World\nBye Bye World\n"
|
37
|
+
end
|
38
|
+
it "should have two strings in stdout_lines" do
|
39
|
+
subject.stderr_lines.should == ["Bye Bye World", "Bye Bye World"]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe Syscmd::Command, ".exec! for positive exit codes" do
|
44
|
+
[1, 2, 254, 255].each do |st|
|
45
|
+
it "should return exit code #{st} for -x #{st}" do
|
46
|
+
cmd = Syscmd::Command.new(TESTER, '-x', st)
|
47
|
+
cmd.cmdline.should == "#{TESTER} -x #{st}"
|
48
|
+
cmd.exec!
|
49
|
+
cmd.exitcode.should == st
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe Syscmd::Command, ".exec! for hugh positive exit codes" do
|
55
|
+
[256, 257, 512, 4000].each do |st|
|
56
|
+
it "should return exit code #{st % 256} for -x #{st}" do
|
57
|
+
cmd = Syscmd::Command.new(TESTER, '-x', st)
|
58
|
+
cmd.cmdline.should == "#{TESTER} -x #{st}"
|
59
|
+
cmd.exec!
|
60
|
+
cmd.exitcode.should == st % 256
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe Syscmd::Command, ".exec! for negative exit codes" do
|
66
|
+
[-1, -2, -255, -256].each do |st|
|
67
|
+
it "should return exit code #{st % 256} for -x #{st}" do
|
68
|
+
cmd = Syscmd::Command.new(TESTER, '-x', st)
|
69
|
+
cmd.cmdline.should == "#{TESTER} -x #{st}"
|
70
|
+
cmd.exec!
|
71
|
+
cmd.exitcode.should == st % 256
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe Syscmd::Command, ".exec! called twice" do
|
77
|
+
subject { Syscmd::Command.new(TESTER) }
|
78
|
+
it "should fail with AlreadyExecutedError" do
|
79
|
+
subject.exec!
|
80
|
+
lambda { subject.exec! }.should raise_error(Syscmd::AlreadyExecutedError)
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe Syscmd::Command, ".new" do
|
4
|
+
subject { Syscmd::Command.new(TESTER) }
|
5
|
+
|
6
|
+
it "should have #{TESTER} as cmd attribute" do
|
7
|
+
subject.cmd.should == TESTER
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should not be executed after creation" do
|
11
|
+
subject.executed?.should == false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Syscmd::Command, ".new with -s 'Hello World'" do
|
16
|
+
subject { Syscmd::Command.new(TESTER, '-s', 'Hello World') }
|
17
|
+
|
18
|
+
it "should have the cmd attribute #{TESTER}" do
|
19
|
+
subject.cmd.should == TESTER
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should have the command line arguments ['-s', 'Hello World']" do
|
23
|
+
subject.args.should == ['-s', 'Hello World']
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have the command line '#{TESTER} -s \"Hello World\"'" do
|
27
|
+
subject.cmdline.should == "#{TESTER} -s \"Hello World\""
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
require 'syscmd'
|
6
|
+
|
7
|
+
TESTER=File.join(File.dirname(__FILE__), 'tester.rb') unless defined?(TESTER)
|
8
|
+
|
9
|
+
Spec::Runner.configure do |config|
|
10
|
+
|
11
|
+
end
|
data/spec/syscmd_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Syscmd, ".execute" do
|
5
|
+
subject { Syscmd }
|
6
|
+
|
7
|
+
it "should provide a .execute method" do
|
8
|
+
subject.should respond_to(:exec!)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should return a Syscmd::Command object" do
|
12
|
+
subject.exec!(TESTER).should be_a(Syscmd::Command)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return an executed Syscmd::Command object" do
|
16
|
+
cmd = subject.exec!(TESTER)
|
17
|
+
cmd.executed?.should == true
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/spec/tester.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# == Synopsis
|
3
|
+
#
|
4
|
+
# tester: test script for syscmd
|
5
|
+
#
|
6
|
+
# == Usage
|
7
|
+
#
|
8
|
+
# tester [OPTION]
|
9
|
+
#
|
10
|
+
# OPTIONS:
|
11
|
+
# --help, -h display this page
|
12
|
+
# --stdout, -s MSG echo MSG to stdout
|
13
|
+
# --stdoutrepeat, -S COUNT print the stdout message COUNT times
|
14
|
+
# (DEFAULT: 1)
|
15
|
+
# --stderr, -e MSG echo MSG to stderr
|
16
|
+
# --stderrrepeat, -E COUNT print the stderr message COUNT times
|
17
|
+
# (DEFAULT: 1)
|
18
|
+
# --exitcode, -x CODE return exit code CODE
|
19
|
+
# --debug
|
20
|
+
|
21
|
+
require "getoptlong"
|
22
|
+
require "rdoc/usage"
|
23
|
+
|
24
|
+
opts = GetoptLong.new(
|
25
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
26
|
+
[ '--stdout', '-s', GetoptLong::REQUIRED_ARGUMENT ],
|
27
|
+
[ '--stdoutcount', '-S', GetoptLong::REQUIRED_ARGUMENT ],
|
28
|
+
[ '--stderr', '-e', GetoptLong::REQUIRED_ARGUMENT ],
|
29
|
+
[ '--stderrcount', '-E', GetoptLong::REQUIRED_ARGUMENT ],
|
30
|
+
[ '--exitcode', '-x', GetoptLong::REQUIRED_ARGUMENT ],
|
31
|
+
[ '--debug', GetoptLong::OPTIONAL_ARGUMENT ]
|
32
|
+
)
|
33
|
+
|
34
|
+
options = {}
|
35
|
+
exitcode = 0
|
36
|
+
|
37
|
+
msg_stdout = nil
|
38
|
+
count_stdout = 1
|
39
|
+
|
40
|
+
msg_stderr = nil
|
41
|
+
count_stderr = 1
|
42
|
+
|
43
|
+
opts.each do |opt, arg|
|
44
|
+
case opt
|
45
|
+
when '--help'
|
46
|
+
RDoc::usage
|
47
|
+
when '--stdout'
|
48
|
+
msg_stdout = arg
|
49
|
+
when '--stdoutcount'
|
50
|
+
count_stdout = arg.to_i
|
51
|
+
when '--stderr'
|
52
|
+
msg_stderr = arg
|
53
|
+
when '--stderrcount'
|
54
|
+
count_stderr = arg.to_i
|
55
|
+
when '--exitcode'
|
56
|
+
exitcode = arg.to_i
|
57
|
+
when '--debug'
|
58
|
+
# not yet implemented
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if msg_stdout
|
63
|
+
count_stdout.times do
|
64
|
+
$stdout.print "#{msg_stdout}\n"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if msg_stderr
|
69
|
+
count_stderr.times do
|
70
|
+
$stderr.print "#{msg_stderr}\n"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
exit exitcode
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: syscmd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Till Salzer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-18 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: till.salzer@googlemail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
- LICENSE
|
25
|
+
files:
|
26
|
+
- README.rdoc
|
27
|
+
- VERSION.yml
|
28
|
+
- lib/syscmd/popen.rb
|
29
|
+
- lib/syscmd.rb
|
30
|
+
- spec/command_exec_spec.rb
|
31
|
+
- spec/command_spec.rb
|
32
|
+
- spec/spec_helper.rb
|
33
|
+
- spec/syscmd_spec.rb
|
34
|
+
- spec/tester.rb
|
35
|
+
- LICENSE
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/tsalzer/syscmd
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --inline-source
|
43
|
+
- --charset=UTF-8
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.4
|
62
|
+
signing_key:
|
63
|
+
specification_version: 2
|
64
|
+
summary: Wrapper for OS command execution
|
65
|
+
test_files: []
|
66
|
+
|