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.
@@ -0,0 +1,154 @@
1
+ require 'json'
2
+ require 'pp'
3
+
4
+ require 'nrser'
5
+
6
+ require_relative "defaults"
7
+
8
+
9
+ # Refinements
10
+ # =======================================================================
11
+
12
+ using NRSER
13
+
14
+
15
+ class Cmds
16
+ TOKENIZE_OPT_KEYS = [
17
+ :array_mode,
18
+ :array_join_string,
19
+ :false_mode,
20
+ :flatten_array_values,
21
+ :hash_mode,
22
+ :hash_join_string,
23
+ :long_opt_separator,
24
+ :short_opt_separator,
25
+ ].freeze
26
+
27
+ # turn an option name and value into an array of shell-escaped string
28
+ # token suitable for use in a command.
29
+ #
30
+ # @param [String] name
31
+ # string name (one or more characters).
32
+ #
33
+ # @param [*] value
34
+ # value of the option.
35
+ #
36
+ # @param [Hash] **opts
37
+ # @option [Symbol] :array_mode (:multiple)
38
+ # one of:
39
+ #
40
+ # 1. `:multiple` (default) provide one token for each value.
41
+ #
42
+ # expand_option 'blah', [1, 2, 3]
43
+ # => ['--blah=1', '--blah=2', '--blah=3']
44
+ #
45
+ # 2. `:join` -- join values in one token.
46
+ #
47
+ # expand_option 'blah', [1, 2, 3], array_mode: :join
48
+ # => ['--blah=1,2,3']
49
+ #
50
+ # @option [String] :array_join_string (',')
51
+ # string to join array values with when `:array_mode` is `:join`.
52
+ #
53
+ # @return [Array<String>]
54
+ # List of individual shell token strings.
55
+ #
56
+ # @raise [ArgumentError]
57
+ # If options are set to bad values.
58
+ #
59
+ def self.tokenize_value value, **opts
60
+ opts = defaults opts, TOKENIZE_OPT_KEYS
61
+
62
+ case value
63
+ when nil
64
+ # `nil` values produces no tokens
65
+ []
66
+
67
+ when Array
68
+ # The PITA one...
69
+ #
70
+ # May produce one or multiple tokens.
71
+ #
72
+
73
+ # Flatten the array value if option is set
74
+ value = value.flatten if opts[:flatten_array_values]
75
+
76
+ case opts[:array_mode]
77
+ when :repeat
78
+ # Encode each entry as it's own token
79
+ #
80
+ # [1, 2, 3] => ["1", "2", "3"]
81
+ #
82
+
83
+ # Pass entries back through for individual tokenization and flatten
84
+ # so we are sure to return a single-depth array
85
+ value.map { |entry| tokenize_value entry, **opts }.flatten
86
+
87
+ when :join
88
+ # Encode all entries as one joined string token
89
+ #
90
+ # [1, 2, 3] => ["1,2,3"]
91
+ #
92
+
93
+ [esc( value.join opts[:array_join_string] )]
94
+
95
+ when :json
96
+ # Encode JSON dump as single token, single-quoted
97
+ #
98
+ # [1, 2, 3] => ["'[1,2,3]'"]
99
+
100
+ [single_quote( JSON.dump value )]
101
+
102
+ else
103
+ # SOL
104
+ raise ArgumentError.new binding.erb <<-END
105
+ Bad `:array_mode` option:
106
+
107
+ <%= opts[:array_mode].pretty_inspect %>
108
+
109
+ Should be :join, :repeat or :json
110
+
111
+ END
112
+
113
+ end # case opts[:array_mode]
114
+
115
+ when Hash
116
+ # Much the same as array
117
+ #
118
+ # May produce one or multiple tokens.
119
+ #
120
+
121
+ case opts[:hash_mode]
122
+ when :join
123
+ # Join the key and value using the option and pass the resulting array
124
+ # back through to be handled as configured
125
+ tokenize_value \
126
+ value.map { |k, v| [k, v].join opts[:hash_join_string] },
127
+ **opts
128
+
129
+ when :json
130
+ # Encode JSON dump as single token, single-quoted
131
+ #
132
+ # [1, 2, 3] => [%{'{"a":1,"b":2,"c":3}'}]
133
+
134
+ [single_quote( JSON.dump value )]
135
+
136
+ else
137
+ # SOL
138
+ raise ArgumentError.new binding.erb <<-END
139
+ Bad `:hash_mode` option:
140
+
141
+ <%= opts[:hash_mode].pretty_inspect %>
142
+
143
+ Should be :join, or :json
144
+
145
+ END
146
+ end
147
+
148
+ else
149
+ # We let {Cmds.esc} handle it, and return that as a single token
150
+ [esc(value)]
151
+
152
+ end
153
+ end
154
+ end
@@ -1,3 +1,3 @@
1
1
  class Cmds
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  end
@@ -3,6 +3,10 @@ require 'spec_helper'
3
3
  describe "Cmds ENV vars" do
4
4
  r_echo_cmd = %{ruby -e "puts ENV['BLAH']"}
5
5
 
6
+ def r_echo key, **options
7
+ Cmds.new( %{ruby -e "puts ENV['#{ key }']"}, **options ).chomp!
8
+ end
9
+
6
10
  it "sets basic (path-like) string ENV var" do
7
11
  cmd = Cmds.new r_echo_cmd, env: {BLAH: "x:y:z"}
8
12
  expect(cmd.chomp!).to eq "x:y:z"
@@ -23,4 +27,70 @@ describe "Cmds ENV vars" do
23
27
  }
24
28
  expect(cmd.chomp!).to eq "/usr/local/bin:/usr/bin:/bin"
25
29
  end
26
- end
30
+
31
+ # Want to play around / test out what happens with ENV inheritance...
32
+ describe "ENV inheritance", :env_inheritance do
33
+ # Cmds::Debug.on
34
+
35
+ context "random parent and child value, set in parent ENV" do
36
+ key = 'BLAH'
37
+
38
+ before :each do
39
+ @rand = Random.rand.to_s
40
+ @parent_value = "parent_#{ @rand }"
41
+ @child_value = "child_#{ @rand }"
42
+ ENV[key] = @parent_value
43
+ end
44
+
45
+ after :each do
46
+ ENV.delete key
47
+ end
48
+
49
+ let :cmd_options do
50
+ {}
51
+ end
52
+
53
+ subject do
54
+ r_echo key, cmd_options
55
+ end
56
+
57
+ it "should have ENV[key] set to @parent_value" do
58
+ expect( ENV[key] ).to eq @parent_value
59
+ end
60
+
61
+ describe "no env provided to cmd" do
62
+ it "has ENV[key] set to @parent_value in child" do
63
+ expect( r_echo key ).to eq @parent_value
64
+ end
65
+ end
66
+
67
+ context_where cmd_options: {env: {}} do
68
+ it "should have ENV[key] set to @parent_value in child" do
69
+ is_expected.to eq @parent_value
70
+ end
71
+ end
72
+
73
+ context_where(
74
+ cmd_options: {
75
+ unsetenv_others: true,
76
+ }
77
+ ) do
78
+ it "should have ENV[key] unset in child" do
79
+ is_expected.to eq ''
80
+ end
81
+ end
82
+
83
+ context_where(
84
+ cmd_options: {
85
+ env: { key => wrap( :@child_value ) }
86
+ }
87
+ ) do
88
+ it "should have ENV[key] set to @child_value in child" do
89
+ expect(
90
+ r_echo key, env: {key => @child_value}
91
+ ).to eq @child_value
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -233,22 +233,73 @@ describe "Cmds.prepare" do
233
233
  it "outputs JSON-encoded options" do
234
234
  expect(
235
235
  Cmds.prepare 'blah <%= opts %>',
236
- opts: { list: ['a', 'b', 'see'] } {
237
- { array_mode: :json }
238
- }
236
+ opts: { list: ['a', 'b', 'see'] } {{ array_mode: :json }}
239
237
  ).to eq %{blah --list='["a","b","see"]'}
240
238
  end
241
239
 
242
240
  it "handles single quotes in the string" do
243
241
  expect(
244
242
  Cmds.prepare 'blah <%= opts %>',
245
- opts: { list: ["you're the best"] } {
246
- { array_mode: :json }
247
- }
243
+ opts: { list: ["you're the best"] } {{ array_mode: :json }}
244
+ ).to eq %{blah --list='["you'"'"'re the best"]'}
245
+
246
+
247
+ expect(
248
+ Cmds.new(
249
+ 'blah <%= opts %>',
250
+ kwds: {
251
+ opts: { list: ["you're the best"] }
252
+ },
253
+ array_mode: :json,
254
+ ).prepare
248
255
  ).to eq %{blah --list='["you'"'"'re the best"]'}
249
256
  end
250
257
  end
251
258
 
252
259
  end # "options with list values"
253
260
 
254
- end # ::sub
261
+
262
+ describe %{space-separated "long" opts} do
263
+
264
+ it %{should work when `long_opt_separator: ' '` passed to Cmds.new} do
265
+ expect(
266
+ Cmds.new(
267
+ 'blah <%= opts %>',
268
+ kwds: {
269
+ opts: {
270
+ file: 'some/path.rb'
271
+ }
272
+ },
273
+ long_opt_separator: ' '
274
+ ).prepare
275
+ ).to eq %{blah --file some/path.rb}
276
+ end
277
+
278
+ end # "space-separated long options"
279
+
280
+
281
+ describe "hash opt values" do
282
+
283
+ it do
284
+ expect(
285
+ Cmds.prepare(
286
+ 'docker build <%= opts %>',
287
+ opts: {
288
+ 'build-arg' => {
289
+ 'from_image' => 'blah:0.1.2',
290
+ 'yarn_version' => '1.3.2',
291
+ }
292
+ }
293
+ ) {{
294
+ array_mode: :repeat,
295
+ long_opt_separator: ' ',
296
+ hash_join_string: '=',
297
+ }}
298
+ ).to eq %{docker build --build-arg from_image\\=blah:0.1.2 --build-arg yarn_version\\=1.3.2}
299
+ end
300
+
301
+ end # "hash opt values"
302
+
303
+
304
+
305
+ end # ::prepare
@@ -0,0 +1,69 @@
1
+ describe 'Cmds#stream' do
2
+ describe "streaming to an output file" do
3
+ describe "given a path that does not exist" do
4
+ let( :path ){ Cmds::ROOT / 'tmp' / 'stream_out.txt' }
5
+
6
+ before :each do
7
+ FileUtils.rm( path ) if path.exist?
8
+ end
9
+
10
+ context "using &io_block" do
11
+ context "and passing it the path opened for write" do
12
+ it "should write to the file" do
13
+ Cmds.stream "echo here" do |io|
14
+ io.out = path.open( 'w' )
15
+ end
16
+
17
+ expect( path.read ).to eq "here\n"
18
+ end
19
+ end
20
+
21
+ context "and passing it the path string" do
22
+ it "should create the file and write to it" do
23
+ Cmds.stream "echo there" do |io|
24
+ io.out = path.to_s
25
+ end
26
+
27
+ expect( path.read ).to eq "there\n"
28
+ end
29
+ end
30
+
31
+ context "and passing it the pathname" do
32
+ it "should create the file and write to it" do
33
+ Cmds.stream "echo everywhere" do |io|
34
+ io.out = path
35
+ end
36
+
37
+ expect( path.read ).to eq "everywhere\n"
38
+ end
39
+ end
40
+ end # using &io_block
41
+
42
+
43
+ # Not sure how to deal with this re capture, so leave it out for now
44
+ # context "using constructor keyword" do
45
+ # context "and passing it the path opened for write" do
46
+ # it "should write to the file" do
47
+ # Cmds.new( "echo here", out: path.open( 'w' ) ).stream
48
+ # expect( path.read ).to eq "here\n"
49
+ # end
50
+ # end
51
+ #
52
+ # context "and passing it the path string" do
53
+ # it "should create the file and write to it" do
54
+ # Cmds.new( "echo there", out: path.to_s ).stream
55
+ # expect( path.read ).to eq "there\n"
56
+ # end
57
+ # end
58
+ #
59
+ # context "and passing it the pathname" do
60
+ # it "should create the file and write to it" do
61
+ # Cmds.new( "echo everywhere", out: path ).stream
62
+ # expect( path.read ).to eq "everywhere\n"
63
+ # end
64
+ # end
65
+ # end # using constructor keyword
66
+ end # "given a path that does not exist"
67
+ end # streaming to an output file
68
+
69
+ end # Cmds#stream
@@ -0,0 +1,18 @@
1
+ describe_spec_file(
2
+ spec_path: __FILE__,
3
+ module: Cmds,
4
+ method: :quote_dance,
5
+ ) do
6
+
7
+ it_behaves_like "function",
8
+ mapping: {
9
+ ["you're", :single] => %{'you'"'"'re'},
10
+ ['such a "goober" dude', :double] => %{"such a "'"'"goober"'"'" dude"},
11
+ [%{hey "ho" let's go}, :double] => %{"hey "'"'"ho"'"'" let's go"}
12
+ },
13
+
14
+ raising: {
15
+ ["blah", :not_there] => KeyError,
16
+ }
17
+
18
+ end # spec file
@@ -5,6 +5,8 @@ require 'tempfile'
5
5
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
6
6
  require 'cmds'
7
7
 
8
+ require 'nrser/rspex'
9
+
8
10
  ECHO_CMD = "./test/echo_cmd.rb"
9
11
 
10
12
  def echo_cmd_data result
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmds
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - nrser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-21 00:00:00.000000000 Z
11
+ date: 2018-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nrser
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
17
20
  - - ">="
18
21
  - !ruby/object:Gem::Version
19
- version: 0.0.17
22
+ version: 0.1.1
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.1'
24
30
  - - ">="
25
31
  - !ruby/object:Gem::Version
26
- version: 0.0.17
32
+ version: 0.1.1
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: erubis
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -67,7 +73,7 @@ dependencies:
67
73
  - !ruby/object:Gem::Version
68
74
  version: '0'
69
75
  - !ruby/object:Gem::Dependency
70
- name: rspec
76
+ name: pastel
71
77
  requirement: !ruby/object:Gem::Requirement
72
78
  requirements:
73
79
  - - ">="
@@ -81,75 +87,75 @@ dependencies:
81
87
  - !ruby/object:Gem::Version
82
88
  version: '0'
83
89
  - !ruby/object:Gem::Dependency
84
- name: pastel
90
+ name: rspec
85
91
  requirement: !ruby/object:Gem::Requirement
86
92
  requirements:
87
- - - ">="
93
+ - - "~>"
88
94
  - !ruby/object:Gem::Version
89
- version: '0'
95
+ version: '3.7'
90
96
  type: :development
91
97
  prerelease: false
92
98
  version_requirements: !ruby/object:Gem::Requirement
93
99
  requirements:
94
- - - ">="
100
+ - - "~>"
95
101
  - !ruby/object:Gem::Version
96
- version: '0'
102
+ version: '3.7'
97
103
  - !ruby/object:Gem::Dependency
98
104
  name: yard
99
105
  requirement: !ruby/object:Gem::Requirement
100
106
  requirements:
101
107
  - - "~>"
102
108
  - !ruby/object:Gem::Version
103
- version: 0.9.9
109
+ version: 0.9.12
104
110
  type: :development
105
111
  prerelease: false
106
112
  version_requirements: !ruby/object:Gem::Requirement
107
113
  requirements:
108
114
  - - "~>"
109
115
  - !ruby/object:Gem::Version
110
- version: 0.9.9
116
+ version: 0.9.12
111
117
  - !ruby/object:Gem::Dependency
112
118
  name: redcarpet
113
119
  requirement: !ruby/object:Gem::Requirement
114
120
  requirements:
115
- - - ">="
121
+ - - "~>"
116
122
  - !ruby/object:Gem::Version
117
- version: '0'
123
+ version: '3.4'
118
124
  type: :development
119
125
  prerelease: false
120
126
  version_requirements: !ruby/object:Gem::Requirement
121
127
  requirements:
122
- - - ">="
128
+ - - "~>"
123
129
  - !ruby/object:Gem::Version
124
- version: '0'
130
+ version: '3.4'
125
131
  - !ruby/object:Gem::Dependency
126
132
  name: github-markup
127
133
  requirement: !ruby/object:Gem::Requirement
128
134
  requirements:
129
- - - ">="
135
+ - - "~>"
130
136
  - !ruby/object:Gem::Version
131
- version: '0'
137
+ version: '1.6'
132
138
  type: :development
133
139
  prerelease: false
134
140
  version_requirements: !ruby/object:Gem::Requirement
135
141
  requirements:
136
- - - ">="
142
+ - - "~>"
137
143
  - !ruby/object:Gem::Version
138
- version: '0'
144
+ version: '1.6'
139
145
  - !ruby/object:Gem::Dependency
140
146
  name: pry
141
147
  requirement: !ruby/object:Gem::Requirement
142
148
  requirements:
143
- - - ">="
149
+ - - "~>"
144
150
  - !ruby/object:Gem::Version
145
- version: '0'
151
+ version: 0.10.4
146
152
  type: :development
147
153
  prerelease: false
148
154
  version_requirements: !ruby/object:Gem::Requirement
149
155
  requirements:
150
- - - ">="
156
+ - - "~>"
151
157
  - !ruby/object:Gem::Version
152
- version: '0'
158
+ version: 0.10.4
153
159
  description:
154
160
  email:
155
161
  - neil@ztkae.com
@@ -184,8 +190,10 @@ files:
184
190
  - lib/cmds/util.rb
185
191
  - lib/cmds/util/defaults.rb
186
192
  - lib/cmds/util/params.rb
193
+ - lib/cmds/util/shell_escape.rb
187
194
  - lib/cmds/util/tokenize_option.rb
188
195
  - lib/cmds/util/tokenize_options.rb
196
+ - lib/cmds/util/tokenize_value.rb
189
197
  - lib/cmds/version.rb
190
198
  - scratch/erb.rb
191
199
  - scratch/popen3.rb
@@ -202,7 +210,9 @@ files:
202
210
  - spec/cmds/out_spec.rb
203
211
  - spec/cmds/prepare_spec.rb
204
212
  - spec/cmds/replace_shortcuts_spec.rb
213
+ - spec/cmds/stream/output_spec.rb
205
214
  - spec/cmds/stream_spec.rb
215
+ - spec/cmds/util/shell_escape/quote_dance_spec.rb
206
216
  - spec/cmds/util/tokenize_option_spec.rb
207
217
  - spec/cmds_spec.rb
208
218
  - spec/debug_helper.rb
@@ -249,7 +259,9 @@ test_files:
249
259
  - spec/cmds/out_spec.rb
250
260
  - spec/cmds/prepare_spec.rb
251
261
  - spec/cmds/replace_shortcuts_spec.rb
262
+ - spec/cmds/stream/output_spec.rb
252
263
  - spec/cmds/stream_spec.rb
264
+ - spec/cmds/util/shell_escape/quote_dance_spec.rb
253
265
  - spec/cmds/util/tokenize_option_spec.rb
254
266
  - spec/cmds_spec.rb
255
267
  - spec/debug_helper.rb