easysh 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|