cmds 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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