execute_shell 0.0.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,13 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- group :rake do
4
- gem 'rake_tasks', '~> 0.0.1'
5
- end
6
-
7
- group :test do
8
- gem 'Platform', '~> 0.4.0', :require => 'platform'
9
- gem 'open4', '~> 1.0.1'
10
- gem 'win32-open3-19', '~> 0.0.1', :require => 'open3'
11
-
12
- gem 'app_mode', '~> 1.0.0'
13
- end
3
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,20 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ execute_shell (1.0.0)
5
+ Platform (~> 0.4.0)
6
+ open4 (~> 1.0.1)
7
+ win32-open3-19 (~> 0.0.1)
8
+
1
9
  GEM
2
10
  remote: http://rubygems.org/
3
11
  specs:
4
12
  Platform (0.4.0)
5
- app_mode (1.0.1)
6
13
  open4 (1.0.1)
7
- rake (0.8.7)
8
- rake_tasks (0.0.4)
9
- rake (~> 0.8.7)
14
+ rake_tasks (2.0.5)
15
+ test_unit_helper (0.0.1)
10
16
  win32-open3-19 (0.0.1)
11
17
 
12
18
  PLATFORMS
13
19
  ruby
14
20
 
15
21
  DEPENDENCIES
16
- Platform (~> 0.4.0)
17
- app_mode (~> 1.0.0)
18
- open4 (~> 1.0.1)
19
- rake_tasks (~> 0.0.1)
20
- win32-open3-19 (~> 0.0.1)
22
+ execute_shell!
23
+ rake_tasks (~> 2.0.5)
24
+ test_unit_helper (~> 0.0.1)
data/README CHANGED
@@ -20,24 +20,43 @@ in the octagon with a pig.
20
20
 
21
21
  == Usage
22
22
 
23
- 1. Include the ExecuteShell module in your class or module:
23
+ 1. Simply call ExecuteShell.run as needed:
24
24
 
25
- include ExecuteShell
25
+ result = ExecuteShell.run('ls ~')
26
26
 
27
- 2. Call a shell command:
27
+ result will be a ShellResult object containing information
28
+ regarding the executed command.
28
29
 
29
- success, output = shell('ls ~')
30
+ Available methods include:
31
+
32
+ out ...... Standard out.
33
+ err ...... Standard error.
34
+ success? . Whether the command was executed successfully.
35
+ to_s ..... Standard out and standard error concatenated.
36
+
37
+ 2. If you wish to simply get a single success, output, or error value back,
38
+ you may use calls such as:
39
+
40
+ ExecuteShell.run_success?('ls ~')
41
+ ExecuteShell.run_out('ls ~')
42
+ ExecuteShell.run_err('ls ~')
43
+ ExecuteShell.run_to_s('ls ~')
44
+
45
+ These may be chained together and will be returned in an array in the order
46
+ they are specified:
47
+
48
+ ExecuteShell.run_out_err('ls ~')
49
+ ExecuteShell.run_to_s_success_out('ls ~')
50
+ ExecuteShell.run_err_out_success('ls ~')
30
51
 
31
52
  == Additional Notes
32
53
 
33
- * The shell method has a second optional parameter, which is a path.
54
+ * The run method has a second optional parameter, which is a path.
34
55
  If specified, the working directory will be changed to this path prior
35
56
  to executing the shell command. It will be set back to the original
36
57
  working directory before returning to the calling function.
37
58
 
38
- * Success will be false under two conditions. One is if there is any output
39
- to stderr. The other is if an exception is raised. In either case, the
40
- information is returned in the output.
59
+ * Success is based on the content of the err attribute of ShellResult.
41
60
 
42
61
  == Additional Documentation
43
62
 
@@ -45,4 +64,6 @@ in the octagon with a pig.
45
64
 
46
65
  == License
47
66
 
48
- ExecuteShell is released under the GPLv3 license.
67
+ ExecuteShell is released under the {LGPLv3 license}[link:../../license/lgpl].
68
+
69
+ link:../../license/lgplv3-88x31.png
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'execute_shell'
3
- s.version = '0.0.5'
3
+ s.version = '1.0.0'
4
4
 
5
5
  s.summary = 'Cross-platform shell commands.'
6
6
  s.description = %Q{
@@ -13,20 +13,20 @@ in the octagon with a pig.
13
13
  s.email = 'tthetoad@gmail.com'
14
14
  s.homepage = 'http://www.bitbucket.org/ToadJamb/gems_execute_shell'
15
15
 
16
- s.license = 'GPLv3'
16
+ s.license = 'LGPLv3'
17
17
 
18
- s.extra_rdoc_files << 'README'
18
+ s.extra_rdoc_files = Dir['README', 'license/*']
19
19
 
20
20
  s.require_paths = ['lib']
21
- s.files = Dir['lib/**/*.rb', '*']
21
+ s.files = Dir['*', 'lib/**/*.rb', 'license/*']
22
22
  s.test_files = Dir['test/**/*.rb']
23
23
 
24
24
  s.add_dependency 'Platform', '~> 0.4.0'
25
25
  s.add_dependency 'open4', '~> 1.0.1'
26
26
  s.add_dependency 'win32-open3-19', '~> 0.0.1'
27
- s.add_dependency 'app_mode', '~> 1.0.0'
28
27
 
29
- s.add_development_dependency 'rake_tasks', '~> 0.0.1'
28
+ s.add_development_dependency 'rake_tasks', '~> 2.0.5'
29
+ s.add_development_dependency 'test_unit_helper', '~> 0.0.1'
30
30
 
31
31
  s.has_rdoc = true
32
32
  end
data/lib/execute_shell.rb CHANGED
@@ -1,8 +1,45 @@
1
+ #--
2
+ ################################################################################
3
+ # Copyright (C) 2011 Travis Herrick #
4
+ ################################################################################
5
+ # #
6
+ # \v^V,^!v\^/ #
7
+ # ~% %~ #
8
+ # { _ _ } #
9
+ # ( * - ) #
10
+ # | / | #
11
+ # \ _, / #
12
+ # \__.__/ #
13
+ # #
14
+ ################################################################################
15
+ # This program is free software: you can redistribute it #
16
+ # and/or modify it under the terms of the GNU Lesser General Public License #
17
+ # as published by the Free Software Foundation, #
18
+ # either version 3 of the License, or (at your option) any later version. #
19
+ ################################################################################
20
+ # This program is distributed in the hope that it will be useful, #
21
+ # but WITHOUT ANY WARRANTY; #
22
+ # without even the implied warranty of MERCHANTABILITY #
23
+ # or FITNESS FOR A PARTICULAR PURPOSE. #
24
+ # See the GNU Lesser General Public License for more details. #
25
+ # #
26
+ # You should have received a copy of the GNU Lesser General Public License #
27
+ # along with this program. If not, see <http://www.gnu.org/licenses/>. #
28
+ ################################################################################
29
+ #++
30
+
1
31
  gem_name = File.basename(__FILE__, '.rb')
2
32
 
3
- require 'app_mode'
4
33
  require 'platform'
5
- require 'open4' if [:linux].include?(Platform::IMPL)
6
- require 'open3' if [:mingw].include?(Platform::IMPL)
7
34
 
35
+ case Platform::IMPL
36
+ when :linux
37
+ require 'open4'
38
+ when :mingw
39
+ require 'open3'
40
+ end
41
+
42
+ require_relative File.join(gem_name, 'shell_result')
8
43
  require_relative File.join(gem_name, gem_name)
44
+
45
+ ExecuteShell.raise_not_implemented(gem_name) unless ExecuteShell.supported?
@@ -15,62 +15,50 @@
15
15
  # #
16
16
  ################################################################################
17
17
  # This program is free software: you can redistribute it #
18
- # and/or modify it under the terms of the GNU General Public License #
18
+ # and/or modify it under the terms of the GNU Lesser General Public License #
19
19
  # as published by the Free Software Foundation, #
20
20
  # either version 3 of the License, or (at your option) any later version. #
21
- # #
22
- # Commercial licensing may be available for a fee under a different license. #
23
21
  ################################################################################
24
22
  # This program is distributed in the hope that it will be useful, #
25
23
  # but WITHOUT ANY WARRANTY; #
26
24
  # without even the implied warranty of MERCHANTABILITY #
27
25
  # or FITNESS FOR A PARTICULAR PURPOSE. #
28
- # See the GNU General Public License for more details. #
26
+ # See the GNU Lesser General Public License for more details. #
29
27
  # #
30
- # You should have received a copy of the GNU General Public License #
28
+ # You should have received a copy of the GNU Lesser General Public License #
31
29
  # along with this program. If not, see <http://www.gnu.org/licenses/>. #
32
30
  ################################################################################
33
31
  #++
34
32
 
35
33
  # Contains methods for returning output from console commands.
36
- module ExecuteShell
37
- # Indicates the mode that ExecuteShell is running in.
38
- ExecuteShellMode = StateManager.new(:production)
39
-
40
- # Returns output from a console command.
41
- # ==== Input
42
- # [command : String] The command to be run.
43
- # [path : String : Dir.getwd] The path to use for the command.
44
- # ==== Output
45
- # [Boolean] Whether the shell execution was successful.
46
- #
47
- # This is determined in two ways.
48
- # The first is if anything is sent to stderr.
49
- # The second is whether any exceptions occurred.
50
- #
51
- # [String] The output from the shell command.
52
- #
53
- # This will include any messages sent to stderr,
54
- # as well as those sent to stdout.
55
- # Any exceptions are included in this return value, as well.
56
- def shell(command, path = Dir.getwd)
57
- raise_not_implemented(__method__) unless
58
- [:linux, :mingw].include?(Platform::IMPL)
59
-
60
- out = ''
61
- err = ''
62
- success = nil
63
-
64
- path ||= Dir.getwd
65
-
66
- begin
67
- STDOUT.puts command if ExecuteShellMode.development
34
+ class ExecuteShell
35
+ class << self
36
+ # Raises an error indicating that the class/method/feature is not supported.
37
+ # ==== Input
38
+ # [text : String] The class/method/feature that isnot supported.
39
+ def raise_not_implemented(text)
40
+ raise NotImplementedError,
41
+ "#{text} has not been implemented for #{Platform::IMPL}."
42
+ end
43
+
44
+ # Returns output from a console command.
45
+ # ==== Input
46
+ # [command : String] The command to be run.
47
+ # [path : String : Dir.getwd] The path to use for the command.
48
+ # ==== Output
49
+ # [ShellResult] A ShellResult object that contains information
50
+ # regarding the output, errors, and success.
51
+ def run(command, path = File.expand_path(Dir.getwd))
52
+ raise_not_implemented(__method__) unless supported?
53
+
54
+ out = ''
55
+ err = ''
68
56
 
69
57
  block = case Platform::IMPL
70
58
  when :mingw
71
59
  lambda {
72
60
  # Run the command and wait for it to execute.
73
- result = Open3::popen3('cmd') do |std_in, std_out, std_err, thread|
61
+ Open3::popen3('cmd') do |std_in, std_out, std_err, thread|
74
62
  # Set up the command.
75
63
  std_in.puts command
76
64
 
@@ -85,7 +73,7 @@ module ExecuteShell
85
73
  when :linux
86
74
  lambda {
87
75
  # Run the command and wait for it to execute.
88
- result = Open4::popen4('bash') do |pid, std_in, std_out, std_err|
76
+ Open4::popen4('bash') do |pid, std_in, std_out, std_err|
89
77
  # Set up the command.
90
78
  std_in.puts command
91
79
 
@@ -99,93 +87,159 @@ module ExecuteShell
99
87
  }
100
88
  end
101
89
 
102
- wrap_success, wrap_out = wrap_path(path, block)
103
-
104
- # Success is determined by lack of error text.
105
- success = err.empty?
90
+ wrap_path path, block
106
91
 
107
- # Remove all the extra stuff that the cmd prompt adds.
108
- if Platform::IMPL == :mingw
109
- out.gsub!(/\n\n#{path.gsub(%r[/], '\\\\\\')}>\Z/, '')
92
+ out = ShellResult.cleanup(command, path, out)
110
93
 
111
- # replace contains the command line prompt
112
- # and the command up to the first space.
113
- replace = path.gsub(%r[/], '\\\\\\')
114
- replace += '>'
115
- replace += command[0..(command.index(/ /) || 0) - 1]
116
-
117
- # Remove the header portion of the text.
118
- # This includes the Microsoft 'banner' text
119
- # that consumes the first two lines.
120
- out = out.gsub(/\A.+#{replace}.*?$/m, '').strip
121
- end
94
+ return ShellResult.new(out, err)
95
+ end
122
96
 
123
- # Set the error flag and notify the user if anything went wrong.
124
- out = (out + "\n" + err).strip unless success
125
- out += wrap_out.strip unless wrap_success
97
+ # Indicates whether the specified operating system is supported.
98
+ # ==== Input
99
+ # [os : Symbol,String : Platform::IMPL] The operating system to check for.
100
+ # ==== Output
101
+ # [Boolean] A boolean value indicating whether the system is supported.
102
+ # ==== Examples
103
+ # ExecuteShell.supported? #=> true
104
+ # ExecuteShell.supported?(:linux) #=> true
105
+ # ExecuteShell.supported?(:my_os) #=> false
106
+ def supported?(os = Platform::IMPL)
107
+ supported_systems.include? os.to_sym
108
+ end
126
109
 
127
- # Include the wrapper's success flag in the resulting success flag.
128
- success = success && wrap_success
129
- rescue Exception => exc
130
- # Format exception messages.
131
- success = false
132
- out += format_error(exc)
110
+ # Returns an array of the supported systems as returned by Platform::IMPL.
111
+ # ==== Output
112
+ # [Array] The supported operating systems.
113
+ # ==== Examples
114
+ # ExecuteShell.supported_systems #=> [:linux, :mingw]
115
+ def supported_systems
116
+ [:linux, :mingw]
133
117
  end
134
118
 
135
- return success, out
136
- end
119
+ # Allows method calls such as run_success? or run_err_out.
120
+ # ==== Input
121
+ # [method : Symbol] The missing method that is being called.
122
+ # [*args : Array] Arguments that were passed to the method.
123
+ # [&block : Block] The block passed to the method.
124
+ # ==== Output
125
+ # The output depends on the method called.
126
+ #
127
+ # This method supports any method that starts with "run_" and
128
+ # combines methods available from a ShellResult object.
129
+ #
130
+ # For example, the following are all valid calls:
131
+ # ExecuteShell.run_success? "echo 'hi'"
132
+ # ExecuteShell.run_success "echo 'hi'"
133
+ # ExecuteShell.run_success_err_out "echo 'hi'"
134
+ # ExecuteShell.run_out_to_s_err "echo 'hi'"
135
+ #
136
+ # If only one method name is provided, that value will be returned alone.
137
+ #
138
+ # If more than one method name is provided, the results will be returned
139
+ # in an array listing the results in the order specified in the method call.
140
+ def method_missing(method, *args, &block)
141
+ return super unless method.to_s =~ /^run_/
142
+
143
+ # Execute the command.
144
+ shell_result = run(*args)
145
+
146
+ # Get a list of the requested result values.
147
+ method_list = method.to_s.sub(/^run_/, '').split('_')
148
+
149
+ results = []
150
+ skip = nil
151
+
152
+ # Loop through the methods, building an array of result values.
153
+ method_list.each_with_index do |item, i|
154
+ # Skip to the next one if two methods have already been combined.
155
+ # i.e. 'to_s'
156
+ if skip
157
+ skip = false
158
+ next
159
+ end
160
+
161
+ item += '?' if item == 'success'
162
+
163
+ if item == 'to'
164
+ item += "_#{method_list[i + 1]}"
165
+ skip = true
166
+ end
167
+
168
+ results << shell_result.send(item.to_sym)
169
+ end
137
170
 
138
- ############################################################################
139
- private
140
- ############################################################################
141
-
142
- # Formats error messages.
143
- # ==== Input
144
- # [exception : Exception] The error that was raised.
145
- # ==== Output
146
- # [String] The error information in a readable format.
147
- def format_error(exception)
148
- error_format = '%s: %s%s'
149
- error_format % [exception.class, exception.message,
150
- ExecuteShellMode.development ? "\n#{exception.backtrace.join("\n")}" : '']
151
- end
171
+ # Return the result(s) in an appropriate way.
172
+ return case results.length
173
+ when 0; nil
174
+ when 1; results[0]
175
+ else; results
176
+ end
177
+ end
152
178
 
153
- # Raises a NotImplementedError.
154
- # ==== Input
155
- # [method : String] The method that does not have
156
- # an implementation for the current platform.
157
- def raise_not_implemented(method)
158
- raise NotImplementedError,
159
- "#{method} " +
160
- "has not been implemented for #{Platform::IMPL}."
161
- end
179
+ # Indicates that this class will respond to
180
+ # calls such as run_success? or run_err_out.
181
+ # ==== Input
182
+ # [method : Symbol] The method that is being checked.
183
+ # [include_private : Boolean] Whether to include private methods
184
+ # in the search.
185
+ # ==== Output
186
+ # [Boolean] Indicates whether the class responds to the specified method.
187
+ # ==== Examples
188
+ # a #=> b
189
+ def respond_to_missing?(method, include_private)
190
+ return super unless method.to_s =~ /^run_/
191
+
192
+ method_list = method.to_s.sub(/^run_/, '').split('_')
193
+
194
+ skip = nil
195
+
196
+ shell_result = ShellResult.new('', '')
197
+
198
+ # Loop through the methods, checking the validity of each.
199
+ method_list.each_with_index do |item, i|
200
+ # Skip to the next one if two methods have already been combined.
201
+ # i.e. 'to_s'
202
+ if skip
203
+ skip = false
204
+ next
205
+ end
206
+
207
+ item += '?' if item == 'success'
208
+
209
+ if item == 'to'
210
+ item += "_#{method_list[i + 1]}"
211
+ skip = true
212
+ end
213
+
214
+ # Return false if the ShellResult object does not respond to the method.
215
+ return false unless shell_result.respond_to?(item.to_sym)
216
+ end
162
217
 
163
- # Runs a block of code by changing the path first.
164
- # ==== Input
165
- # [path : String] The path to change to prior to running the block.
166
- # [block : Proc] The code block to execute.
167
- # [*args : Array] Any other parameters
168
- # that will be passed on to <tt>block</tt>.
169
- # ==== Output
170
- # [Boolean] Indicates whether the block was executed successfully.
171
- # [String] Any error messages from trapped errors.
172
- def wrap_path(path, block, *args)
173
- path ||= File.expand_path(Dir.getwd)
174
- original = File.expand_path(Dir.getwd)
175
- out = nil
176
-
177
- STDOUT.puts path if ExecuteShellMode.development
178
-
179
- begin
180
- Dir.chdir path unless path == original
181
- block.call(*args)
182
- rescue Exception => exc
183
- # Format exception messages.
184
- out = format_error(exc)
185
- ensure
186
- Dir.chdir original unless path == original
218
+ return true
187
219
  end
188
220
 
189
- return out.nil?, out
221
+ ########################################################################
222
+ private
223
+ ########################################################################
224
+
225
+ # Runs a block of code by changing the path first.
226
+ # ==== Input
227
+ # [path : String] The path to change to prior to running the block.
228
+ # [block : Proc] The code block to execute.
229
+ # [*args : Array] Any other parameters
230
+ # that will be passed on to <tt>block</tt>.
231
+ # ==== Output
232
+ # [String] Any error messages from trapped errors.
233
+ def wrap_path(path, block, *args)
234
+ path ||= File.expand_path(Dir.getwd)
235
+ original = File.expand_path(Dir.getwd)
236
+
237
+ begin
238
+ Dir.chdir path unless path == original
239
+ block.call(*args)
240
+ ensure
241
+ Dir.chdir original unless path == original
242
+ end
243
+ end
190
244
  end
191
245
  end