shell_helpers 0.1.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.
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: []