shell_helpers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e423354674b7162de244672ab417617837d4667f
4
+ data.tar.gz: 36527735c314d40be7389de683688e4446c24961
5
+ SHA512:
6
+ metadata.gz: 743a7bbaec3fa0511cbba321b847db43233a3c964bc4a03cbd07577c5ef2ae4e5357d98a9906532c5536842f92f10ecddf815904f2e776035533763a3a9b8fb4
7
+ data.tar.gz: 023a3e4ce0a45b3cde0c94906ae05ab1d15687531f9425eb93b60fd59d1fd17c3fd6939b629617d92f0520d6083f06b729a3e54e1ae2f3a317ce58c6b80bf03f
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ doc/
2
+ pkg/
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown -M kramdown --title "shell_helpers Documentation" --protected
data/ChangeLog.md ADDED
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2015-02-24
2
+
3
+ * Initial release:
4
+
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Damien Robert
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.md ADDED
@@ -0,0 +1,37 @@
1
+ # shell_helpers
2
+
3
+ * [Homepage](https://github.com/DamienRobert/shell_helpers#readme)
4
+ * [Gems]("https://rubygems.org/gems/shell_helpers)
5
+ * [Issues](https://github.com/DamienRobert/shell_helpers/issues)
6
+ * [Documentation](http://rubydoc.info/gems/shell_helpers/frames)
7
+ * [Email](mailto:Damien.Olivier.Robert+gems at gmail.com)
8
+
9
+ ## Description
10
+
11
+ This gem contains a collection of libraries to ease working with the
12
+ shell with ruby.
13
+
14
+ A lot of the ideas here are inspired by the utilities in
15
+ [methadone](https://github.com/davetron5000/methadone). In particular
16
+ `logger.rb` and `sh.rb` which were based on `cli_logger.rb`,
17
+ `cli_logging.rb`, `error.rb`, `exit_now.rb`, `process_status.rb`,
18
+ `run.rb` from methadone.
19
+
20
+ The reason to incorporate them in this gem is that I wanted to be able to
21
+ add some functionalities (such as `log_and_do` for `logger.rb`, and on
22
+ succes and on error callbacks for `sh.rb`), and also to be able to use
23
+ this functionality from other command parsers than methadone
24
+ (like [gli](https://github.com/davetron5000/gli)).
25
+
26
+ ## Install
27
+
28
+ $ gem install shell_helpers
29
+
30
+ ## Copyright
31
+
32
+ Copyright (c) 2015 Damien Robert
33
+
34
+ MIT License. See {file:LICENSE.txt} for details.
35
+
36
+ See above for the copyright: for the files `logger.rb` and `sh.rb` the
37
+ copyright applies only to the diff from the original import from methadone.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+
6
+ begin
7
+ gem 'rubygems-tasks', '~> 0.2'
8
+ require 'rubygems/tasks'
9
+
10
+ Gem::Tasks.new
11
+ rescue LoadError => e
12
+ warn e.message
13
+ warn "Run `gem install rubygems-tasks` to install Gem::Tasks."
14
+ end
15
+
16
+ require 'rake/testtask'
17
+
18
+ Rake::TestTask.new do |test|
19
+ test.libs << 'test'
20
+ test.pattern = 'test/**/test_*.rb'
21
+ test.verbose = true
22
+ end
23
+
24
+ begin
25
+ gem 'yard', '~> 0.8'
26
+ require 'yard'
27
+
28
+ YARD::Rake::YardocTask.new
29
+ rescue LoadError => e
30
+ task :yard do
31
+ abort "Please run `gem install yard` to install YARD."
32
+ end
33
+ end
34
+ task :doc => :yard
data/gemspec.yml ADDED
@@ -0,0 +1,15 @@
1
+ name: shell_helpers
2
+ summary: "Shell Helpers"
3
+ description: |
4
+ Collection of script to ease working with the shell with ruby.
5
+ license: MIT
6
+ authors: Damien Robert
7
+ email: Damien.Olivier.Robert+gems@gmail.com
8
+ homepage: https://github.com/DamienRobert/shell_helpers#readme
9
+
10
+ dependencies:
11
+ drain: ~> 0.1
12
+ development_dependencies:
13
+ minitest: ~> 5.0
14
+ rubygems-tasks: ~> 0.2
15
+ yard: ~> 0.8
@@ -0,0 +1,32 @@
1
+ require 'shell_helpers/version'
2
+
3
+ require 'shellwords'; require 'pathname'; require 'fileutils'
4
+ require 'dr/ruby_ext/core_ext'; require 'dr/ruby_ext/pathname_ext'
5
+ #load everything in shell_helpers/*.rb
6
+ dir=File.expand_path(File.basename(__FILE__).chomp('.rb'), File.dirname(__FILE__))
7
+ Dir.glob(File.expand_path('*.rb',dir)) do |file|
8
+ require file
9
+ end
10
+
11
+ module SH
12
+ include Run #run_command, run_output, run_status, run
13
+ include CLILogging #logger.{debug info warn error fatal}, log_and_do
14
+ include ExitNow #exit_now!
15
+ include Sh #sh, sh!
16
+ include ShellExport #export
17
+ include ShellUtils #find, run_pager
18
+ extend self
19
+ #activates debug mode
20
+ def self.debug(level=Logger::DEBUG)
21
+ #activates logging on Pathname
22
+ Pathname.send(:include, CLILogging)
23
+ logger.level=(level)
24
+ end
25
+ #including SH::FU to add FileUtils
26
+ module FU
27
+ include ::FileUtils
28
+ include ::SH
29
+ extend self
30
+ end
31
+ end
32
+
@@ -0,0 +1,219 @@
1
+ # vim: foldmethod=marker
2
+ #From methadone (cli_logger.rb, cli_logging.rb, last import: v1.3.1-2-g9be3b5a)
3
+ require 'logger'
4
+
5
+ module SH
6
+ # CLILogger {{{
7
+ # A Logger instance that gives better control of messaging the user and
8
+ # logging app activity. At it's most basic, you would use <tt>info</tt>
9
+ # as a replacement for +puts+ and <tt>error</tt> as a replacement for
10
+ # <tt>STDERR.puts</tt>. Since this is a logger, however, you can also
11
+ # use #debug, #warn, and #fatal, and you can control the format and
12
+ # "logging level" as such.
13
+ #
14
+ # So, by default:
15
+ # * debug messages do not appear anywhere
16
+ # * info messages appear on the standard output
17
+ # * warn, error, and fatal message appear on the standard error
18
+ # * The default format of messages is simply the message, no logging
19
+ # cruft, however if your output is redirected to a file, a better
20
+ # timestamped logging format is used
21
+ #
22
+ # You can customize this in several ways:
23
+ # * You can override the devices used by passing different devices to the constructor
24
+ # * You can adjust the level of message that goes to the error logger via error_level=
25
+ # * You can adjust the format for messages to the error logger separately via error_formatter=
26
+ #
27
+ # === Example
28
+ #
29
+ # logger = CLILogger.new
30
+ # logger.debug("Starting up") # => only the standard output gets this
31
+ # logger.warn("careful!") # => only the standard error gets this
32
+ # logger.error("Something went wrong!") # => only the standard error gets this
33
+ #
34
+ # logger = CLILogger.new
35
+ # logger.error_level = Logger::ERROR
36
+ # logger.debug("Starting up") # => only the standard output gets this
37
+ # logger.warn("careful!") # => only the standard OUTPUT gets this
38
+ # logger.error("Something went wrong!") # => only the standard error gets this
39
+ #
40
+ # logger = CLILogger.new('logfile.txt')
41
+ # logger.debug("Starting up") #=> logfile.txt gets this
42
+ # logger.error("Something went wrong!") # => BOTH logfile.txt AND the standard error get this
43
+ class CLILogger < Logger
44
+ BLANK_FORMAT = lambda { |severity,datetime,progname,msg|
45
+ msg + "\n"
46
+ }
47
+
48
+ # Helper to proxy methods to the super class AND to the internal error logger
49
+ # +symbol+:: Symbol for name of the method to proxy
50
+ def self.proxy_method(symbol) #:nodoc:
51
+ old_name = "old_#{symbol}".to_sym
52
+ alias_method old_name,symbol
53
+ define_method symbol do |*args,&block|
54
+ send(old_name,*args,&block)
55
+ @stderr_logger.send(symbol,*args,&block)
56
+ end
57
+ end
58
+
59
+ proxy_method :'formatter='
60
+ proxy_method :'datetime_format='
61
+
62
+ def add(severity, message = nil, progname = nil, &block) #:nodoc:
63
+ if @split_logs
64
+ unless severity >= @stderr_logger.level
65
+ super(severity,message,progname,&block)
66
+ end
67
+ else
68
+ super(severity,message,progname,&block)
69
+ end
70
+ @stderr_logger.add(severity,message,progname,&block)
71
+ end
72
+
73
+ DEFAULT_ERROR_LEVEL = Logger::Severity::WARN
74
+
75
+ # A logger that logs error-type messages to a second device; useful for
76
+ # ensuring that error messages go to standard error. This should be
77
+ # pretty smart about doing the right thing. If both log devices are
78
+ # ttys, e.g. one is going to standard error and the other to the
79
+ # standard output, messages only appear once in the overall output
80
+ # stream. In other words, an ERROR logged will show up *only* in the
81
+ # standard error. If either log device is NOT a tty, then all messages
82
+ # go to +log_device+ and only errors go to +error_device+
83
+ #
84
+ # +log_device+:: device where all log messages should go, based on level
85
+ # By default, this is Logger::Severity::WARN
86
+ # +error_device+:: device where all error messages should go.
87
+ def initialize(log_device=$stdout,error_device=$stderr,
88
+ split_log:log_device.tty? && error_device.tty?)
89
+ super(log_device)
90
+ @stderr_logger = Logger.new(error_device)
91
+
92
+ @split_logs = split_log
93
+ self.level = Logger::Severity::INFO
94
+ @stderr_logger.level = DEFAULT_ERROR_LEVEL
95
+
96
+ self.formatter = BLANK_FORMAT if log_device.tty?
97
+ @stderr_logger.formatter = BLANK_FORMAT if error_device.tty?
98
+ end
99
+
100
+ def level=(level)
101
+ super(level)
102
+ #current_error_level = @stderr_logger.level
103
+ if (level > DEFAULT_ERROR_LEVEL) && @split_logs
104
+ @stderr_logger.level = level
105
+ end
106
+ end
107
+
108
+ # Set the threshold for what messages go to the error device. Note
109
+ # that calling #level= will *not* affect the error logger *unless* both
110
+ # devices are TTYs.
111
+ # +level+:: a constant from Logger::Severity for the level of messages that should go to the error logger
112
+ def error_level=(level)
113
+ @stderr_logger.level = level
114
+ end
115
+
116
+ # Overrides the formatter for the error logger. A future call to
117
+ # #formatter= will affect both, so the order of the calls matters.
118
+ # +formatter+:: Proc that handles the formatting, the same as for #formatter=
119
+ def error_formatter=(formatter)
120
+ @stderr_logger.formatter=formatter
121
+ end
122
+
123
+ end
124
+ #}}}
125
+ # CLILogging {{{
126
+ # Provides easier access to a shared DR::CLILogger instance.
127
+ # Include this module into your class, and #logger provides access to a
128
+ # shared logger. This is handy if you want all of your clases to have
129
+ # access to the same logger, but don't want to (or aren't able to) pass
130
+ # it around to each class.
131
+ # This also provides methods for direct logging without going through the
132
+ # #logger
133
+ #
134
+ # === Example
135
+ #
136
+ # class MyClass
137
+ # include DR::CLILogging
138
+ #
139
+ # def doit
140
+ # debug("About to doit!")
141
+ # if results
142
+ # info("We did it!")
143
+ # else
144
+ # error("Something went wrong")
145
+ # end
146
+ # debug("Done doing it")
147
+ # end
148
+ # end
149
+ #
150
+ # Note that every class that mixes this in shares the *same logger
151
+ # instance*, so if you call #change_logger, this will change the logger
152
+ # for all classes that mix this in. This is likely what you want.
153
+ module CLILogging
154
+ extend self
155
+
156
+ # Access the shared logger. All classes that include this module
157
+ # will get the same logger via this method.
158
+ def logger
159
+ unless Module.class_variable_defined?(:@@logger)
160
+ @@logger = CLILogger.new
161
+ @@logger.progname=$0
162
+ end
163
+ @@logger
164
+ end
165
+
166
+ # Change the global logger that includers will use. Useful if you
167
+ # don't want the default configured logger. Note that the
168
+ # +change_logger+ version is preferred because Ruby will often parse
169
+ # <tt>logger = Logger.new</tt> as the declaration of, and assignment
170
+ # to, of a local variable. You'd need to do
171
+ # <tt>self.logger=Logger.new</tt> to be sure. This method is a bit
172
+ # easier.
173
+ #
174
+ # +new_logger+:: the new logger. May not be nil and should be a logger of some kind
175
+ def change_logger(new_logger)
176
+ raise ArgumentError,"Logger may not be nil" if new_logger.nil?
177
+ @@logger = new_logger
178
+ @@logger.level = @log_level if @log_level
179
+ end
180
+
181
+ alias logger= change_logger
182
+
183
+ LOG_LEVELS = {
184
+ 'debug' => Logger::DEBUG,
185
+ 'info' => Logger::INFO,
186
+ 'warn' => Logger::WARN,
187
+ 'error' => Logger::ERROR,
188
+ 'fatal' => Logger::FATAL,
189
+ }
190
+
191
+ #log the action and execute it
192
+ #Severity is Logger:: DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
193
+ def log_and_do(*args, severity: Logger::INFO, definee: self, **opts, &block)
194
+ msg="log_and_do #{args} on #{self}"
195
+ msg+=" with options #{opts}" unless opts.empty?
196
+ msg+=" with block #{block}" if block
197
+ logger.add(severity,msg)
198
+ if opts.empty?
199
+ definee.send(*args, &block)
200
+ else
201
+ definee.send(*args, **opts, &block)
202
+ end
203
+ end
204
+
205
+ #Include this in place of CLILogging if you prefer to use
206
+ #info directly rather than logger.info
207
+ module Shortcuts #{{{
208
+ extend self
209
+ include CLILogging
210
+
211
+ def debug(progname = nil, &block); logger.debug(progname,&block); end
212
+ def info(progname = nil, &block); logger.info(progname,&block); end
213
+ def warns(progname = nil, &block); logger.warn(progname,&block); end
214
+ def error(progname = nil, &block); logger.error(progname,&block); end
215
+ def fatal(progname = nil, &block); logger.fatal(progname,&block); end
216
+ end
217
+ #}}}
218
+ end #}}}
219
+ end
@@ -0,0 +1,159 @@
1
+ require 'pathname'
2
+
3
+ #backports from ruby 2.1
4
+ class Pathname
5
+ unless method_defined?(:write)
6
+ def write(*args,&b)
7
+ IO.write(self,*args,&b)
8
+ end
9
+ end
10
+ unless method_defined?(:to_path)
11
+ alias to_path to_str
12
+ end
13
+ end
14
+
15
+ autoload :FileUtils, "fileutils"
16
+
17
+ module SH
18
+ #SH::Pathname is Pathname with extra features
19
+ #and methods from FileUtils rather than File when possible
20
+ #to use this module rather than ::Pathname in a module or class,
21
+ #simply define Pathname=SH::Pathname in an appropriate nesting level
22
+ class Pathname < ::Pathname
23
+ def hidden?
24
+ return self.basename.to_s[0]=="."
25
+ end
26
+
27
+ def filewrite(*args,mode:"w",perm: nil,mkdir: false)
28
+ logger.debug("Write to #{self}"+ (perm ? " (#{perm})" : "")) if respond_to?(:logger)
29
+ self.dirname.mkpath if mkdir
30
+ self.open(mode: mode) do |fh|
31
+ fh.chmod(perm) if perm
32
+ #hack to pass an array to write and do the right thing
33
+ if args.length == 1 && Array === args.first
34
+ fh.puts(args.first)
35
+ else
36
+ fh.write(*args)
37
+ end
38
+ yield fh if block_given?
39
+ end
40
+ end
41
+ #write is not the same as filewrite
42
+ #but if given only one argument we should be ok
43
+ unless Pathname.method_defined?(:write)
44
+ alias :write :filewrite
45
+ end
46
+
47
+ #Pathname.new("foo")+"bar" return "foo/bar"
48
+ #Pathname.new("foo").append_name("bar") return "foobar"
49
+ def append_name(*args,join:'')
50
+ Pathname.new(self.to_s+args.join(join))
51
+ end
52
+
53
+ def backup(suffix: '.old', overwrite: true)
54
+ if self.exist?
55
+ filebk=self.append_name(suffix)
56
+ if filebk.exist? and !overwrite
57
+ num=0
58
+ begin
59
+ filebknum=filebk.append_name("%02d" % num)
60
+ num+=1
61
+ end while filebknum.exist?
62
+ filebk=filebknum
63
+ end
64
+ logger.debug "Backup #{self} -> #{filebk}" if respond_to?(:logger)
65
+ FileUtils.mv(self,filebk)
66
+ end
67
+ end
68
+
69
+ def abs_path(base: Pathname.pwd, mode: :clean)
70
+ f=base+self
71
+ case clean
72
+ when :clean
73
+ f.cleanpath
74
+ when :clean_sym
75
+ f.cleanpath(consider_symlink: true)
76
+ when :real
77
+ f.realpath
78
+ when :realdir
79
+ f.realdirpath
80
+ end
81
+ end
82
+
83
+ #wrapper around FileUtils
84
+ #Pathname#rmdir uses Dir.rmdir, but the rmdir from FileUtils is a wrapper
85
+ #around Dir.rmdir that accepts extra options
86
+ #The same for mkdir
87
+ [:chdir, :rmdir, :mkdir].each do |method|
88
+ define_method method do |*args,&b|
89
+ FileUtils.send(method,*args,&b)
90
+ end
91
+ end
92
+ #mkpath is already defined (use FileUtils), but not mkdir_p
93
+ #alias mkdir_p mkpath
94
+
95
+ #Options: verbose, noop, force
96
+ def rm(recursive: false, mode: :file, verbose: true, noop: false, force: false)
97
+ mode.to_s.match(/^recursive-(.*)$/) do |m|
98
+ recursive=true
99
+ mode=m[1].to_sym
100
+ end
101
+ case mode
102
+ when :symlink
103
+ return unless self.symlink?
104
+ when :dangling_symlink
105
+ return unless self.symlink? && ! self.exist?
106
+ end
107
+ if recursive
108
+ FileUtils.rm_r(self, verbose: verbose, noop: noop, force: force)
109
+ else
110
+ FileUtils.rm(self, verbose: verbose, noop: noop, force: force)
111
+ end
112
+ rescue => e
113
+ warn "Error in #{self}.clobber: #{e}"
114
+ end
115
+
116
+ #Options: preserve noop verbose
117
+ def cp(*files,**opts)
118
+ FileUtils.cp(files,self,**opts)
119
+ end
120
+ #Options: force noop verbose
121
+ def mv(*files,**opts)
122
+ FileUtils.mv(files,self,**opts)
123
+ end
124
+
125
+ #find already exists
126
+ def dr_find(*args,&b)
127
+ require 'dr/sh/utils'
128
+ SH::ShellUtils.find(self,*args,&b)
129
+ end
130
+
131
+ def self.home
132
+ return Pathname.new(Dir.home)
133
+ end
134
+ def self.hometilde
135
+ return Pathname.new('~')
136
+ end
137
+ def self.slash
138
+ return Pathname.new("/")
139
+ end
140
+ #differ from Pathname.pwd in that this returns a relative path
141
+ def self.current
142
+ return Pathname.new(".")
143
+ end
144
+ def self.null
145
+ return Pathname.new('/dev/null')
146
+ end
147
+
148
+ #Pathname / 'usr'
149
+ def self./(path)
150
+ new(path)
151
+ end
152
+ #Pathname['/usr']
153
+ def self.[](path)
154
+ new(path)
155
+ end
156
+
157
+ end
158
+
159
+ end
@@ -0,0 +1,139 @@
1
+ # vim: foldmethod=marker
2
+ require 'open3'
3
+ require 'shellwords'
4
+
5
+ module SH
6
+ module Run #{{{
7
+ extend(self)
8
+ #the run_* commands here capture all the output
9
+ def run_command(*command)
10
+ return Open3.capture3(*command)
11
+ end
12
+
13
+ def run_output(*command)
14
+ stdout,_stderr,_status = run_command(*command)
15
+ return stdout
16
+ end
17
+
18
+ def run_status(*command)
19
+ _stdout,_stderr,status = run_command(*command)
20
+ return status.success?
21
+ end
22
+
23
+ #a simple wrapper for %x//
24
+ def run_simple(*command, quiet: false, fail_mode: :error, chomp: false)
25
+ if command.length > 1
26
+ launch=command.shelljoin
27
+ else
28
+ launch=command.first
29
+ end
30
+ launch+=" 2>/dev/null" if quiet
31
+ begin
32
+ out = %x/#{launch}/
33
+ status=$?.success?
34
+ rescue => e
35
+ status=false
36
+ case fail_mode
37
+ when :error
38
+ raise e
39
+ when :empty
40
+ out=""
41
+ end
42
+ end
43
+ out.chomp! if chomp
44
+ return out, status
45
+ end
46
+
47
+ #capture stdout and status, silence stderr
48
+ def run(*args)
49
+ begin
50
+ if Open3.respond_to?(:capture3) then
51
+ out, _error, status=Open3.capture3(*args)
52
+ return out, status.success?
53
+ else
54
+ out = `#{args.shelljoin} 2>/dev/null`
55
+ status=$?
56
+ return out, status.success?
57
+ end
58
+ end
59
+ end
60
+
61
+ #same as Run, but if we get interrupted once, we don't want to launch any more commands
62
+ module Interrupt #{{{
63
+ extend(self)
64
+ @interrupted=false
65
+ def run_command(*args)
66
+ if !@interrupted
67
+ begin
68
+ DR::Run.run_command(*args)
69
+ rescue Interrupt #interruption
70
+ @interrupted=true
71
+ return "", "", false
72
+ end
73
+ else
74
+ return "", "", false
75
+ end
76
+ end
77
+
78
+ def run(*command)
79
+ if !@interrupted
80
+ begin
81
+ return DR::Run.run(*args)
82
+ rescue Interrupt #interruption
83
+ @interrupted=true
84
+ return "", false
85
+ end
86
+ else
87
+ return "", false
88
+ end
89
+ end
90
+ end #}}}
91
+ end #}}}
92
+
93
+ # ProcessStatus {{{
94
+ # from methadone (process_status.rb; last import v1.3.1-2-g9be3b5a)
95
+ #
96
+ # A wrapper/enhancement of Process::Status that handles coercion and expected
97
+ # nonzero statuses
98
+ class ProcessStatus
99
+
100
+ # The exit status, either directly from a Process::Status, from the exit code, or derived from a non-Int value.
101
+ attr_reader :exitstatus, :status
102
+
103
+ # Create the ProcessStatus with the given status.
104
+ # respond to success?,exitstatus,status
105
+ #
106
+ # status:: if this responds to #exitstatus, that method is used to extract the exit code. If it's an Int, that is used as the exit code. Otherwise, it's truthiness is used: 0 for truthy, 1 for falsey.
107
+ # expected:: an Int or Array of Int representing the expected exit status, other than zero, that represent "success".
108
+ #Ex usage: stdout,stderr,status = DR::Run.run_command(*command,**opts)
109
+ #process_status = DR::ProcessStatus.new(status,expected)
110
+ def initialize(status,expected=nil)
111
+ @status=status
112
+ @exitstatus = derive_exitstatus(status)
113
+ @success = ([0] + Array(expected)).include?(@exitstatus)
114
+ end
115
+
116
+ # True if the exit status was a successul (i.e. expected) one.
117
+ def success?
118
+ @success
119
+ end
120
+
121
+ private
122
+
123
+ def derive_exitstatus(status)
124
+ status = if status.respond_to? :exitstatus
125
+ status.exitstatus
126
+ else
127
+ status
128
+ end
129
+ if status.kind_of? Fixnum
130
+ status
131
+ elsif status
132
+ 0
133
+ else
134
+ 1
135
+ end
136
+ end
137
+ end
138
+ # }}}
139
+ end
@@ -0,0 +1,244 @@
1
+ # vim: foldmethod=marker
2
+ #from methadone (error.rb, exit_now.rb, process_status.rb, run.rb; last
3
+ #import v1.3.1-2-g9be3b5a)
4
+ require_relative 'logger'
5
+ require_relative 'run'
6
+ require 'simplecolor'
7
+
8
+ module SH
9
+ # ExitNow {{{
10
+ # Standard exception you can throw to exit with a given status code.
11
+ # Generally, you should prefer DR::ExitNow.exit_now! over using this
12
+ # directly, however you may wish to create a rich hierarchy of exceptions
13
+ # that extend from this in your app, so this is provided if you wish to
14
+ # do so.
15
+ class ExitError < StandardError
16
+ attr_reader :exit_code
17
+ # Create an Error with the given status code and message
18
+ def initialize(exit_code,message=nil)
19
+ super(message)
20
+ @exit_code = exit_code
21
+ end
22
+ end
23
+
24
+ # Provides #exit_now! You might mix this into your business logic classes
25
+ # if they will need to exit the program with a human-readable error
26
+ # message.
27
+ module ExitNow
28
+ # Call this to exit the program immediately
29
+ # with the given error code and message.
30
+ # +exit_code+:: exit status you'd like to exit with
31
+ # +message+:: message to display to the user explaining the problem
32
+ # If +exit_code+ is a String and +message+ is omitted, +exit_code+ will
33
+ # === Examples
34
+ # exit_now!(4,"Oh noes!")
35
+ # # => exit app with status 4 and show the user "Oh noes!" on stderr
36
+ # exit_now!("Oh noes!")
37
+ # # => exit app with status 1 and show the user "Oh noes!" on stderr
38
+ # exit_now!(4)
39
+ # # => exit app with status 4 and dont' give the user a message (how rude of you)
40
+ def exit_now!(exit_code,message=nil)
41
+ if exit_code.kind_of?(String) && message.nil?
42
+ raise ExitError.new(1,exit_code)
43
+ else
44
+ raise ExitError.new(exit_code,message)
45
+ end
46
+ end
47
+ end
48
+ # }}}
49
+ # Sh {{{
50
+ # Module with various helper methods for executing external commands.
51
+ # In most cases, you can use #sh to run commands and have decent logging
52
+ # done.
53
+ # == Examples
54
+ #
55
+ # extend SH::Sh
56
+ #
57
+ # sh 'cp foo.txt /tmp'
58
+ # # => logs the command to DEBUG, executes the command, logs its output to DEBUG and its error output to WARN, returns 0
59
+ #
60
+ # sh 'cp non_existent_file.txt /nowhere_good'
61
+ # # => logs the command to DEBUG, executes the command, logs its output to INFO and its error output to WARN, returns the nonzero exit status of the underlying command
62
+ #
63
+ # sh! 'cp non_existent_file.txt /nowhere_good'
64
+ # # => same as above, EXCEPT, raises a Methadone::FailedCommandError
65
+ #
66
+ # sh 'cp foo.txt /tmp' do
67
+ # # Behaves exactly as before, but this block is called after
68
+ # end
69
+ #
70
+ # sh 'cp non_existent_file.txt /nowhere_good' do
71
+ # # This block isn't called, since the command failed
72
+ # end
73
+ #
74
+ # sh 'ls -l /tmp/' do |stdout|
75
+ # # stdout contains the output of the command
76
+ # end
77
+ # sh 'ls -l /tmp/ /non_existent_dir' do |stdout,stderr|
78
+ # # stdout contains the output of the command,
79
+ # # stderr contains the standard error output.
80
+ # end
81
+
82
+ # FailedCommandError {{{
83
+ # Thrown by certain methods when an externally-called command exits nonzero
84
+ class FailedCommandError < StandardError
85
+ # The command that caused the failure
86
+ attr_reader :command
87
+ # exit_code:: exit code of the command that caused this
88
+ # command:: the entire command-line that caused this
89
+ # custom_error_message:: an error message to show the user instead of
90
+ # the boilerplate one. Useful for allowing this exception to bubble up
91
+ # and exit the program, but to give the user something actionable.
92
+ def initialize(exit_code,command,failure_msg: nil)
93
+ error_message = String(failure_msg).empty? ? "Command '#{command}' exited #{exit_code}" : failure_msg
94
+ super(error_message)
95
+ @command = command
96
+ end
97
+ end
98
+ # }}}
99
+
100
+ module Sh
101
+ include CLILogging
102
+ extend self
103
+ attr_writer :default_sh_options
104
+ def default_sh_options
105
+ @default_sh_options||={log: false, capture: false, on_success: nil, on_failure: nil, expected:0, dryrun: false, escape: false,
106
+ log_level_execute: :debug, log_level_error: :error,
107
+ log_level_stderr: :error, log_level_stdout_success: :info,
108
+ log_level_stdout_fail: :warn}
109
+ end
110
+
111
+ # Run a shell command, capturing and logging its output.
112
+ # keywords:: log+capture
113
+ # If the command completed successfully, it's output is logged at DEBUG.
114
+ # If not, its output is logged at INFO. In either case, its
115
+ # error output is logged at WARN.
116
+ # +:expected+:: an Int or Array of Int representing error codes, <b>in addition to 0</b>, that are expected and therefore constitute success. Useful for commands that don't use exit codes the way you'd like
117
+ # name: pretty name of command
118
+ # on_success,on_failure: blocks to call on success/failure
119
+ # block:: if provided, will be called if the command exited nonzero. The block may take 0, 1, 2, or 3 arguments.
120
+ # The arguments provided are the standard output as a string, standard error as a string, and the processstatus as DR::ProcessStatus
121
+ # You should be safe to pass in a lambda instead of a block, as long as your lambda doesn't take more than three arguments
122
+ #
123
+ # Example
124
+ # sh "cp foo /tmp"
125
+ # sh "ls /tmp" do |stdout|
126
+ # # stdout contains the output of ls /tmp
127
+ # end
128
+ # sh "ls -l /tmp foobar" do |stdout,stderr|
129
+ # # ...
130
+ # end
131
+ #
132
+ # Returns the exit status of the command. Note that if the command doesn't exist, this returns 127.
133
+ def sh(*command, **opts, &block)
134
+ defaults=default_sh_options
135
+ curopts=defaults.dup
136
+ defaults.keys.each do |k|
137
+ v=opts.delete(k)
138
+ curopts[k]=v if v
139
+ end
140
+ log=curopts[:log]
141
+ command=command.first if command.length==1 and command.first.kind_of?(Array)
142
+ command_name = curopts[:name] || command_name(command)
143
+ command=command.shelljoin if curopts[:escape]
144
+ sh_logger.send(curopts[:log_level_execute], SimpleColor.color("Executing '#{command_name}'",:bold)) if log
145
+
146
+ if !curopts[:dryrun]
147
+ if curopts[:capture]
148
+ case command
149
+ when Array
150
+ stdout,stderr,status = DR::Run.run_command(*command,**opts)
151
+ else
152
+ stdout,stderr,status = DR::Run.run_command(command.to_s,**opts)
153
+ end
154
+ else
155
+ case command
156
+ when Array
157
+ system(*command,**opts)
158
+ else
159
+ system(command.to_s,**opts)
160
+ end
161
+ status=$?
162
+ stdout=nil; stderr=nil
163
+ end
164
+ else
165
+ puts command.to_s
166
+ status=0
167
+ stdout=nil; stderr=nil
168
+ end
169
+ process_status = ProcessStatus.new(status,curopts[:expected])
170
+
171
+ sh_logger.send(curopts[:log_level_stderr], SimpleColor.color("stderr output of '#{command_name}':\n",:bold,:red)+stderr) unless stderr.nil? or stderr.strip.length == 0 or !log
172
+ if process_status.success?
173
+ sh_logger.send(curopts[:log_level_stdout_success], SimpleColor.color("stdout output of '#{command_name}':\n",:bold,:green)+stdout) unless stdout.nil? or stdout.strip.length == 0 or !log
174
+ curopts[:on_success].call(stdout,stderr,process_status) unless curopts[:on_success].nil?
175
+ block.call(stdout,stderr,process_status) unless block.nil?
176
+ else
177
+ sh_logger.send(curopts[:log_level_stdout_fail], SimpleColor.color("stdout output of '#{command_name}':\n",:bold,:yellow)+stdout) unless stdout.nil? or stdout.strip.length == 0 or !log
178
+ sh_logger.send(curopts[:log_level_error], SimpleColor.color("Error running '#{command_name}': #{process_status.status}",:red,:bold)) if log
179
+ curopts[:on_failure].call(stdout,stderr,process_status) unless curopts[:on_failure].nil?
180
+ end
181
+ return process_status.success?,stdout,stderr,process_status
182
+
183
+ rescue SystemCallError => ex
184
+ sh_logger.send(curopts[:log_level_error], SimpleColor.color("Error running '#{command_name}': #{ex.message}",:red,:bold)) if log
185
+ return 127
186
+ end
187
+
188
+ # Run a command, throwing an exception if the command exited nonzero.
189
+ # Otherwise, behaves exactly like #sh.
190
+ # Raises DR::FailedCommandError if the command exited nonzero.
191
+ # Examples:
192
+ #
193
+ # sh!("rsync foo bar")
194
+ # # => if command fails, app exits and user sees: "error: Command 'rsync foo bar' exited 12"
195
+ # sh!("rsync foo bar", :failure_msg => "Couldn't rsync, check log for details")
196
+ # # => if command fails, app exits and user sees: "error: Couldn't rsync, check log for details
197
+ def sh!(*args,failure_msg: nil,**opts, &block)
198
+ on_failure=Proc.new do |*blockargs|
199
+ process_status=blockargs.last
200
+ raise DR::FailedCommandError.new(process_status.exitstatus,command_name(args),failure_msg: failure_msg)
201
+ end
202
+ sh(*args,**opts,on_failure: on_failure,&block)
203
+ end
204
+
205
+ # Override the default logger (which is the one provided by CLILogging).
206
+ # You would do this if you want a custom logger or you aren't mixing-in
207
+ # CLILogging.
208
+ #
209
+ # Note that this method is *not* called <tt>sh_logger=</tt> to avoid annoying situations
210
+ # where Ruby thinks you are setting a local variable
211
+ def change_sh_logger(logger)
212
+ @sh_logger = logger
213
+ end
214
+
215
+ private
216
+ def command_name(command)
217
+ if command.size == 1
218
+ return command.first.to_s
219
+ else
220
+ return command.to_s
221
+ end
222
+ end
223
+
224
+ def sh_logger
225
+ @sh_logger ||= begin
226
+ raise StandardError, "No logger set! Please include DR::CLILogging
227
+ ng or provide your own via #change_sh_logger." unless self.respond_to?(:logger)
228
+ self.logger
229
+ end
230
+ end
231
+
232
+ end
233
+
234
+ #SH::ShLog.sh is like SH::Sh.sh but with login enabled even when
235
+ #command succeed
236
+ module ShLog
237
+ include Sh
238
+ extend self
239
+ @default_sh_options=default_sh_options
240
+ @default_sh_options[:log]=true
241
+ @default_sh_options[:log_level_execute]=:info
242
+ end
243
+ # }}}
244
+ end
@@ -0,0 +1,226 @@
1
+ require 'shellwords'
2
+ require 'dr/ruby_ext/core_ext'
3
+ require_relative 'pathname'
4
+ require_relative 'parser'
5
+
6
+ module SH
7
+ module ShellExport
8
+ extend self
9
+
10
+ #export a value for SHELL consumption
11
+ def export_value(v)
12
+ case v
13
+ when String
14
+ return v.shellescape
15
+ when Array
16
+ return "(#{v.map {|i| i.to_s.shellescape}.join(' ')})"
17
+ when Hash
18
+ return "(#{v.map {|k,v| k.to_s.shellescape+" "+v.to_s.shellescape}.join(' ')})"
19
+ when nil
20
+ return ""
21
+ else
22
+ return v.to_s.shellescape
23
+ end
24
+ end
25
+
26
+ #from {ploum: plim} return something like
27
+ #ploum=plim
28
+ #that can be evaluated by the shell
29
+ def export_hash(hash, local: false, export: false, prefix:"")
30
+ r=""
31
+ r+="local #{hash.keys.map {|s| s.to_s.upcase}.join(" ")}\n" if local
32
+ hash.each do |k,v|
33
+ name=prefix+k.to_s.upcase
34
+ r+="typeset -A #{name};\n" if Hash === v
35
+ r+=name+"="+export_value(v)+";\n"
36
+ end
37
+ r+="export #{hash.keys.map {|k| prefix+k.to_s.upcase}.join(" ")}\n" if export
38
+ return r
39
+ end
40
+
41
+ #export_variable("ploum","plam") yields ploum="plam"
42
+ def export_variable(name, value, local: false, export: false)
43
+ r=""
44
+ #name=name.upcase
45
+ r+="local #{name}\n" if local
46
+ r+="typeset -A #{name};\n" if Hash === value
47
+ r+=name+"="+export_value(value)+";\n"
48
+ r+="export #{name}\n" if export
49
+ return r
50
+ end
51
+
52
+ #export_parse(hash,"name:value")
53
+ #will output name=$(hash[value])
54
+ #special cases: when value = '/' we return the full hash
55
+ # when value ends by /, we return the splitted hash (and name serves
56
+ # as a prefix)
57
+ #Ex: Numenor ~ $ ./mine/00COMPUTERS.rb --export=//
58
+ # HOSTNAME=Numenor;
59
+ # HOSTTYPE=perso;
60
+ # HOMEPATH=/home/dams;...
61
+ # Numenor ~ $ ./mine/00COMPUTERS.rb --export=syst/
62
+ # LAPTOP=true;
63
+ # ARCH=i686;...
64
+ #Remark: in name:value, we don't put name in uppercase
65
+ #But in split hash mode, we put the keys in uppercase (to prevent collisions)
66
+ def export_parse(hash,s)
67
+ r=""
68
+ args=Parser.parse_string(s)
69
+ args[:values].each do |k,v|
70
+ name=k
71
+ if !v
72
+ v=name
73
+ #in split mode, don't use prefix if name gave the value
74
+ name= name.size==1? "all" : "" if name[name.size-1]=="/"
75
+ end
76
+ if v.size>1 && v[v.size-1]=='/'
77
+ all=true
78
+ v=v[0...v.size-1]
79
+ end
80
+ value=hash.keyed_value(v)
81
+ if all
82
+ r+=export_hash(value, local: args[:opts][k]["local"], export: args[:opts][k]["export"], prefix: name)
83
+ else
84
+ r+=export_variable(name,value, local: args[:opts][k]["local"], export: args[:opts][k]["export"])
85
+ end
86
+ end
87
+ return r
88
+ end
89
+ end
90
+
91
+ module ShellUtils
92
+ extend self
93
+
94
+ class << self
95
+ attr_accessor :orig_stdin, :orig_stdout, :orig_stderr
96
+ end
97
+ @orig_stdin=$stdin
98
+ @orig_stdout=$stdout
99
+ @orig_stderr=$stderr
100
+
101
+ #An improved find from Find::find that takes in the block the absolute and relative name of the files (+the directory where the relative file is from), and has filter options
102
+ def find(*paths, filter: nil, follow_symlink: false, depth: nil)
103
+ block_given? or return enum_for(__method__, *paths, filter: filter, follow_symlink: follow_symlink, depth: depth)
104
+
105
+ paths.collect!{|d| raise Errno::ENOENT unless File.exist?(d); Pathname.new(d.dup)}
106
+ paths.collect!{|d| [ d, Pathname.new('.'), d ]}
107
+ while filedata = paths.shift
108
+ file, filerel, fileabs = *filedata
109
+ catch(:prune) do #use throw(:prune) to skip a path
110
+ Dir.chdir(fileabs) do
111
+ #if the filter is true, we don't yield the file
112
+ case filter
113
+ when Proc
114
+ filter.call(file,filerel,fileabs)
115
+ when Array
116
+ filter.all? do |test|
117
+ case test
118
+ when :directory? #special case
119
+ file.directory? && !file.symlink?
120
+ else
121
+ file.send(test)
122
+ end
123
+ end
124
+ end or yield file.dup.taint, filerel.dup.taint, fileabs.dup.taint
125
+ if file.directory? and (depth.nil? or filerel.each_filename.size <= depth) then
126
+ next if !follow_symlink && file.symlink?
127
+ file.children(false).sort.reverse_each do |f|
128
+ fj = file + f
129
+ f = filerel + f
130
+ paths.unshift [fj.untaint,f.untaint,fileabs]
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ #all output is sent to the pager
139
+ def run_pager(opt=nil)
140
+ return unless $stdout.tty? and opt != :never
141
+ read, write = IO.pipe
142
+
143
+ unless Kernel.fork # Child process
144
+ $stdout.reopen(write)
145
+ $stderr.reopen(write) if $stderr.tty?
146
+ read.close
147
+ write.close
148
+ return
149
+ end
150
+
151
+ # Parent process, become pager
152
+ $stdin.reopen(read)
153
+ read.close
154
+ write.close
155
+
156
+ #ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
157
+ lessenv=ENV['LESS']
158
+ lessenv="-FRX" if lessenv.empty?
159
+ lessenv+="F" unless lessenv.match(/F/) or opt == :always
160
+ lessenv+="R" unless lessenv.match(/R/)
161
+ lessenv+="X" unless lessenv.match(/X/)
162
+ ENV['LESS']=lessenv
163
+
164
+ Kernel.select [$stdin] # Wait until we have input before we start the pager
165
+ pager = ENV['PAGER'] || 'less'
166
+ exec pager rescue exec "/bin/sh", "-c", pager
167
+ end
168
+
169
+ #inside run_pager, escape from the pager
170
+ #does not work :-(
171
+ def escape_pager(mode=nil)
172
+ case mode
173
+ when :orig
174
+ stdout=ShellUtils.orig_stdout
175
+ stderr=ShellUtils.orig_stderr
176
+ stdin=ShellUtils.orig_stdin
177
+ else
178
+ stdout=STDOUT
179
+ stderr=STDERR
180
+ stdin=STDIN
181
+ end
182
+ $stdout.reopen(stdout)
183
+ $stderr.reopen(stderr)
184
+ $stdin.reopen(stdin)
185
+ end
186
+
187
+ def output_list(s, split: "\n")
188
+ s=s.shelljoin if s.kind_of?(Array)
189
+ return open("| #{s}").read.split(split)
190
+ end
191
+
192
+ #Stolen from mkmf:
193
+ # Searches for the executable +bin+ on +path+. The default path is your
194
+ # +PATH+ environment variable. If that isn't defined, it will resort to
195
+ # searching /usr/local/bin, /usr/ucb, /usr/bin and /bin.
196
+ # If found, it will return the full path, including the executable name, of
197
+ # where it was found.
198
+ # exts: an array of extensions to add
199
+ def find_executable(bin, path = nil, exts: nil)
200
+ executable_file = lambda do |name|
201
+ name=Pathname.new(name)
202
+ return name if name.file? and name.executable?
203
+ end
204
+ #we use Proc so that 'return' escapes the block
205
+ try_executable = Proc.new do |file|
206
+ return file if executable_file.call(file)
207
+ exts && exts.each {|ext| executable_file.call(ext = file.append_name(ext)) and return ext}
208
+ nil
209
+ end
210
+
211
+ bin=Pathname.new(bin)
212
+ if bin.absolute?
213
+ try_executable.call(bin)
214
+ else
215
+ path ||= ENV['PATH'] || %w[/usr/local/bin /usr/bin /bin]
216
+ path = path.split(File::PATH_SEPARATOR) unless path.kind_of?(Array)
217
+ path.each do |dir|
218
+ dir=Pathname.new(dir)
219
+ try_executable.call(dir+bin)
220
+ end
221
+ end
222
+ nil
223
+ end
224
+
225
+ end
226
+ end
@@ -0,0 +1,4 @@
1
+ module ShellHelpers
2
+ # shell_helpers version
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'shell_helpers/version'
14
+ ShellHelpers::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+
24
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
+
26
+ gem.files = `git ls-files`.split($/)
27
+ gem.files = glob[gemspec['files']] if gemspec['files']
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
33
+
34
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
35
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
36
+
37
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
38
+ %w[ext lib].select { |dir| File.directory?(dir) }
39
+ })
40
+
41
+ gem.requirements = gemspec['requirements']
42
+ gem.required_ruby_version = gemspec['required_ruby_version']
43
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
44
+ gem.post_install_message = gemspec['post_install_message']
45
+
46
+ split = lambda { |string| string.split(/,\s*/) }
47
+
48
+ if gemspec['dependencies']
49
+ gemspec['dependencies'].each do |name,versions|
50
+ gem.add_dependency(name,split[versions])
51
+ end
52
+ end
53
+
54
+ if gemspec['development_dependencies']
55
+ gemspec['development_dependencies'].each do |name,versions|
56
+ gem.add_development_dependency(name,split[versions])
57
+ end
58
+ end
59
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'rubygems'
2
+ require 'minitest/autorun'
@@ -0,0 +1,12 @@
1
+ require 'helper'
2
+ require 'shell_helpers'
3
+
4
+ class TestShellHelpers < Minitest::Test
5
+
6
+ def test_version
7
+ version = ShellHelpers.const_get('VERSION')
8
+
9
+ assert(!version.empty?, 'should have a VERSION constant')
10
+ end
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shell_helpers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Damien Robert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: drain
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubygems-tasks
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
69
+ description: 'Collection of script to ease working with the shell with ruby.
70
+
71
+ '
72
+ email: Damien.Olivier.Robert+gems@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files:
76
+ - ChangeLog.md
77
+ - LICENSE.txt
78
+ - README.md
79
+ files:
80
+ - ".document"
81
+ - ".gitignore"
82
+ - ".yardopts"
83
+ - ChangeLog.md
84
+ - LICENSE.txt
85
+ - README.md
86
+ - Rakefile
87
+ - gemspec.yml
88
+ - lib/shell_helpers.rb
89
+ - lib/shell_helpers/logger.rb
90
+ - lib/shell_helpers/pathname.rb
91
+ - lib/shell_helpers/run.rb
92
+ - lib/shell_helpers/sh.rb
93
+ - lib/shell_helpers/utils.rb
94
+ - lib/shell_helpers/version.rb
95
+ - shell_helpers.gemspec
96
+ - test/helper.rb
97
+ - test/test_shell_helpers.rb
98
+ homepage: https://github.com/DamienRobert/shell_helpers#readme
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.4.5
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Shell Helpers
122
+ test_files: []