easysh 0.1.1 → 0.1.2

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 +209 -80
  2. data/test/test_easysh.rb +25 -0
  3. metadata +1 -1
data/lib/easysh.rb CHANGED
@@ -1,67 +1,177 @@
1
- # EasySH examples:
1
+ # EasySH Examples
2
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.ps._e._o('euser,comm').map(&:split).group_by(&:first)
21
- #
22
- # # chaining commands
23
- # sudo = sh.sudo
24
- # tail = sh.tail
25
- # sudo[tail._f '/var/log/everything.log'].lines { |l| puts l.upcase }
26
- # sudo.tail._f '/var/log/everything.log' do |l| puts l.upcase end
27
- #
28
- # lab = sh.ssh['lab'] # or, sh.ssh.lab
29
- # puts lab.ls._l '/bin', color: :always
30
- #
31
- # # redirects
32
- # puts sh.echo('hello') > '/tmp/test'
33
- # puts sh.echo 'hello', 1 => '/tmp/stdout', 2 => '/tmp/stderr'
34
- # puts sh.cat < '/tmp/test'
35
- # puts sh.cat 0 => '/tmp/fffff'
36
- #
37
- # # pipes
38
- # puts sh.man('ls') | sh.tail(n: 30) | sh.head(:n, 4)
39
- #
40
- # grep = sh['grep']
41
- # filter = grep['problem'] | grep._v['bugs']
42
- # puts sh.man.ls | filter
43
- #
44
- # kat = sh.cat
45
- # puts kat['/tmp/foo'] | (kat | kat | kat.|(kat) | (kat | kat) | (kat | kat))
46
- #
47
- # # exit status
48
- # p = sh.which('bash')
49
- # puts p
50
- # p.status # => #<Process::Status: pid 5931 exit 0>
51
- # p = sh.which.nonexists
52
- # puts p
53
- # p.status # => #<Process::Status: pid 6156 exit 1>
54
- #
3
+ # Basic usage:
4
+ #
5
+ # require 'easysh'
6
+ # sh = EasySH.instant;
7
+ #
8
+ # sh.ls # ls
9
+ # sh['/bin/ls'] # /bin/ls
10
+ #
11
+ # EasySH automatically convert method names, symbols, hashes to meaningful parameters:
12
+ #
13
+ # * `_method` will be converted to `-method`
14
+ # * `__method` will be converted to `--method`
15
+ # * `:symbol` will be converted to `--symbol`
16
+ # * `:s` will be converted to `-s`
17
+ # * `{:a => '1', :long => 2}` will be converted to `-a 1`, `--long=2`
18
+ # * strings will be left untouched.
55
19
  #
56
- # # instant mode
57
- # # tired with 'puts' and 'to_s' in REPL? just set instant = true
58
- # # [2] pry(main)> sh.instant = false; sh.uptime
59
- # # => #<EasySH: uptime>
60
- # # [3] pry(main)> sh.instant = true; sh.uptime
61
- # # => 22:14:23 up 1 day, 4:02, 12 users, load average: 0.69, 0.65, 0.67
62
- # # [4] pry(main)> sh = EasySH.instant; sh.uname
63
- # # => Linux
20
+ # Examples:
21
+ #
22
+ # sh.ls('/bin')._l # ls /bin -l
23
+ # sh.ls._l '/bin' # ls -l /bin
24
+ # sh.ls._l '/bin', color: 'always' # ls /bin -l --color=always
25
+ #
26
+ # EasySH supports method chaining and `[params]`, `method(params)`, just write in any form as you like:
27
+ #
28
+ # sh['ls', '-l', :color => :always]
29
+ # sh.ls '/bin', :l, :color => :always
30
+ # sh.ls('/bin')['-l', :color => :always]
31
+ # sh.ls('/bin')._l(:color => :always)
32
+ # sh.ls._l(:color => :always)['/bin']
33
+ # sh.ls('/bin', :color => :always)._l
34
+ #
35
+ # You can save command with parameters to variables for later use:
36
+ #
37
+ # myls = sh.ls._l :color => :always;
38
+ # myls['/bin'] # note: myls '/bin' will not work since myls is an object, not a method
39
+ #
40
+ # Commands can also be chained freely:
41
+ #
42
+ # sudo = sh.sudo;
43
+ # sudo.whoami
44
+ #
45
+ # lab = sh.ssh.lab; # or: sh.ssh 'lab', sh.ssh['lab'], sh.ssh('lab')
46
+ # lab.ls._l '/bin' # ssh lab ls -l /bin
47
+ #
48
+ # You can pass arrays or EasySH objects(without pipes) as arguments to another EasySH object:
49
+ #
50
+ # cmd = sh.ifconfig.eth0;
51
+ # opt = ['mtu', 1440]
52
+ # sudo[cmd].up # sudo ifconfig eth0 up
53
+ # sudo[cmd, opt].up # sudo ifconfig eth0 up mtu 1440
54
+ # # sudo[cmd | sh.cat] # Error: EasySH objects with pipes are not allowed here.
55
+ #
56
+ # EasySH makes full use of Ruby's Enumerable. `each_line` (`lines`), `each_char` (`chars`), `each_byte` (`bytes`) are available like string. For convenience, `each` is an alias of `each_line`.
57
+ #
58
+ # Use Enumerable for simple or complex tasks:
59
+ #
60
+ # sh.ls.max
61
+ # sh.ls.sort
62
+ # sh.ls.chars.to_a.sample(5) # pick 5 chars randomly from `ls` output
63
+ # sh.ps._e._o('euser,comm').map(&:split).group_by(&:first) # group process names by user name
64
+ #
65
+ # EasySH handles endless stream correctly:
66
+ #
67
+ # sh.cat('/dev/urandom').bytes.first(10)
68
+ # sudo[sh.tail._f '/var/log/everything.log'].lines { |l| puts l.upcase }
69
+ #
70
+ # You can even omit `lines` or `each` sometimes:
71
+ #
72
+ # sh.cat { |l| puts l.upcase }
73
+ # sudo.tail._f '/var/log/everything.log' do |l| puts l.upcase end
74
+ #
75
+ # By not passing a block, you can use external iterator: (Note: in this case, make sure that the iteration does reach the end, otherwise background processes do not exit)
76
+ #
77
+ # iter = sh.ls('/sys/fs').lines
78
+ # iter.next # 'btrfs'
79
+ # iter.next # 'cgroup'
80
+ # iter.next # 'ext4'
81
+ # iter.next # 'fuse'
82
+ # iter.next # StopIteration
83
+ #
84
+ # Redirects
85
+ # ---------
86
+ #
87
+ # Use `<` or `>` (Note: only one input redirect and one output redirect is supported currently):
64
88
  #
89
+ # sh.echo('hello') > '/tmp/test'
90
+ # sh.cat < '/tmp/test'
91
+ # sh.cat < '/tmp/abc' > '/tmp/def'
92
+ #
93
+ # You can also associate file descriptor to file directly by using fd numbers => filename Hash (Note: for more information, see Process.spawn. EasySH will distinct Hash parameters from Hash redirects by
94
+ # checking if the Hash has any numeric key):
95
+ #
96
+ # sh.echo 'hello', 1 => '/tmp/stdout', 2 => '/tmp/stderr'
97
+ # sh.cat 0 => '/tmp/test'
98
+ #
99
+ # Pipes
100
+ # -----
101
+ #
102
+ # Use `|` (Note: redirects except the rightmost output and leftmost input will be ignored):
103
+ #
104
+ # (sh.cat | sh.head(n: 5)).each { |l| puts l.upcase }
105
+ # sh.man('ls') | sh.tail(n: 30) | sh.head(:n, 4) # man ls | tail -n 30 | head -n 4
106
+ # (sh.cat < '/tmp/abc') | sh.cat | sh.cat > '/tmp/def' # cat < /tmp/abc | cat | cat > /tmp/def
107
+ #
108
+ # EasySH objects connected with pipes can be saved for later use:
109
+ #
110
+ # grep = sh['grep']; # sh.grep does not work because grep is provided by Enumerable
111
+ # filter = grep['problem'] | grep._v['bugs'];
112
+ # sh.man.ls | filter
113
+ #
114
+ # Since EasySH does some lazy evaluation. You can add parentheses in anywhere in any order:
115
+ #
116
+ # kat = sh.cat;
117
+ # kat['/tmp/foo'] | (kat | (kat | (kat | kat)) | (kat | kat) | (kat | kat))
118
+ #
119
+ # Exit status
120
+ # -----------
121
+ #
122
+ # Use `exitcode` or `to_i` to get exitcode directly:
123
+ #
124
+ # sh.true.exitcode # => 0
125
+ # sh.false.to_i # => 1
126
+ #
127
+ # `successful?` is `exitcode == 0` and `failed?` is `exitcode != 0`:
128
+ #
129
+ # grep = sh['grep', :q];
130
+ # (sh.echo.hello | grep['world']).failed? # => true
131
+ # (sh.echo.world | grep['world']).successful? # => true
132
+ #
133
+ # Use `status` method to get a Process::Status object about last run status:
134
+ #
135
+ # p = sh.which('bash')
136
+ # p.status # => #<Process::Status: pid 5931 exit 0>
137
+ # p = sh.which.nonexists
138
+ # p.status # => #<Process::Status: pid 6156 exit 1>
139
+ #
140
+ # More sugars
141
+ # -----------
142
+ #
143
+ # An EasySH object behaves like an Array or a String sometimes.
144
+ #
145
+ # If you pass arguments like: `[int]`, `[int, int]`, `[range]`; `[regex]`, `[regex, int]`, then `to_a` or `to_s` will be automatically called:
146
+ #
147
+ # # like Array
148
+ # sh.echo("Line 1\nLine 2\nLine 3")[1] # => "Line 2"
149
+ # sh.echo("Line 1\nLine 2\nLine 3")[-1] # => "Line 3"
150
+ # sh.echo("Line 1\nLine 2\nLine 3")[0, 2] # => ["Line 1", "Line 2"]
151
+ # sh.echo("Line 1\nLine 2\nLine 3")[1..2] # => ["Line 2", "Line 3"]
152
+ #
153
+ # # like String
154
+ # sh.echo("Hello world\nThis is a test")[/T.*$/] # => "This is a test"
155
+ # sh.echo("Hello world\nThis is a test")[/T.* ([^ ]*)$/, 1] # => "test"
156
+ #
157
+ # Instant mode
158
+ # ------------
159
+ # EasySH object with `instant = true` will execute command when `inspect` is called, which is useful in REPL environment like pry or irb.
160
+ #
161
+ # If you like traditional `inspect` behavior, you can create the `sh` object using:
162
+ #
163
+ # sh = EasySH.new
164
+ #
165
+ # or set `instant` to false:
166
+ #
167
+ # sh.instant = false
168
+ #
169
+ # With `instant = false`, you need additional `to_s` or `to_a` or `to_i` etc. to get command executed:
170
+ #
171
+ # [1] pry(main)> sh = EasySH.new; sh.uname
172
+ # => #<EasySH: uname>
173
+ # [2] pry(main)> sh.uname.to_s
174
+ # => "Linux"
65
175
  #
66
176
  class EasySH < Struct.new(:cmd, :opt, :chain, :instant) # :no-doc:
67
177
  include Enumerable
@@ -75,23 +185,34 @@ class EasySH < Struct.new(:cmd, :opt, :chain, :instant) # :no-doc:
75
185
  # continue
76
186
  end
77
187
 
78
- r = if name.is_a? EasySH
79
- self.class.new [*cmd, *name.cmd], Hash[*opt, *name.opt], chain, instant
188
+ args = [name, *args]
189
+ *args, opt = *args if args.last.is_a?(Hash) && args.last.keys.find{|k| k.is_a? Integer}
190
+ opt ||= {}
191
+ args = args.map.with_index do |a, i|
192
+ case a
193
+ when Symbol
194
+ if i == 0
195
+ a.to_s.gsub(/^_+/) {|s| '-' * s.size}
80
196
  else
81
- args = [name && name.to_s.gsub(/^_+/) {|s| '-' * s.size}, *args].compact
82
- *args, opt = *args if args.last.is_a?(Hash) && args.last.keys.find{|k| k.is_a? Integer}
83
- args = args.map do |a|
84
- case a
85
- when Symbol
86
- "-#{a.length > 1 ? '-' : ''}#{a}"
87
- when Hash
88
- a.map { |k,v| k.length > 1 ? "--#{k}=#{v}" : ["-#{k}", v.to_s] }
89
- else
90
- a.to_s
91
- end
92
- end.flatten
93
- self.class.new [*cmd, *args], Hash[[*self.opt, *opt]], chain, instant
197
+ "-#{a.length > 1 ? '-' : ''}#{a}"
94
198
  end
199
+ when Hash
200
+ a.map { |k,v| k.length > 1 ? "--#{k}=#{v}" : ["-#{k}", v.to_s] }
201
+ when EasySH
202
+ # no Pipe allowed
203
+ raise ArgumentError.new("#{self.class} argument can not be #{self.class} with pipes") if a.chain && !a.chain.empty?
204
+ opt = Hash[[*opt, *a.opt]]
205
+ a.cmd
206
+ when NilClass
207
+ nil
208
+ when Array
209
+ a
210
+ else
211
+ a.to_s
212
+ end
213
+ end.compact.flatten
214
+
215
+ r = self.class.new [*cmd, *args], Hash[[*self.opt, *opt]], chain, instant
95
216
  block ? r.each(&block) : r
96
217
  end
97
218
 
@@ -99,7 +220,12 @@ class EasySH < Struct.new(:cmd, :opt, :chain, :instant) # :no-doc:
99
220
  def to_s(n = "\n"); cmd ? to_a.join(n) : ''; end
100
221
 
101
222
  def inspect
102
- instant ? to_s : "#<#{self.class}: #{([*chain, [cmd]]).map(&:first).map {|c| c && c.join(' ')}.compact.join(' | ')}>"
223
+ if instant
224
+ s = to_s
225
+ s.empty? ? nil : s
226
+ else
227
+ "#<#{self.class}: #{([*chain, [cmd]]).map(&:first).map {|c| c && c.join(' ')}.compact.join(' | ')}>"
228
+ end
103
229
  end
104
230
 
105
231
  def |(sh, &block)
@@ -107,8 +233,8 @@ class EasySH < Struct.new(:cmd, :opt, :chain, :instant) # :no-doc:
107
233
  self.class.new sh.cmd, sh.opt, [*chain, cmd && [cmd, opt || {}], *sh.chain].compact, instant
108
234
  end
109
235
 
110
- def < path; self.opt[0] = path; self; end
111
- def > path; self.opt[1] = path; self; end
236
+ def < path; self.opt ||= {}; self.opt[0] = path; self; end
237
+ def > path; self.opt ||= {}; self.opt[1] = path; self; end
112
238
 
113
239
  def to_io
114
240
  return unless cmd
@@ -188,8 +314,11 @@ class EasySH < Struct.new(:cmd, :opt, :chain, :instant) # :no-doc:
188
314
  ! successful?
189
315
  end
190
316
 
317
+ def to_ary
318
+ [*cmd]
319
+ end
320
+
191
321
  alias :call :method_missing
192
- alias :to_ary :to_a
193
322
  alias :lines :each_line
194
323
  alias :each :each_line
195
324
  alias :lines :each_line
data/test/test_easysh.rb CHANGED
@@ -156,4 +156,29 @@ class EasySHTest < Test::Unit::TestCase
156
156
  assert_not_equal sh1.status, nil
157
157
  assert_equal sh1.exitcode, 0
158
158
  end
159
+
160
+ def test_sh_in_sh
161
+ sudo = sh.sudo
162
+ cmd = sh.ifconfig.eth0
163
+
164
+ assert_equal sudo[cmd].cmd, sh.sudo.ifconfig.eth0.cmd
165
+ assert_equal sh.sudo(cmd).cmd, sh.sudo.ifconfig.eth0.cmd
166
+ assert_equal sudo[cmd, 'up'].cmd, sh.sudo.ifconfig.eth0.up.cmd
167
+ assert_equal sh.sudo(cmd).up.cmd, sh.sudo.ifconfig.eth0.up.cmd
168
+
169
+ assert_raise(ArgumentError) { sudo[cmd | cmd] }
170
+
171
+ opt = (sh > '/tmp/output')
172
+ assert_equal sudo.cmd, sudo[opt].cmd
173
+ assert_not_equal sudo.opt, sudo[opt].opt
174
+ assert_equal sudo[opt].opt[1], '/tmp/output'
175
+
176
+ mtu = sh.mtu['1440']
177
+ ref_cmd = sh.sudo.ifconfig.eth0.up.mtu['1440'].cmd
178
+ assert_equal ref_cmd, sh.sudo[cmd].up[mtu].cmd
179
+ assert_equal ref_cmd, sh.sudo[cmd, 'up', mtu].cmd
180
+ assert_equal ref_cmd, sh.sudo[cmd, ['up', mtu]].cmd
181
+ assert_equal ref_cmd, sh.sudo(cmd, 'up', mtu).cmd
182
+ assert_equal ref_cmd, sh.sudo(cmd, ['up', 'mtu', '1440']).cmd
183
+ end
159
184
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easysh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: