cmds 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  # coding: utf-8
3
+
2
4
  lib = File.expand_path('../lib', __FILE__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require 'cmds/version'
@@ -17,18 +19,51 @@ Gem::Specification.new do |spec|
17
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
21
  spec.require_paths = ["lib"]
20
-
21
- spec.add_dependency 'nrser', '>= 0.0.17'
22
- spec.add_dependency 'erubis', '~> 2.7'
23
-
24
- spec.add_development_dependency "bundler", "~> 1.5"
22
+
23
+
24
+ # Dependencies
25
+ # ============================================================================
26
+
27
+ # Runtime Dependencies
28
+ # ----------------------------------------------------------------------------
29
+
30
+ # Mu guns
31
+ spec.add_dependency "nrser", '~> 0.1', ">= 0.1.1"
32
+
33
+ # ERB replacement with more features
34
+ #
35
+ # Allows custom auto-escaping, which allows us to shell quote when rendering
36
+ # values into command templates.
37
+ #
38
+ spec.add_dependency 'erubis', '~> 2.7'
39
+
40
+
41
+ # Development Dependencies
42
+ # ----------------------------------------------------------------------------
43
+
44
+ # You've probably heard of Bundler. I like Bundler.
45
+ spec.add_development_dependency "bundler", "~> 1.5"
46
+
47
+ # And you've probably hear of Rake. I don't like Rake.
25
48
  spec.add_development_dependency "rake"
26
- spec.add_development_dependency "rspec"
49
+
50
+ # For coloring debug logs. Want to get rid of it when I finally get around to
51
+ # fixing up logging on config in NRSER and can just use that.
27
52
  spec.add_development_dependency "pastel"
28
- spec.add_development_dependency "yard", "~> 0.9.9"
29
- spec.add_development_dependency "redcarpet"
30
- spec.add_development_dependency 'github-markup'
31
53
 
32
- # A better REPL
33
- spec.add_development_dependency 'pry'
54
+ # Testing with `rspec`
55
+ spec.add_development_dependency "rspec", '~> 3.7'
56
+
57
+ # Doc site generation with `yard`
58
+ spec.add_development_dependency "yard", '~> 0.9.12'
59
+
60
+ # These, along with `//.yardopts` config, are *supposed to* result in
61
+ # rendering markdown files and doc comments using
62
+ # GitHub-Flavored Markdown (GFM), though I'm not sure if it's totally working
63
+ spec.add_development_dependency "redcarpet", '~> 3.4'
64
+ spec.add_development_dependency "github-markup", '~> 1.6'
65
+
66
+ # Nicer REPL experience
67
+ spec.add_development_dependency "pry", '~> 0.10.4'
68
+
34
69
  end
@@ -1,7 +1,16 @@
1
- # deps
1
+ # Requirements
2
+ # =======================================================================
3
+
4
+ # Stdlib
5
+ # -----------------------------------------------------------------------
6
+ require 'pathname'
7
+
8
+ # Deps
9
+ # -----------------------------------------------------------------------
2
10
  require 'nrser'
3
11
 
4
- # project
12
+ # Project / Package
13
+ # -----------------------------------------------------------------------
5
14
  require 'cmds/version'
6
15
  require 'cmds/debug'
7
16
  require 'cmds/util'
@@ -13,7 +22,25 @@ require 'cmds/sugar'
13
22
  require 'cmds/stream'
14
23
  require 'cmds/capture'
15
24
 
25
+
26
+ # Definitions
27
+ # =======================================================================
28
+
16
29
  class Cmds
30
+
31
+ # Constants
32
+ # ============================================================================
33
+
34
+ # Absolute, expanded path to the gem's root directory.
35
+ #
36
+ # @return [Pathname]
37
+ #
38
+ ROOT = ( Pathname.new(__FILE__).dirname / '..' ).expand_path
39
+
40
+
41
+ # Attributes
42
+ # ============================================================================
43
+
17
44
  # ERB stirng template (with Cmds-specific extensions) for the command.
18
45
  #
19
46
  # @return [String]
@@ -26,9 +53,9 @@ class Cmds
26
53
  #
27
54
  # defaults to `[]`.
28
55
  #
29
- # {#prepare} and the methods that invoke it (like {#capture},
56
+ # {#prepare} and the methods that invoke it (like {#capture},
30
57
  # {#stream}, etc.) accept `*args`, which will be appended to
31
- # these values to create the final array for rendering.
58
+ # these values to create the final array for rendering.
32
59
  #
33
60
  # @return [Array<Object>]
34
61
  #
@@ -39,7 +66,7 @@ class Cmds
39
66
  #
40
67
  # defaults to `{}`.
41
68
  #
42
- # {#prepare} and the methods that invoke it (like {#capture},
69
+ # {#prepare} and the methods that invoke it (like {#capture},
43
70
  # {#stream}, etc.) accept `**kwds`, which will be merged on top of
44
71
  # these values to create the final hash for rendering.
45
72
  #
@@ -51,7 +78,7 @@ class Cmds
51
78
  # string or readable IO-like object to use as default input to the
52
79
  # command.
53
80
  #
54
- # {#prepare} and the methods that invoke it (like {#capture},
81
+ # {#prepare} and the methods that invoke it (like {#capture},
55
82
  # {#stream}, etc.) accept an optional block that will override this
56
83
  # value if present.
57
84
  #
@@ -113,7 +140,6 @@ class Cmds
113
140
  attr_reader :chdir
114
141
 
115
142
 
116
-
117
143
  # The results of the last time {Cmds#prepare} was called on the instance.
118
144
  #
119
145
  # A little bit funky, I know, but it turns out to be quite useful.
@@ -126,15 +152,17 @@ class Cmds
126
152
  #
127
153
  attr_reader :last_prepared_cmd
128
154
 
129
-
130
-
155
+
156
+ # Constructor
157
+ # ============================================================================
158
+
131
159
  # Construct a `Cmds` instance.
132
160
  #
133
161
  # @param [String] template
134
- # String template to use when creating the command string to send to the
162
+ # String template to use when creating the command string to send to the
135
163
  # shell via {#prepare}.
136
164
  #
137
- # Allows ERB (positional and keyword), `%s` (positional) and `%{name}`
165
+ # Allows ERB (positional and keyword), `%s` (positional) and `%{name}`
138
166
  # (keyword) placeholders.
139
167
  #
140
168
  # Available as the {#template} attribute.
@@ -145,10 +173,10 @@ class Cmds
145
173
  # Available as the {#args} attribute.
146
174
  #
147
175
  # @param [Boolean] assert:
148
- # When `true`, execution will raise an error if the command doesn't exit
176
+ # When `true`, execution will raise an error if the command doesn't exit
149
177
  # successfully (if the command exits with any status other than `0`).
150
178
  #
151
- # Available as the {#assert} attribute.
179
+ # Available as the {#assert} attribute.
152
180
  #
153
181
  # @param [nil | String | Pathname] chdir:
154
182
  # Optional directory to change into when executing.
@@ -167,8 +195,8 @@ class Cmds
167
195
  # if you want do print the command out and paste it into a terminal.
168
196
  # This is the default.
169
197
  #
170
- # - `:spawn_arg` passes them as an argument to `Process.spawn`. In this
171
- # case they will not be included in the output of {#prepare}
198
+ # - `:spawn_arg` passes them as an argument to `Process.spawn`. In this
199
+ # case they will not be included in the output of {#prepare}
172
200
  # (or {#render}).
173
201
  #
174
202
  # Available as the {#env_mode} attribute.
@@ -182,11 +210,11 @@ class Cmds
182
210
  #
183
211
  # - `nil` performs **no formatting at all*.
184
212
  #
185
- # - `:squish` reduces any consecutive whitespace (including newlines) to
213
+ # - `:squish` reduces any consecutive whitespace (including newlines) to
186
214
  # a single space. This is the default.
187
215
  #
188
- # - `:pretty` tries to keep the general formatting but make it acceptable
189
- # to the shell by adding `\` at the end of lines. See
216
+ # - `:pretty` tries to keep the general formatting but make it acceptable
217
+ # to the shell by adding `\` at the end of lines. See
190
218
  # {Cmds.pretty_format}.
191
219
  #
192
220
  # - An object that responds to `#call` will be called with the command
@@ -226,7 +254,14 @@ class Cmds
226
254
  # {#prepare} has never been called. Kinda funky but ends up being useful.
227
255
  @last_prepared_cmd = nil
228
256
  end # #initialize
229
-
257
+
258
+
259
+ # Instance Methods
260
+ # ============================================================================
261
+ #
262
+ # That ended up here. There are many more topically organized in
263
+ # `//lib/cmds/*.rb` files.
264
+ #
230
265
 
231
266
  # returns a new {Cmds} with the parameters and input merged in
232
267
  def curry *args, **kwds, &input_block
@@ -432,4 +467,4 @@ class Cmds
432
467
 
433
468
  # @!endgroup Execution Instance Methods
434
469
 
435
- end # Cmds
470
+ end # Cmds
@@ -3,11 +3,20 @@ class Cmds
3
3
  attr_accessor :in, :out, :err
4
4
 
5
5
  def initialize
6
- @queue = Queue.new
7
6
  @in = nil
8
7
  @out = $stdout
9
8
  @err = $stderr
10
9
  end
10
+
11
+ def out= value
12
+ value = value.to_s if value.is_a? Pathname
13
+ @out = value
14
+ end
15
+
16
+ def err= value
17
+ value = value.to_s if value.is_a? Pathname
18
+ @err = value
19
+ end
11
20
 
12
21
  def on_out &block
13
22
  @out = block
@@ -32,6 +41,10 @@ class Cmds
32
41
  end
33
42
 
34
43
  def start
44
+ # Initialize a thread-safe queue for passing output from the IO threads
45
+ # back to the main thread
46
+ @queue = Queue.new
47
+
35
48
  # if out is a proc, it's not done
36
49
  out_done = ! @out.is_a?(Proc)
37
50
  # same for err
@@ -73,4 +86,4 @@ class Cmds
73
86
 
74
87
  # end private
75
88
  end # end IOHandler
76
- end # class Cmds
89
+ end # class Cmds
@@ -1,26 +1,51 @@
1
- # stdlib
1
+ ##
2
+ # Handle the actual spawning of child processes via {Process.spawn}
3
+ #
4
+ # These methods are the low-level core of the library. Everything ends up here
5
+ # to actually execute a command, but users should not need to call them
6
+ # directly in most cases.
7
+ ##
8
+
9
+ # Requirements
10
+ # =======================================================================
11
+
12
+ # Stdlib
13
+ # -----------------------------------------------------------------------
2
14
  require 'open3'
3
15
  require 'thread'
4
16
 
5
- # deps
17
+ # Deps
18
+ # -----------------------------------------------------------------------
6
19
  require 'nrser'
7
- require 'nrser/refinements'
8
20
 
9
- # project
21
+ # Project / Package
22
+ # -----------------------------------------------------------------------
10
23
  require 'cmds/pipe'
11
24
  require 'cmds/io_handler'
12
25
 
26
+
27
+ # Refinements
28
+ # =======================================================================
29
+
30
+ using NRSER
31
+
32
+
33
+ # Definitions
34
+ # =======================================================================
35
+
13
36
  class Cmds
37
+ # @!group Spawn Methods
38
+
14
39
  # Low-level static method to spawn and stream inputs and/or outputs using
15
40
  # threads.
16
41
  #
17
42
  # This is the core execution functionality of the whole library - everything
18
- # end up here.
43
+ # ends up here.
19
44
  #
20
- # **WARNING** - This method runs the `cmd` string **AS IS** - no escaping,
21
- # formatting, interpolation, etc. are done at this point.
45
+ # **_WARNING - This method runs the `cmd` string AS IS - no escaping,
46
+ # formatting, interpolation, etc. are done at this point._**
22
47
  #
23
- # The whole rest of the library is built on top of this method to provide
48
+ # The whole rest of the library is built on top of this method to provide
24
49
  # that stuff, and if you're using this library, you probably want to use that
25
50
  # stuff.
26
51
  #
@@ -31,7 +56,9 @@ class Cmds
31
56
  #
32
57
  # https://nickcharlton.net/posts/ruby-subprocesses-with-stdout-stderr-streams.html
33
58
  #
34
- # with major modifications from looking at Ruby's open3 module.
59
+ # with major modifications from looking at Ruby's [open3][] module.
60
+ #
61
+ # [open3]: https://ruby-doc.org/stdlib/libdoc/open3/rdoc/Open3.html
35
62
  #
36
63
  # At the end of the day ends up calling `Process.spawn`.
37
64
  #
@@ -39,13 +66,6 @@ class Cmds
39
66
  # **SHELL-READY** command string. This is important - whatever you feed in
40
67
  # here will be run **AS IS** - no escaping, formatting, etc.
41
68
  #
42
- # @param [nil | String | #read] input
43
- # String or readable input, or `nil` (meaning no input).
44
- #
45
- # Allows {Cmds} instances can pass their `@input` instance variable.
46
- #
47
- # Don't provide input here and via `io_block`.
48
- #
49
69
  # @param [Hash{(Symbol | String) => Object}] env
50
70
  # Hash of `ENV` vars to provide for the command.
51
71
  #
@@ -55,10 +75,23 @@ class Cmds
55
75
  # Pretty much you want to have everything be strings or symbols for this
56
76
  # to make any sense but we're not checking shit at the moment.
57
77
  #
58
- # If the {Cmds#env_mode} is `:inline` it should have already prefixed
59
- # `cmd` with the definitions and not provide this keyword (or provide
78
+ # If the {Cmds#env_mode} is `:inline` it should have already prefixed
79
+ # `cmd` with the definitions and not provide this keyword (or provide
60
80
  # `{}`).
61
81
  #
82
+ # @param [nil | String | #read] input
83
+ # String or readable input, or `nil` (meaning no input).
84
+ #
85
+ # Allows {Cmds} instances can pass their `@input` instance variable.
86
+ #
87
+ # Don't provide input here and via `io_block`.
88
+ #
89
+ # @param [Hash<Symbol, Object>] **spawn_opts
90
+ # Any additional options are passed as the [options][Process.spawn options]
91
+ # to {Process.spawn}
92
+ #
93
+ # [Process.spawn options]: http://ruby-doc.org/core/Process.html#method-c-spawn
94
+ #
62
95
  # @param [#call & (#arity ∈ {0, 1})] &io_block
63
96
  # Optional block to handle io. Behavior depends on arity:
64
97
  #
@@ -83,15 +116,20 @@ class Cmds
83
116
  def self.spawn cmd,
84
117
  env: {},
85
118
  input: nil,
86
- chdir: nil,
119
+ **spawn_opts,
87
120
  &io_block
88
121
  Cmds.debug "entering Cmds#spawn",
89
122
  cmd: cmd,
90
123
  env: env,
91
124
  input: input,
92
- chdir: chdir,
125
+ spawn_opts: spawn_opts,
93
126
  io_block: io_block
94
127
 
128
+ # Process.spawn doesn't like a `nil` chdir
129
+ if spawn_opts.key?( :chdir ) && spawn_opts[:chdir].nil?
130
+ spawn_opts.delete :chdir
131
+ end
132
+
95
133
  # create the handler that will be yielded to the input block
96
134
  handler = Cmds::IOHandler.new
97
135
 
@@ -99,7 +137,7 @@ class Cmds
99
137
  #
100
138
  # if a block was provided it overrides the `input` argument.
101
139
  #
102
- if io_block
140
+ if io_block
103
141
  case io_block.arity
104
142
  when 0
105
143
  # when the input block takes no arguments it returns the input
@@ -137,17 +175,11 @@ class Cmds
137
175
  end # case io_block.arity
138
176
  end # if io_block
139
177
 
140
- # hash of options that will be passed to `spawn`
141
- spawn_opts = {}
142
-
143
- # add chdir if provided
144
- spawn_opts[:chdir] = chdir if chdir
145
-
146
178
  Cmds.debug "looking at input...",
147
179
  input: input
148
180
 
149
181
  # (possibly) create the input pipe... this will be nil if the provided
150
- # input is io-like. in this case it will be used directly in the
182
+ # input is io-like. in this case it will be used directly in the
151
183
  # `spawn` options.
152
184
  in_pipe = case input
153
185
  when nil, String
@@ -176,7 +208,7 @@ class Cmds
176
208
  # `stream` can be told to send it's output to either:
177
209
  #
178
210
  # 1. a Proc that will invoked with each line.
179
- # 2. an io-like object that can be provided as `spawn`'s `:out` or
211
+ # 2. an io-like object that can be provided as `spawn`'s `:out` or
180
212
  # `:err` options.
181
213
  #
182
214
  # in case (1) a `Cmds::Pipe` wrapping read and write piped `IO` instances
@@ -190,8 +222,11 @@ class Cmds
190
222
  ["OUTPUT", :out],
191
223
  ].map do |name, sym|
192
224
  Cmds.debug "looking at #{ name }..."
225
+
226
+ dest = handler.public_send sym
227
+
193
228
  # see if hanlder.out or hanlder.err is a Proc
194
- if handler.send(sym).is_a? Proc
229
+ if dest.is_a? Proc
195
230
  Cmds.debug "#{ name } is a Proc, creating pipe..."
196
231
  pipe = Cmds::Pipe.new name, sym
197
232
  # the corresponding :out or :err option for spawn needs to be
@@ -202,8 +237,8 @@ class Cmds
202
237
 
203
238
  else
204
239
  Cmds.debug "#{ name } should be io-like, setting spawn opt.",
205
- output: handler.send(sym)
206
- spawn_opts[sym] = handler.send(sym)
240
+ output: dest
241
+ spawn_opts[sym] = dest
207
242
  # the pipe is nil!
208
243
  nil
209
244
  end
@@ -321,7 +356,7 @@ class Cmds
321
356
  protected
322
357
  # ========================================================================
323
358
 
324
- # Internal method that simply passes through to {Cmds.spawn}, serving as
359
+ # Internal method that simply passes through to {Cmds.spawn}, serving as
325
360
  # a hook point for subclasses.
326
361
  #
327
362
  # Accepts and returns the same things as {Cmds#stream}.
@@ -335,10 +370,11 @@ class Cmds
335
370
  # include env if mode is spawn argument
336
371
  env: (env_mode == :spawn_arg ? env : {}),
337
372
  chdir: chdir,
373
+ unsetenv_others: !!@unsetenv_others,
338
374
  &io_block
339
375
  end # #spawn
340
376
 
341
377
  # end protected
342
378
 
343
379
 
344
- end # Cmds
380
+ end # Cmds