easysh 0.1.1 → 0.1.2

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