easysh 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/easysh.rb +185 -0
  2. data/test/test_easysh.rb +105 -0
  3. 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
+
@@ -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: