shell 0.0.1 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5731c5319990a3fb1a02db2a135c057bf853ded268b98b586776f6fdcce1df85
4
+ data.tar.gz: f2dd57578ebed9655e3b0ba674f3cb927eb721de212758e3c5b1c937e67ad375
5
+ SHA512:
6
+ metadata.gz: bd1c3a801a915e3392865852e362a5809acc51d6e68244e381074f62b50dcb046f3b2963808e48382b2b3f0eb54aa0cb3fba65f0eb04fac5f4899aa048643d99
7
+ data.tar.gz: 1261e7aa66a6d488588332d83c5ea9a7d93bbe30344cd01abc838fb811d3fabb4758dfdb98c679c4090322c4982f811bc76a07c9c6e006b3cd6de141351a6d28
data/.gitignore CHANGED
@@ -1,4 +1,8 @@
1
- *.gem
2
- .bundle
3
- Gemfile.lock
4
- pkg/*
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,6 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ - ruby-head
6
+ before_install: gem install bundler -v 1.16.2
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in shell_utils.gemspec
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in shell.gemspec
4
6
  gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+ 1. Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22
+ SUCH DAMAGE.
data/README.md CHANGED
@@ -1,15 +1,97 @@
1
- # ShellUtils
1
+ # Shell
2
2
 
3
- Some common shell utilities.
3
+ Shell implements an idiomatic Ruby interface for common UNIX shell commands.
4
4
 
5
- ## sh
5
+ It provides users the ability to execute commands with filters and pipes, like +sh+/+csh+ by using native facilities of Ruby.
6
6
 
7
- Execute the command and return the output. The stderr and stdout are merged together into the output. If it exits with a non-zero status, this method will raise an error.
7
+ ## Installation
8
8
 
9
- ## sh_with_code
9
+ Add this line to your application's Gemfile:
10
10
 
11
- Execute the command and return the output and status code. The stderr and stdout are merged together into the output
11
+ ```ruby
12
+ gem 'shell'
13
+ ```
12
14
 
13
- ## escape
15
+ And then execute:
14
16
 
15
- Escapes any number of words and joins them into an escaped string.
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install shell
22
+
23
+ ## Usage
24
+
25
+ ### Temp file creation
26
+
27
+ In this example we will create three +tmpFile+'s in three different folders under the +/tmp+ directory.
28
+
29
+ ```
30
+ sh = Shell.cd("/tmp") # Change to the /tmp directory
31
+ sh.mkdir "shell-test-1" unless sh.exists?("shell-test-1")
32
+ # make the 'shell-test-1' directory if it doesn't already exist
33
+ sh.cd("shell-test-1") # Change to the /tmp/shell-test-1 directory
34
+ for dir in ["dir1", "dir3", "dir5"]
35
+ if !sh.exists?(dir)
36
+ sh.mkdir dir # make dir if it doesn't already exist
37
+ sh.cd(dir) do
38
+ # change to the `dir` directory
39
+ f = sh.open("tmpFile", "w") # open a new file in write mode
40
+ f.print "TEST\n" # write to the file
41
+ f.close # close the file handler
42
+ end
43
+ print sh.pwd # output the process working directory
44
+ end
45
+ end
46
+ ```
47
+
48
+ ### Temp file creation with self
49
+
50
+ This example is identical to the first, except we're using CommandProcessor#transact.
51
+
52
+ CommandProcessor#transact executes the given block against self, in this case +sh+; our Shell object. Within the block we can substitute +sh.cd+ to +cd+, because the scope within the block uses +sh+ already.
53
+
54
+ ```
55
+ sh = Shell.cd("/tmp")
56
+ sh.transact do
57
+ mkdir "shell-test-1" unless exists?("shell-test-1")
58
+ cd("shell-test-1")
59
+ for dir in ["dir1", "dir3", "dir5"]
60
+ if !exists?(dir)
61
+ mkdir dir
62
+ cd(dir) do
63
+ f = open("tmpFile", "w")
64
+ f.print "TEST\n"
65
+ f.close
66
+ end
67
+ print pwd
68
+ end
69
+ end
70
+ end
71
+ ```
72
+
73
+ ### Pipe /etc/printcap into a file
74
+
75
+ In this example we will read the operating system file +/etc/printcap+, generated by +cupsd+, and then output it to a new file relative to the +pwd+ of +sh+.
76
+
77
+ ```
78
+ sh = Shell.new
79
+ sh.cat("/etc/printcap") | sh.tee("tee1") > "tee2"
80
+ (sh.cat < "/etc/printcap") | sh.tee("tee11") > "tee12"
81
+ sh.cat("/etc/printcap") | sh.tee("tee1") >> "tee2"
82
+ (sh.cat < "/etc/printcap") | sh.tee("tee11") >> "tee12"
83
+ ```
84
+
85
+ ## Development
86
+
87
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
88
+
89
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
90
+
91
+ ## Contributing
92
+
93
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/shell.
94
+
95
+ ## License
96
+
97
+ The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause).
data/Rakefile CHANGED
@@ -1,8 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
- require 'rake/testtask'
2
+ require "rake/testtask"
3
3
 
4
- task :test do
5
- Rake::TestTask.new do |t|
6
- Dir['test/*_test.rb'].each{|f| require File.expand_path(f)}
7
- end
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test" << "test/lib"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
8
  end
9
+
10
+ task :default => :test
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require_relative "../lib/shell"
5
+
6
+ require "irb"
7
+ IRB.start(__FILE__)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,462 @@
1
+ # frozen_string_literal: false
2
+ #
3
+ # shell.rb -
4
+ # $Release Version: 0.7 $
5
+ # $Revision: 1.9 $
6
+ # by Keiju ISHITSUKA(keiju@ruby-lang.org)
7
+ #
8
+ # --
9
+ #
10
+ #
11
+ #
12
+
13
+ require "e2mmap"
14
+
15
+ require "forwardable"
16
+
17
+ require "shell/error"
18
+ require "shell/command-processor"
19
+ require "shell/process-controller"
20
+ require "shell/version"
21
+
22
+ # Shell implements an idiomatic Ruby interface for common UNIX shell commands.
23
+ #
24
+ # It provides users the ability to execute commands with filters and pipes,
25
+ # like +sh+/+csh+ by using native facilities of Ruby.
26
+ #
27
+ # == Examples
28
+ #
29
+ # === Temp file creation
30
+ #
31
+ # In this example we will create three +tmpFile+'s in three different folders
32
+ # under the +/tmp+ directory.
33
+ #
34
+ # sh = Shell.cd("/tmp") # Change to the /tmp directory
35
+ # sh.mkdir "shell-test-1" unless sh.exists?("shell-test-1")
36
+ # # make the 'shell-test-1' directory if it doesn't already exist
37
+ # sh.cd("shell-test-1") # Change to the /tmp/shell-test-1 directory
38
+ # for dir in ["dir1", "dir3", "dir5"]
39
+ # if !sh.exists?(dir)
40
+ # sh.mkdir dir # make dir if it doesn't already exist
41
+ # sh.cd(dir) do
42
+ # # change to the `dir` directory
43
+ # f = sh.open("tmpFile", "w") # open a new file in write mode
44
+ # f.print "TEST\n" # write to the file
45
+ # f.close # close the file handler
46
+ # end
47
+ # print sh.pwd # output the process working directory
48
+ # end
49
+ # end
50
+ #
51
+ # === Temp file creation with self
52
+ #
53
+ # This example is identical to the first, except we're using
54
+ # CommandProcessor#transact.
55
+ #
56
+ # CommandProcessor#transact executes the given block against self, in this case
57
+ # +sh+; our Shell object. Within the block we can substitute +sh.cd+ to +cd+,
58
+ # because the scope within the block uses +sh+ already.
59
+ #
60
+ # sh = Shell.cd("/tmp")
61
+ # sh.transact do
62
+ # mkdir "shell-test-1" unless exists?("shell-test-1")
63
+ # cd("shell-test-1")
64
+ # for dir in ["dir1", "dir3", "dir5"]
65
+ # if !exists?(dir)
66
+ # mkdir dir
67
+ # cd(dir) do
68
+ # f = open("tmpFile", "w")
69
+ # f.print "TEST\n"
70
+ # f.close
71
+ # end
72
+ # print pwd
73
+ # end
74
+ # end
75
+ # end
76
+ #
77
+ # === Pipe /etc/printcap into a file
78
+ #
79
+ # In this example we will read the operating system file +/etc/printcap+,
80
+ # generated by +cupsd+, and then output it to a new file relative to the +pwd+
81
+ # of +sh+.
82
+ #
83
+ # sh = Shell.new
84
+ # sh.cat("/etc/printcap") | sh.tee("tee1") > "tee2"
85
+ # (sh.cat < "/etc/printcap") | sh.tee("tee11") > "tee12"
86
+ # sh.cat("/etc/printcap") | sh.tee("tee1") >> "tee2"
87
+ # (sh.cat < "/etc/printcap") | sh.tee("tee11") >> "tee12"
88
+ #
89
+ class Shell
90
+
91
+ include Error
92
+ extend Exception2MessageMapper
93
+
94
+ # debug: true -> normal debug
95
+ # debug: 1 -> eval definition debug
96
+ # debug: 2 -> detail inspect debug
97
+ @debug = false
98
+ @verbose = true
99
+
100
+ @debug_display_process_id = false
101
+ @debug_display_thread_id = true
102
+ @debug_output_mutex = Thread::Mutex.new
103
+ @default_system_path = nil
104
+ @default_record_separator = nil
105
+
106
+ class << Shell
107
+ extend Forwardable
108
+
109
+ attr_accessor :cascade, :verbose
110
+ attr_reader :debug
111
+
112
+ alias debug? debug
113
+ alias verbose? verbose
114
+ @verbose = true
115
+
116
+ def debug=(val)
117
+ @debug = val
118
+ @verbose = val if val
119
+ end
120
+
121
+
122
+ # call-seq:
123
+ # Shell.cd(path)
124
+ #
125
+ # Creates a new Shell instance with the current working directory
126
+ # set to +path+.
127
+ def cd(path)
128
+ new(path)
129
+ end
130
+
131
+ # Returns the directories in the current shell's PATH environment variable
132
+ # as an array of directory names. This sets the system_path for all
133
+ # instances of Shell.
134
+ #
135
+ # Example: If in your current shell, you did:
136
+ #
137
+ # $ echo $PATH
138
+ # /usr/bin:/bin:/usr/local/bin
139
+ #
140
+ # Running this method in the above shell would then return:
141
+ #
142
+ # ["/usr/bin", "/bin", "/usr/local/bin"]
143
+ #
144
+ def default_system_path
145
+ if @default_system_path
146
+ @default_system_path
147
+ else
148
+ ENV["PATH"].split(":")
149
+ end
150
+ end
151
+
152
+ # Sets the system_path that new instances of Shell should have as their
153
+ # initial system_path.
154
+ #
155
+ # +path+ should be an array of directory name strings.
156
+ def default_system_path=(path)
157
+ @default_system_path = path
158
+ end
159
+
160
+ def default_record_separator
161
+ if @default_record_separator
162
+ @default_record_separator
163
+ else
164
+ $/
165
+ end
166
+ end
167
+
168
+ def default_record_separator=(rs)
169
+ @default_record_separator = rs
170
+ end
171
+
172
+ # os resource mutex
173
+ mutex_methods = ["unlock", "lock", "locked?", "synchronize", "try_lock"]
174
+ for m in mutex_methods
175
+ def_delegator("@debug_output_mutex", m, "debug_output_"+m.to_s)
176
+ end
177
+
178
+ end
179
+
180
+ # call-seq:
181
+ # Shell.new(pwd, umask) -> obj
182
+ #
183
+ # Creates a Shell object which current directory is set to the process
184
+ # current directory, unless otherwise specified by the +pwd+ argument.
185
+ def initialize(pwd = Dir.pwd, umask = nil)
186
+ @cwd = File.expand_path(pwd)
187
+ @dir_stack = []
188
+ @umask = umask
189
+
190
+ @system_path = Shell.default_system_path
191
+ @record_separator = Shell.default_record_separator
192
+
193
+ @command_processor = CommandProcessor.new(self)
194
+ @process_controller = ProcessController.new(self)
195
+
196
+ @verbose = Shell.verbose
197
+ @debug = Shell.debug
198
+ end
199
+
200
+ # Returns the command search path in an array
201
+ attr_reader :system_path
202
+
203
+ # Sets the system path (the Shell instance's PATH environment variable).
204
+ #
205
+ # +path+ should be an array of directory name strings.
206
+ def system_path=(path)
207
+ @system_path = path
208
+ rehash
209
+ end
210
+
211
+
212
+ # Returns the umask
213
+ attr_accessor :umask
214
+ attr_accessor :record_separator
215
+ attr_accessor :verbose
216
+ attr_reader :debug
217
+
218
+ def debug=(val)
219
+ @debug = val
220
+ @verbose = val if val
221
+ end
222
+
223
+ alias verbose? verbose
224
+ alias debug? debug
225
+
226
+ attr_reader :command_processor
227
+ attr_reader :process_controller
228
+
229
+ def expand_path(path)
230
+ File.expand_path(path, @cwd)
231
+ end
232
+
233
+ # Most Shell commands are defined via CommandProcessor
234
+
235
+ #
236
+ # Dir related methods
237
+ #
238
+ # Shell#cwd/dir/getwd/pwd
239
+ # Shell#chdir/cd
240
+ # Shell#pushdir/pushd
241
+ # Shell#popdir/popd
242
+ # Shell#mkdir
243
+ # Shell#rmdir
244
+
245
+ # Returns the current working directory.
246
+ attr_reader :cwd
247
+ alias dir cwd
248
+ alias getwd cwd
249
+ alias pwd cwd
250
+
251
+ attr_reader :dir_stack
252
+ alias dirs dir_stack
253
+
254
+ # call-seq:
255
+ # Shell.chdir(path)
256
+ #
257
+ # Creates a Shell object which current directory is set to +path+.
258
+ #
259
+ # If a block is given, it restores the current directory when the block ends.
260
+ #
261
+ # If called as iterator, it restores the current directory when the
262
+ # block ends.
263
+ def chdir(path = nil, verbose = @verbose)
264
+ check_point
265
+
266
+ if iterator?
267
+ notify("chdir(with block) #{path}") if verbose
268
+ cwd_old = @cwd
269
+ begin
270
+ chdir(path, nil)
271
+ yield
272
+ ensure
273
+ chdir(cwd_old, nil)
274
+ end
275
+ else
276
+ notify("chdir #{path}") if verbose
277
+ path = "~" unless path
278
+ @cwd = expand_path(path)
279
+ notify "current dir: #{@cwd}"
280
+ rehash
281
+ Void.new(self)
282
+ end
283
+ end
284
+ alias cd chdir
285
+
286
+ # call-seq:
287
+ # pushdir(path)
288
+ # pushdir(path) { &block }
289
+ #
290
+ # Pushes the current directory to the directory stack, changing the current
291
+ # directory to +path+.
292
+ #
293
+ # If +path+ is omitted, it exchanges its current directory and the top of its
294
+ # directory stack.
295
+ #
296
+ # If a block is given, it restores the current directory when the block ends.
297
+ def pushdir(path = nil, verbose = @verbose)
298
+ check_point
299
+
300
+ if iterator?
301
+ notify("pushdir(with block) #{path}") if verbose
302
+ pushdir(path, nil)
303
+ begin
304
+ yield
305
+ ensure
306
+ popdir
307
+ end
308
+ elsif path
309
+ notify("pushdir #{path}") if verbose
310
+ @dir_stack.push @cwd
311
+ chdir(path, nil)
312
+ notify "dir stack: [#{@dir_stack.join ', '}]"
313
+ self
314
+ else
315
+ notify("pushdir") if verbose
316
+ if pop = @dir_stack.pop
317
+ @dir_stack.push @cwd
318
+ chdir pop
319
+ notify "dir stack: [#{@dir_stack.join ', '}]"
320
+ self
321
+ else
322
+ Shell.Fail DirStackEmpty
323
+ end
324
+ end
325
+ Void.new(self)
326
+ end
327
+ alias pushd pushdir
328
+
329
+ # Pops a directory from the directory stack, and sets the current directory
330
+ # to it.
331
+ def popdir
332
+ check_point
333
+
334
+ notify("popdir")
335
+ if pop = @dir_stack.pop
336
+ chdir pop
337
+ notify "dir stack: [#{@dir_stack.join ', '}]"
338
+ self
339
+ else
340
+ Shell.Fail DirStackEmpty
341
+ end
342
+ Void.new(self)
343
+ end
344
+ alias popd popdir
345
+
346
+ # Returns a list of scheduled jobs.
347
+ def jobs
348
+ @process_controller.jobs
349
+ end
350
+
351
+ # call-seq:
352
+ # kill(signal, job)
353
+ #
354
+ # Sends the given +signal+ to the given +job+
355
+ def kill(sig, command)
356
+ @process_controller.kill_job(sig, command)
357
+ end
358
+
359
+ # call-seq:
360
+ # def_system_command(command, path = command)
361
+ #
362
+ # Convenience method for Shell::CommandProcessor.def_system_command.
363
+ # Defines an instance method which will execute the given shell command.
364
+ # If the executable is not in Shell.default_system_path, you must
365
+ # supply the path to it.
366
+ #
367
+ # Shell.def_system_command('hostname')
368
+ # Shell.new.hostname # => localhost
369
+ #
370
+ # # How to use an executable that's not in the default path
371
+ #
372
+ # Shell.def_system_command('run_my_program', "~/hello")
373
+ # Shell.new.run_my_program # prints "Hello from a C program!"
374
+ #
375
+ def Shell.def_system_command(command, path = command)
376
+ CommandProcessor.def_system_command(command, path)
377
+ end
378
+
379
+ # Convenience method for Shell::CommandProcessor.undef_system_command
380
+ def Shell.undef_system_command(command)
381
+ CommandProcessor.undef_system_command(command)
382
+ end
383
+
384
+ # call-seq:
385
+ # alias_command(alias, command, *opts, &block)
386
+ #
387
+ # Convenience method for Shell::CommandProcessor.alias_command.
388
+ # Defines an instance method which will execute a command under
389
+ # an alternative name.
390
+ #
391
+ # Shell.def_system_command('date')
392
+ # Shell.alias_command('date_in_utc', 'date', '-u')
393
+ # Shell.new.date_in_utc # => Sat Jan 25 16:59:57 UTC 2014
394
+ #
395
+ def Shell.alias_command(ali, command, *opts, &block)
396
+ CommandProcessor.alias_command(ali, command, *opts, &block)
397
+ end
398
+
399
+ # Convenience method for Shell::CommandProcessor.unalias_command
400
+ def Shell.unalias_command(ali)
401
+ CommandProcessor.unalias_command(ali)
402
+ end
403
+
404
+ # call-seq:
405
+ # install_system_commands(pre = "sys_")
406
+ #
407
+ # Convenience method for Shell::CommandProcessor.install_system_commands.
408
+ # Defines instance methods representing all the executable files found in
409
+ # Shell.default_system_path, with the given prefix prepended to their
410
+ # names.
411
+ #
412
+ # Shell.install_system_commands
413
+ # Shell.new.sys_echo("hello") # => hello
414
+ #
415
+ def Shell.install_system_commands(pre = "sys_")
416
+ CommandProcessor.install_system_commands(pre)
417
+ end
418
+
419
+ #
420
+ def inspect
421
+ if debug.kind_of?(Integer) && debug > 2
422
+ super
423
+ else
424
+ to_s
425
+ end
426
+ end
427
+
428
+ def self.notify(*opts)
429
+ Shell::debug_output_synchronize do
430
+ if opts[-1].kind_of?(String)
431
+ yorn = verbose?
432
+ else
433
+ yorn = opts.pop
434
+ end
435
+ return unless yorn
436
+
437
+ if @debug_display_thread_id
438
+ if @debug_display_process_id
439
+ prefix = "shell(##{Process.pid}:#{Thread.current.to_s.sub("Thread", "Th")}): "
440
+ else
441
+ prefix = "shell(#{Thread.current.to_s.sub("Thread", "Th")}): "
442
+ end
443
+ else
444
+ prefix = "shell: "
445
+ end
446
+ _head = true
447
+ STDERR.print opts.collect{|mes|
448
+ mes = mes.dup
449
+ yield mes if iterator?
450
+ if _head
451
+ _head = false
452
+ prefix + mes
453
+ else
454
+ " "* prefix.size + mes
455
+ end
456
+ }.join("\n")+"\n"
457
+ end
458
+ end
459
+
460
+ CommandProcessor.initialize
461
+ CommandProcessor.run_config
462
+ end