easysh 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.
- data/lib/easysh.rb +185 -0
- data/test/test_easysh.rb +105 -0
- metadata +48 -0
data/lib/easysh.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
# EasySH examples:
|
2
|
+
#
|
3
|
+
# sh = EasySH.new
|
4
|
+
#
|
5
|
+
# # basic usage
|
6
|
+
# puts sh.ls
|
7
|
+
# puts sh['/bin/ls']
|
8
|
+
#
|
9
|
+
# # command parameters
|
10
|
+
# puts sh.ls['/bin']._l
|
11
|
+
# puts sh.ls._l '/bin'
|
12
|
+
# puts sh.ls('/bin', :l, :color => :always)
|
13
|
+
# puts sh['/bin/ls', '-l', :color => :always]
|
14
|
+
#
|
15
|
+
# # enumerable
|
16
|
+
# sh.ls.max
|
17
|
+
# sh.ls.sort
|
18
|
+
# sh.ls.chars.to_a.sample(5)
|
19
|
+
# sh.cat('/dev/urandom').bytes.first(10)
|
20
|
+
# sh.ls._l.map{|l| l.split}
|
21
|
+
# sh.ps._e._o('euser,comm').map(&:split).group_by(&:first)
|
22
|
+
#
|
23
|
+
# # chaining commands
|
24
|
+
# sudo = sh.sudo
|
25
|
+
# tail = sh.tail
|
26
|
+
# sudo[tail._f '/var/log/everything.log'].lines { |l| puts l.upcase }
|
27
|
+
# sudo.tail._f '/var/log/everything.log' do |l| puts l.upcase end
|
28
|
+
#
|
29
|
+
# lab = sh.ssh['lab'] # or, sh.ssh.lab
|
30
|
+
# puts lab.ls._l '/bin', color: :always
|
31
|
+
#
|
32
|
+
# # redirects
|
33
|
+
# puts sh.echo('hello') > '/tmp/test'
|
34
|
+
# puts sh.echo 'hello', 1 => '/tmp/stdout', 2 => '/tmp/stderr'
|
35
|
+
# puts sh.cat < '/tmp/test'
|
36
|
+
# puts sh.cat 0 => '/tmp/fffff'
|
37
|
+
#
|
38
|
+
# # pipes
|
39
|
+
# puts sh.man('ls') | sh.tail(n: 30) | sh.head(:n, 4)
|
40
|
+
#
|
41
|
+
# grep = sh['grep']
|
42
|
+
# filter = grep['problem'] | grep._v['bugs']
|
43
|
+
# puts sh.man.ls | filter
|
44
|
+
#
|
45
|
+
# kat = sh.cat
|
46
|
+
# puts kat['/tmp/foo'] | (kat | kat | kat.|(kat) | (kat | kat) | (kat | kat))
|
47
|
+
#
|
48
|
+
# # exit status
|
49
|
+
# p = sh.which('bash')
|
50
|
+
# puts p
|
51
|
+
# p.exitstatus # => #<Process::Status: pid 5931 exit 0>
|
52
|
+
# p = sh.which.nonexists
|
53
|
+
# puts p
|
54
|
+
# p.exitstatus # => #<Process::Status: pid 6156 exit 1>
|
55
|
+
#
|
56
|
+
#
|
57
|
+
# # instant mode
|
58
|
+
# # tired with 'puts' and 'to_s' in REPL? just set instant = true
|
59
|
+
# # [2] pry(main)> sh.instant = false; sh.uptime
|
60
|
+
# # => #<EasySH: uptime>
|
61
|
+
# # [3] pry(main)> sh.instant = true; sh.uptime
|
62
|
+
# # => 22:14:23 up 1 day, 4:02, 12 users, load average: 0.69, 0.65, 0.67
|
63
|
+
# # [4] pry(main)> sh = EasySH.instant; sh.uname
|
64
|
+
# # => Linux
|
65
|
+
#
|
66
|
+
#
|
67
|
+
class EasySH < Struct.new(:cmd, :opt, :chain, :instant)
|
68
|
+
include Enumerable
|
69
|
+
|
70
|
+
attr_reader :exitstatus
|
71
|
+
|
72
|
+
def method_missing name, *args, &block # :no-doc:
|
73
|
+
begin
|
74
|
+
return super(name, *args, &block)
|
75
|
+
rescue NoMethodError, ArgumentError => ex
|
76
|
+
# continue
|
77
|
+
end
|
78
|
+
|
79
|
+
r = if name.is_a? EasySH
|
80
|
+
self.class.new [*cmd, *name.cmd], Hash[*opt, *name.opt], chain, instant
|
81
|
+
else
|
82
|
+
args = [name.to_s.gsub(/^_+/) {|s| '-' * s.size}, *args]
|
83
|
+
*args, opt = *args if args.last.is_a?(Hash) && args.last.keys.find{|k| k.is_a? Integer}
|
84
|
+
args = args.map do |a|
|
85
|
+
case a
|
86
|
+
when Symbol
|
87
|
+
"-#{a.length > 1 ? '-' : ''}#{a}"
|
88
|
+
when Hash
|
89
|
+
a.map { |k,v| k.length > 1 ? "--#{k}=#{v}" : "-#{k} #{v}" }
|
90
|
+
else
|
91
|
+
a.to_s
|
92
|
+
end
|
93
|
+
end.flatten
|
94
|
+
self.class.new [*cmd, *args], Hash[[*self.opt, *opt]], chain, instant
|
95
|
+
end
|
96
|
+
block ? r.each(&block) : r
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_a; each.to_a; end
|
100
|
+
def to_s(n = "\n"); cmd ? to_a.join(n) : ''; end
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
instant ? to_s : "#<#{self.class}: #{([*chain, [cmd]]).map(&:first).map {|c| c && c.join(' ')}.compact.join(' | ')}>"
|
104
|
+
end
|
105
|
+
|
106
|
+
def |(sh, &block)
|
107
|
+
raise TypeError.new("EasySH expected, got #{sh.inspect}") unless sh.is_a? EasySH
|
108
|
+
self.class.new sh.cmd, sh.opt, [*chain, cmd && [cmd, opt || {}], *sh.chain].compact, instant
|
109
|
+
end
|
110
|
+
|
111
|
+
def < path; self.opt[0] = path; self; end
|
112
|
+
def > path; self.opt[1] = path; self; end
|
113
|
+
|
114
|
+
def to_io
|
115
|
+
return unless cmd
|
116
|
+
|
117
|
+
cur_opt = opt.clone
|
118
|
+
cur_chain = (chain || []) + [[cmd, cur_opt]]
|
119
|
+
pipes = cur_chain.map { IO.pipe }
|
120
|
+
n = pipes.size
|
121
|
+
cur_opt[0] = pipes[n-1][0] if n > 1
|
122
|
+
lpid, lopt = nil
|
123
|
+
pids = []
|
124
|
+
|
125
|
+
begin
|
126
|
+
cur_chain.reverse.each_with_index do |cmd_opt, i|
|
127
|
+
i = n - 1 - i
|
128
|
+
c, o = *cmd_opt
|
129
|
+
o = o.clone
|
130
|
+
o[1] = nil if i < n - 1
|
131
|
+
o[1] ||= pipes[i][1]
|
132
|
+
o[0] = pipes[i-1][0] if i > 0
|
133
|
+
pid = spawn(*c, o)
|
134
|
+
pids << pid
|
135
|
+
lopt, lpid = o, pid if i == n - 1
|
136
|
+
end
|
137
|
+
|
138
|
+
if lopt[1] == pipes[n-1][1]
|
139
|
+
rfd = pipes[n-1][0]
|
140
|
+
(pipes.flatten-[rfd]).each { |io| io.close unless io.closed? }
|
141
|
+
yield rfd
|
142
|
+
end
|
143
|
+
ensure
|
144
|
+
pipes.flatten.each { |io| io.close unless io.closed? }
|
145
|
+
@exitstatus = Process.wait2(lpid)[-1] rescue nil
|
146
|
+
['TERM', 'KILL'].each { |sig| Process.kill sig, *pids rescue nil }
|
147
|
+
Process.waitall rescue nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def each_line
|
152
|
+
return to_enum(:each_line) unless block_given?
|
153
|
+
to_io { |i| while l = i.gets; yield l.chomp; end }
|
154
|
+
end
|
155
|
+
|
156
|
+
def each_char
|
157
|
+
return to_enum(:each_char) unless block_given?
|
158
|
+
to_io { |i| while c = i.getc; yield c; end }
|
159
|
+
end
|
160
|
+
|
161
|
+
def each_byte
|
162
|
+
return to_enum(:each_byte) unless block_given?
|
163
|
+
to_io { |i| while b = i.getbyte; yield b; end }
|
164
|
+
end
|
165
|
+
|
166
|
+
alias :call :method_missing
|
167
|
+
alias :[] :method_missing
|
168
|
+
alias :to_ary :to_a
|
169
|
+
alias :lines :each_line
|
170
|
+
alias :each :each_line
|
171
|
+
alias :lines :each_line
|
172
|
+
alias :chars :each_char
|
173
|
+
alias :bytes :each_byte
|
174
|
+
alias :read :to_s
|
175
|
+
alias :! :to_s
|
176
|
+
|
177
|
+
def pretty_print(q)
|
178
|
+
q.text self.inspect
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.instant
|
182
|
+
new(nil, {}, [], true)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
data/test/test_easysh.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'easysh'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
class EasySHTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def sh; @sh ||= EasySH.new; end
|
9
|
+
|
10
|
+
def with_tmpfile(content = [*0..5].join("\n"))
|
11
|
+
tmppath = File.join(Dir.tmpdir, 'test.txt')
|
12
|
+
begin
|
13
|
+
File.open(tmppath, 'w') { |f| f.write content }
|
14
|
+
yield tmppath, content
|
15
|
+
ensure
|
16
|
+
FileUtils.rm_f tmppath
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def fd_count
|
21
|
+
Dir['/proc/self/fd/*'].count
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_fd_count_equal
|
25
|
+
count = fd_count
|
26
|
+
yield
|
27
|
+
assert_equal fd_count, count
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_execute
|
31
|
+
with_tmpfile do |tmppath, content|
|
32
|
+
assert_equal sh.cat(tmppath).to_s, content
|
33
|
+
assert_equal sh.cat[tmppath].to_s, content
|
34
|
+
assert_equal sh['cat', tmppath].to_s, content
|
35
|
+
kat = sh.cat[tmppath]
|
36
|
+
assert_equal kat.to_s, content
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_enumerator
|
41
|
+
with_tmpfile [*10..25].join("\n") do |tmppath, content|
|
42
|
+
kat = sh.cat
|
43
|
+
kat_t = kat[tmppath]
|
44
|
+
assert_equal kat[tmppath].min, '10'
|
45
|
+
assert_equal kat_t.max, '25'
|
46
|
+
|
47
|
+
en = kat_t.each
|
48
|
+
assert_equal en.min, '10'
|
49
|
+
assert_equal en.max, '25'
|
50
|
+
|
51
|
+
assert_equal kat_t.chars.min, "\n"
|
52
|
+
assert_equal kat_t.bytes.min, "\n".ord
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_options
|
57
|
+
with_tmpfile do |tmppath|
|
58
|
+
assert_include sh.ls._l(tmppath).to_s, '-'
|
59
|
+
assert_equal sh.ls._l(tmppath).read, sh.ls(:l)[tmppath].to_s
|
60
|
+
assert_equal sh.ls._l(tmppath).to_s, sh.ls['-l', tmppath].read
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_pipes
|
65
|
+
kat = sh.cat
|
66
|
+
|
67
|
+
with_tmpfile do |tmppath, content|
|
68
|
+
assert_fd_count_equal do
|
69
|
+
assert_equal kat[tmppath].to_s, content
|
70
|
+
assert_equal (kat[tmppath] | kat | kat | kat | kat | kat | kat | kat | kat | kat).to_s, content
|
71
|
+
assert_equal (kat[tmppath] | kat | (kat | (kat | kat | kat) | kat | kat) | kat | kat).to_s, content
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_broken_pipes
|
77
|
+
kat = sh.cat
|
78
|
+
assert_fd_count_equal do
|
79
|
+
assert_equal (sh.echo('hello') | kat | sh.false | kat | kat).read.empty?, true
|
80
|
+
end
|
81
|
+
assert_raise(TypeError) { sh.echo('hello') | 'abc' }
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_instant
|
85
|
+
kat = sh.cat
|
86
|
+
kat.instant = true
|
87
|
+
|
88
|
+
with_tmpfile do |tmppath, content|
|
89
|
+
assert_fd_count_equal do
|
90
|
+
assert_equal kat[tmppath].inspect, content
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_redirects
|
96
|
+
kat = sh.cat
|
97
|
+
with_tmpfile do |tmppath, content|
|
98
|
+
assert_fd_count_equal do
|
99
|
+
assert_equal ((kat < tmppath) | kat).to_s, content
|
100
|
+
((sh.echo('hello') | kat) > tmppath).!
|
101
|
+
assert_equal File.read(tmppath).chomp, 'hello'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: easysh
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Wu Jun
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-17 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Synatactic sugars about shell executables, redirects and pipes
|
15
|
+
email: quark@zju.edu.cn
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/easysh.rb
|
21
|
+
- test/test_easysh.rb
|
22
|
+
homepage: https://github.com/quark-zju/easyshell
|
23
|
+
licenses: []
|
24
|
+
post_install_message:
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements: []
|
41
|
+
rubyforge_project:
|
42
|
+
rubygems_version: 1.8.23
|
43
|
+
signing_key:
|
44
|
+
specification_version: 3
|
45
|
+
summary: Make shell task easier for ruby script.
|
46
|
+
test_files:
|
47
|
+
- test/test_easysh.rb
|
48
|
+
has_rdoc:
|