cmds 0.2.4 → 0.2.5

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