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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/Gemfile +1 -1
- data/README.md +352 -224
- data/cmds.gemspec +46 -11
- data/lib/cmds.rb +55 -20
- data/lib/cmds/io_handler.rb +15 -2
- data/lib/cmds/spawn.rb +70 -34
- data/lib/cmds/util.rb +3 -12
- data/lib/cmds/util/defaults.rb +45 -16
- data/lib/cmds/util/shell_escape.rb +101 -0
- data/lib/cmds/util/tokenize_option.rb +141 -70
- data/lib/cmds/util/tokenize_value.rb +154 -0
- data/lib/cmds/version.rb +1 -1
- data/spec/cmds/env_spec.rb +71 -1
- data/spec/cmds/prepare_spec.rb +58 -7
- data/spec/cmds/stream/output_spec.rb +69 -0
- data/spec/cmds/util/shell_escape/quote_dance_spec.rb +18 -0
- data/spec/spec_helper.rb +2 -0
- metadata +36 -24
data/cmds.gemspec
CHANGED
@@ -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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
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
|
-
#
|
33
|
-
spec.add_development_dependency '
|
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
|
data/lib/cmds.rb
CHANGED
@@ -1,7 +1,16 @@
|
|
1
|
-
#
|
1
|
+
# Requirements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
# Stdlib
|
5
|
+
# -----------------------------------------------------------------------
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
# Deps
|
9
|
+
# -----------------------------------------------------------------------
|
2
10
|
require 'nrser'
|
3
11
|
|
4
|
-
#
|
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
|
data/lib/cmds/io_handler.rb
CHANGED
@@ -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
|
data/lib/cmds/spawn.rb
CHANGED
@@ -1,26 +1,51 @@
|
|
1
|
-
|
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
|
-
#
|
17
|
+
# Deps
|
18
|
+
# -----------------------------------------------------------------------
|
6
19
|
require 'nrser'
|
7
|
-
require 'nrser/refinements'
|
8
20
|
|
9
|
-
#
|
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
|
-
#
|
43
|
+
# ends up here.
|
19
44
|
#
|
20
|
-
# **
|
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
|
-
|
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
|
-
|
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
|
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:
|
206
|
-
spawn_opts[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
|