shell 0.0.1 → 0.7

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.
@@ -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