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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c17b6e518aac921af67affc110d82d610170cb8
|
4
|
+
data.tar.gz: f473be8e43fd43680036291c809b8ebbf5f2a309
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99b6b930f1fc5a51576d139d2becaa7ca47a1938fadf301d9d555393d70cdf9a77ba8b914ffc7b034ed158897517d00ff1c3a42e2b401ae82893bdf37792d0b7
|
7
|
+
data.tar.gz: 28cdcc19b8e14f7a7201466df932af03042c651f87bc7c17d0a728c993e2ce84edf750b4f828ffe7404f6f1ccca4ca2531f9e3a49b0a08f78d9addf853e3073b
|
data/.rspec
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -5,9 +5,11 @@ Cmds
|
|
5
5
|
|
6
6
|
It treats generating shell the in a similar fashion to generating SQL or HTML.
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
Best read at
|
9
|
+
|
10
|
+
<http://www.rubydoc.info/gems/cmds/>
|
11
|
+
|
12
|
+
where the API doc links should work and you got a table and contents.
|
11
13
|
|
12
14
|
|
13
15
|
-----------------------------------------------------------------------------
|
@@ -19,14 +21,26 @@ Ya know, before you get too excited...
|
|
19
21
|
It's kinda starting to work. I'll be using it for stuff and seeing how it goes, but no promises until `1.0` of course.
|
20
22
|
|
21
23
|
|
24
|
+
-----------------------------------------------------------------------------
|
25
|
+
License
|
26
|
+
-----------------------------------------------------------------------------
|
27
|
+
|
28
|
+
MIT
|
29
|
+
|
30
|
+
|
22
31
|
-----------------------------------------------------------------------------
|
23
32
|
Real-World Examples
|
24
33
|
-----------------------------------------------------------------------------
|
25
34
|
|
35
|
+
Or, "what's it look like?"...
|
36
|
+
|
26
37
|
- Instead of
|
27
38
|
|
28
39
|
```Ruby
|
29
|
-
`psql
|
40
|
+
`psql \
|
41
|
+
--username=#{ (db_config['username'] || ENV['USER']).shellescape } \
|
42
|
+
#{ db_config['database'].shellescape } \
|
43
|
+
< #{ filepath.shellescape }`
|
30
44
|
```
|
31
45
|
|
32
46
|
write
|
@@ -46,333 +60,450 @@ Real-World Examples
|
|
46
60
|
psql --username=nrser that_db < ./some/file/path
|
47
61
|
```
|
48
62
|
|
63
|
+
Cmds takes care of shell escaping for you.
|
64
|
+
|
49
65
|
|
50
66
|
- Instead of
|
51
67
|
|
52
68
|
```Ruby
|
53
|
-
`
|
69
|
+
`PGPASSWORD=#{ config[:password].shellescape } \
|
70
|
+
pg_dump \
|
71
|
+
--username=#{ config[:username].shellescape } \
|
72
|
+
--host=#{ config[:host].shellescape } \
|
73
|
+
--port=#{ config[:port].shellescape } \
|
74
|
+
#{ config[:database].shellescape } \
|
75
|
+
> #{ filepath.shellescape }`
|
54
76
|
```
|
55
77
|
|
56
|
-
write
|
78
|
+
which can be really hard to pick out what's going on from a quick glance, write
|
57
79
|
|
58
80
|
```Ruby
|
59
|
-
Cmds
|
60
|
-
|
61
|
-
|
81
|
+
Cmds.new(
|
82
|
+
'pg_dump %{opts} %{database}',
|
83
|
+
kwds: {
|
84
|
+
opts: {
|
85
|
+
username: config[:username],
|
86
|
+
host: config[:host],
|
87
|
+
port: config[:port],
|
88
|
+
},
|
89
|
+
database: config[:database],
|
90
|
+
},
|
91
|
+
env: {
|
92
|
+
PGPASSWORD: config[:password],
|
93
|
+
},
|
94
|
+
).stream! { |io| io.out = filename }
|
62
95
|
```
|
63
|
-
|
64
|
-
|
65
|
-
- Instead of
|
66
96
|
|
67
|
-
|
68
|
-
`PGPASSWORD=#{ config[:password].shellescape } pg_dump -U #{ config[:username].shellescape } -h #{ config[:host].shellescape } -p #{ config[:port] } #{ config[:database].shellescape } > #{ filepath.shellescape }`
|
69
|
-
```
|
97
|
+
I find it much easier to see what's going on their quickly.
|
70
98
|
|
71
|
-
|
99
|
+
Again, with some additional comments and examples:
|
72
100
|
|
73
101
|
```Ruby
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
102
|
+
# We're going to instantiate a new {Cmds} object this time, because we're
|
103
|
+
# not just providing values for the string template, we're specifying an
|
104
|
+
# environment variable for the child process too.
|
105
|
+
#
|
106
|
+
cmd = Cmds.new(
|
107
|
+
# The string template to use.
|
108
|
+
'pg_dump %{opts} %{database}',
|
109
|
+
kwds: {
|
110
|
+
# Hashes will automatically be expanded to CLI options. By default,
|
111
|
+
# we use `--name=VALUE` format for long ones and `-n VALUE` for short,
|
112
|
+
# but it's configurable.
|
113
|
+
opts: {
|
114
|
+
username: config[:username],
|
115
|
+
host: config[:host],
|
116
|
+
port: config[:port],
|
117
|
+
},
|
118
|
+
# As mentioned above, everything is shell escaped automatically
|
119
|
+
database: config[:database],
|
120
|
+
},
|
121
|
+
# Pass environment as it's own Hash. There are options for how it is
|
122
|
+
# provided to the child process as well.
|
123
|
+
env: {
|
124
|
+
# Symbol keys are fine, we'll take care of that for you
|
125
|
+
PGPASSWORD: config[:password],
|
126
|
+
},
|
127
|
+
)
|
128
|
+
|
129
|
+
# Take a look!
|
130
|
+
cmd.prepare
|
131
|
+
# => "PGPASSWORD=shhh\\! pg_dump --host=localhost --port=5432 --username=nrser blah"
|
132
|
+
|
133
|
+
# Now stream it. the `!` means raise if the exit code is not 0
|
134
|
+
exit_code = cmd.stream! { |io|
|
135
|
+
# We use the block to configure I/O. Here we send the standard output to
|
136
|
+
# a file, which can be a String, Pathname or IO object
|
137
|
+
io.out = filename
|
138
|
+
}
|
83
139
|
```
|
84
140
|
|
141
|
+
|
85
142
|
-----------------------------------------------------------------------------
|
86
143
|
Installation
|
87
144
|
-----------------------------------------------------------------------------
|
88
145
|
|
89
146
|
Add this line to your application's `Gemfile`:
|
90
147
|
|
91
|
-
|
92
|
-
gem 'cmds'
|
93
|
-
```
|
148
|
+
gem 'cmds'
|
94
149
|
|
95
150
|
|
96
151
|
And then execute:
|
97
152
|
|
98
|
-
|
99
|
-
$ bundle
|
100
|
-
```
|
153
|
+
bundle install
|
101
154
|
|
102
155
|
|
103
|
-
Or install it
|
156
|
+
Or install it globally with:
|
104
157
|
|
105
|
-
|
106
|
-
$ gem install cmds
|
107
|
-
```
|
158
|
+
gem install cmds
|
108
159
|
|
109
160
|
|
110
|
-
## architecture
|
111
161
|
|
112
|
-
|
162
|
+
-----------------------------------------------------------------------------
|
163
|
+
Overview
|
164
|
+
-----------------------------------------------------------------------------
|
113
165
|
|
114
|
-
|
166
|
+
Cmds is based around a central {Cmds} class that takes a template for the command and a few options and operates by either wrapping the results in a {Cmds::Result} instance or streaming the results to `IO` objects or handler blocks.
|
115
167
|
|
116
|
-
the `Cmds` constructor looks like
|
117
168
|
|
118
|
-
|
119
|
-
|
120
|
-
|
169
|
+
-----------------------------------------------------------------------------
|
170
|
+
Features
|
171
|
+
-----------------------------------------------------------------------------
|
121
172
|
|
122
|
-
|
123
|
-
|
124
|
-
* `template`
|
125
|
-
* a `String` template processed with ERB against positional and keyword arguments.
|
126
|
-
* `opts`
|
127
|
-
* `:args`
|
128
|
-
* an `Array` of positional substitutions for the template.
|
129
|
-
* assigned to `@args`.
|
130
|
-
* defaults to an empty `Array`.
|
131
|
-
* `:kwds`
|
132
|
-
* a `Hash` of keyword substitutions for the template.
|
133
|
-
* assigned to `@kwds`.
|
134
|
-
* defaults to an empty `Hash`.
|
135
|
-
* `:input`
|
136
|
-
* a `String` to provide as standard input.
|
137
|
-
* assigned to `@input`.
|
138
|
-
* defaults to `nil`.
|
139
|
-
* `:assert`
|
140
|
-
* if this tests true, the execution of the command will raise an error on a nonzero exit status.
|
141
|
-
* assigned to `@assert`.
|
142
|
-
* defaults to `False`.
|
173
|
+
### Templates ###
|
143
174
|
|
144
|
-
|
175
|
+
#### ERB ####
|
145
176
|
|
146
|
-
|
177
|
+
Templates are processed with "[Embedded Ruby][]" (eRuby/ERB) using the [Erubis][] gem.
|
147
178
|
|
148
|
-
|
149
|
-
|
150
|
-
3. input to stdin
|
179
|
+
[Embedded Ruby]: https://en.wikipedia.org/wiki/ERuby
|
180
|
+
[Erubis]: http://www.kuwata-lab.com/erubis/
|
151
181
|
|
152
|
-
|
182
|
+
For how it works check out
|
153
183
|
|
154
|
-
1.
|
155
|
-
|
156
|
-
|
184
|
+
1. {Cmds::ERBContext}
|
185
|
+
2. {Cmds::ShellERuby}
|
186
|
+
3. {Cmds#render}
|
187
|
+
|
188
|
+
******************************************************************************
|
189
|
+
|
190
|
+
|
191
|
+
##### Positional Values from `args` #####
|
192
|
+
|
193
|
+
1. Use the `args` array made available in the templates with entry indexes.
|
157
194
|
|
158
|
-
|
195
|
+
Example when constructing:
|
159
196
|
|
160
|
-
|
197
|
+
```ruby
|
198
|
+
Cmds.new(
|
199
|
+
'cp <%= args[0] %> <%= args[1] %>',
|
200
|
+
args: [
|
201
|
+
'source.txt',
|
202
|
+
'dest.txt',
|
203
|
+
],
|
204
|
+
).prepare
|
205
|
+
# => "cp source.txt dest.txt"
|
206
|
+
```
|
161
207
|
|
162
|
-
|
208
|
+
This will raise an error if it's called after using the last positional argument, but will not complain if all positional arguments are not used.
|
163
209
|
|
164
|
-
|
165
|
-
|
166
|
-
3. input and output is handled with blocks:
|
210
|
+
2. Use the `arg` method made available in the templates to get the next positional arg.
|
167
211
|
|
168
|
-
`
|
212
|
+
Example when using "sugar" methods that take `args` as the single-splat (`*args`):
|
169
213
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
end`
|
214
|
+
```ruby
|
215
|
+
Cmds.prepare 'cp <%= arg %> <%= arg %>',
|
216
|
+
'source.txt',
|
217
|
+
'dest.txt'
|
218
|
+
# => "cp source.txt dest.txt"
|
219
|
+
```
|
220
|
+
|
221
|
+
******************************************************************************
|
179
222
|
|
180
223
|
|
224
|
+
##### Keyword Values from `kwds` #####
|
181
225
|
|
182
|
-
|
226
|
+
Just use the key as the method name.
|
183
227
|
|
184
|
-
|
228
|
+
When constructing:
|
185
229
|
|
186
|
-
|
230
|
+
```ruby
|
231
|
+
Cmds.new(
|
232
|
+
'cp <%= src %> <%= dest %>',
|
233
|
+
kwds: {
|
234
|
+
src: 'source.txt',
|
235
|
+
dest: 'dest.txt',
|
236
|
+
},
|
237
|
+
).prepare
|
238
|
+
# => "cp source.txt dest.txt"
|
239
|
+
```
|
187
240
|
|
188
|
-
|
241
|
+
When using "sugar" methods that take `kwds` as the double-splat (`**kwds`):
|
189
242
|
|
190
|
-
|
191
|
-
|
243
|
+
```ruby
|
244
|
+
Cmds.prepare 'cp <%= src %> <%= dest %>',
|
245
|
+
src: 'source.txt',
|
246
|
+
dest: 'dest.txt'
|
247
|
+
# => "cp source.txt dest.txt"
|
248
|
+
```
|
192
249
|
|
193
|
-
## substitutions
|
194
250
|
|
195
|
-
|
251
|
+
###### Key Names to Avoid ######
|
196
252
|
|
197
|
-
|
253
|
+
If possible, avoid naming your keys:
|
198
254
|
|
199
|
-
|
255
|
+
- `arg`
|
256
|
+
- `args`
|
257
|
+
- `initialize`
|
258
|
+
- `get_binding`
|
259
|
+
- `method_missing`
|
200
260
|
|
201
|
-
|
202
|
-
Cmds.sub "psql <%= arg %> <%= arg %> < <%= arg %>", [
|
203
|
-
{
|
204
|
-
username: "bingo bob",
|
205
|
-
host: "localhost",
|
206
|
-
port: 12345,
|
207
|
-
},
|
208
|
-
"blah",
|
209
|
-
"/where ever/it/is.psql",
|
210
|
-
]
|
211
|
-
# => 'psql --host=localhost --port=12345 --username=bingo\ bob blah < /where\ ever/it/is.psql'
|
212
|
-
```
|
261
|
+
If you must name them those things, don't expect to be able to access them as shown above; use `<%= @kwds[key] %>`.
|
213
262
|
|
214
|
-
internally this translates to calling `@args.fetch(@arg_index)` and increments `@arg_index` by 1.
|
215
263
|
|
216
|
-
|
264
|
+
###### Keys That Might Not Be There ######
|
217
265
|
|
218
|
-
|
266
|
+
Normally, if you try to interpolate a key that doesn't exist you will get a `KeyError`:
|
219
267
|
|
268
|
+
```ruby
|
269
|
+
Cmds.prepare "blah <%= maybe %> <%= arg %>", "value"
|
270
|
+
# KeyError: couldn't find keys :maybe or "maybe" in keywords {}
|
220
271
|
```
|
221
|
-
Cmds.sub "psql <%= @args[2] %> <%= @args[0] %> < <%= @args[1] %>", [
|
222
|
-
"blah",
|
223
|
-
"/where ever/it/is.psql",
|
224
|
-
{
|
225
|
-
username: "bingo bob",
|
226
|
-
host: "localhost",
|
227
|
-
port: 12345,
|
228
|
-
},
|
229
|
-
]
|
230
|
-
# => 'psql --host=localhost --port=12345 --username=bingo\ bob blah < /where\ ever/it/is.psql'
|
231
|
-
```
|
232
|
-
|
233
|
-
note that `@args` is a standard Ruby array and will simply return `nil` if there is no value at that index (though you can use `args.fetch(i)` to get the same behavior as the `arg` method with a specific index `i`).
|
234
272
|
|
235
|
-
|
273
|
+
I like a lot this better than just silently omitting the value, but sometimes you know that they key might not be set and want to receive `nil` if it's not.
|
236
274
|
|
237
|
-
|
275
|
+
In this case, append `?` to the key name (which is a method call in this case) and you will get `nil` if it's not set:
|
238
276
|
|
277
|
+
```ruby
|
278
|
+
Cmds.prepare "blah <%= maybe? %> <%= arg %>", "value"
|
279
|
+
# => "blah value"
|
239
280
|
```
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
opts: {
|
245
|
-
username: "bingo bob",
|
246
|
-
host: "localhost",
|
247
|
-
port: 12345,
|
248
|
-
}
|
249
|
-
# => 'psql --host=localhost --port=12345 --username=bingo\ bob blah < /where\ ever/it/is.psql'
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
Cmds.prepare "blah <%= maybe? %> <%= arg %>", "value", maybe: "yes"
|
284
|
+
# => "blah yes value"
|
250
285
|
```
|
251
286
|
|
252
|
-
|
287
|
+
******************************************************************************
|
253
288
|
|
254
|
-
there are four key names that may not be accessed this way due to method definition on the context object:
|
255
289
|
|
256
|
-
|
257
|
-
* `initialize`
|
258
|
-
* `get_binding`
|
259
|
-
* `method_missing`
|
290
|
+
##### Shell Escaping #####
|
260
291
|
|
261
|
-
|
292
|
+
Cmds automatically shell-escapes values it interpolates into templates by passing them through the Ruby standard libray's [Shellwords.escape][].
|
262
293
|
|
263
|
-
|
294
|
+
[Shellwords.escape]: http://ruby-doc.org/stdlib/libdoc/shellwords/rdoc/Shellwords.html#method-c-escape
|
264
295
|
|
296
|
+
```ruby
|
297
|
+
Cmds.prepare "cp <%= src %> <%= dest %>",
|
298
|
+
src: "source.txt",
|
299
|
+
dest: "path with spaces.txt"
|
300
|
+
=> "cp source.txt path\\ with\\ spaces.txt"
|
265
301
|
```
|
266
|
-
c = Cmds.new <<-BLOCK
|
267
|
-
defaults
|
268
|
-
<% if current_host? %>
|
269
|
-
-currentHost <%= current_host %>
|
270
|
-
<% end %>
|
271
|
-
export <%= domain %> <%= filepath %>
|
272
|
-
BLOCK
|
273
302
|
|
274
|
-
|
275
|
-
# defaults export com.nrser.blah /tmp/export.plist
|
303
|
+
It doesn't always do the prettiest job, but it's part of the standard library and seems to work pretty well... shell escaping is a messy and complicated topic (escaping for *which* shell?!), so going with the built-in solution seems reasonable for the moment, though I do hate all those backslashes... they're a pain to read.
|
276
304
|
|
277
|
-
c.call current_host: 'xyz', domain: 'com.nrser.blah', filepath: '/tmp/export.plist'
|
278
|
-
# defaults -currentHost xyz export com.nrser.blah /tmp/export.plist
|
279
|
-
```
|
280
305
|
|
281
|
-
|
306
|
+
###### Raw Interpolation ######
|
282
307
|
|
283
|
-
|
308
|
+
You can render a raw string with `<%== %>`.
|
284
309
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
port: 12345,
|
293
|
-
}
|
294
|
-
# => 'psql --host=localhost --port=12345 --username=bingo\ bob blah < /where\ ever/it/is.psql'
|
310
|
+
To see the difference with regard to the previous example (which would break the `cp` command in question):
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
Cmds.prepare "cp <%= src %> <%== dest %>",
|
314
|
+
src: "source.txt",
|
315
|
+
dest: "path with spaces.txt"
|
316
|
+
=> "cp source.txt path with spaces.txt"
|
295
317
|
```
|
296
318
|
|
297
|
-
|
319
|
+
And a way it make a little more sense:
|
298
320
|
|
299
|
-
```
|
300
|
-
Cmds "
|
321
|
+
```ruby
|
322
|
+
Cmds.prepare "<%== bin %> <%= *args %>",
|
323
|
+
'blah',
|
324
|
+
'boo!',
|
325
|
+
bin: '/usr/bin/env echo'
|
326
|
+
=> "/usr/bin/env echo blah boo\\!"
|
301
327
|
```
|
302
328
|
|
303
|
-
|
329
|
+
******************************************************************************
|
304
330
|
|
305
|
-
```
|
306
|
-
Cmds "blah <%= maybe? %> <%= arg %>", ["value"]
|
307
|
-
Cmds "blah <%= maybe? %> <%= arg %>", ["value"], maybe: "yes!"
|
308
|
-
```
|
309
331
|
|
310
|
-
|
332
|
+
##### Splatting (`*`) To Render Multiple Shell Tokens #####
|
311
333
|
|
334
|
+
Render multiple shell tokens (individual strings the shell picks up - basically, each one is an entry in `ARGV` for the child process) in one expression tag by prefixing the value with `*`:
|
312
335
|
|
313
|
-
|
336
|
+
```ruby
|
337
|
+
Cmds.prepare '<%= *exe %> <%= cmd %> <%= opts %> <%= *args %>',
|
338
|
+
'x', 'y', # <= these are the `args`
|
339
|
+
exe: ['/usr/bin/env', 'blah'],
|
340
|
+
cmd: 'do-stuff',
|
341
|
+
opts: {
|
342
|
+
really: true,
|
343
|
+
'some-setting': 'dat-value',
|
344
|
+
}
|
345
|
+
# => "/usr/bin/env blah do-stuff --really --some-setting=dat-value x y"
|
346
|
+
```
|
314
347
|
|
315
|
-
`
|
348
|
+
`ARGV` tokenization by the shell would look like:
|
316
349
|
|
317
|
-
|
350
|
+
```ruby
|
351
|
+
[
|
352
|
+
'/usr/bin/env',
|
353
|
+
'blah',
|
354
|
+
'do-stuff',
|
355
|
+
'--really',
|
356
|
+
'--some-setting=dat-value',
|
357
|
+
'x',
|
358
|
+
'y',
|
359
|
+
]
|
360
|
+
```
|
318
361
|
|
362
|
+
- Compare to *without* splats:
|
363
|
+
|
364
|
+
```ruby
|
365
|
+
Cmds.prepare '<%= exe %> <%= cmd %> <%= opts %> <%= args %>',
|
366
|
+
'x', 'y', # <= these are the `args`
|
367
|
+
exe: ['/usr/bin/env', 'blah'],
|
368
|
+
cmd: 'do-stuff',
|
369
|
+
opts: {
|
370
|
+
really: true,
|
371
|
+
'some-setting': 'dat-value',
|
372
|
+
}
|
373
|
+
# => "/usr/bin/env,blah do-stuff --really --some-setting=dat-value x,y"
|
374
|
+
```
|
375
|
+
|
376
|
+
Which is probably *not* what you were going for... it would produce an `ARGV` something like:
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
[
|
380
|
+
'/usr/bin/env,blah',
|
381
|
+
'do-stuff',
|
382
|
+
'--really',
|
383
|
+
'--some-setting=dat-value',
|
384
|
+
'x,y',
|
385
|
+
]
|
386
|
+
```
|
319
387
|
|
320
|
-
|
388
|
+
You can of course use "splatting" together with slicing or mapping or whatever.
|
321
389
|
|
322
|
-
|
390
|
+
******************************************************************************
|
323
391
|
|
324
|
-
so
|
325
392
|
|
326
|
-
|
327
|
-
Cmds.sub "./test/echo_cmd.rb %s", ["hello world!"]
|
328
|
-
```
|
393
|
+
##### Logic #####
|
329
394
|
|
330
|
-
is the
|
395
|
+
All of ERB is available to you. I've tried to put in features and options that make it largely unnecessary, but if you've got a weird or complicated case, or if you just like the HTML/Rails-esque templating style, it's there for you:
|
331
396
|
|
332
|
-
```
|
333
|
-
Cmds
|
397
|
+
```ruby
|
398
|
+
cmd = Cmds.new <<-END
|
399
|
+
<% if use_bin_env %>
|
400
|
+
/usr/bin/env
|
401
|
+
<% end %>
|
402
|
+
|
403
|
+
docker build .
|
404
|
+
-t <%= tag %>
|
405
|
+
|
406
|
+
<% if file %>
|
407
|
+
--file <%= file %>
|
408
|
+
<% end %>
|
409
|
+
|
410
|
+
<% build_args.each do |key, value| %>
|
411
|
+
--build-arg <%= key %>=<%= value %>
|
412
|
+
<% end %>
|
413
|
+
|
414
|
+
<% if yarn_cache %>
|
415
|
+
--build-arg yarn_cache_file=<%= yarn_cache_file %>
|
416
|
+
<% end %>
|
417
|
+
END
|
418
|
+
|
419
|
+
cmd.prepare(
|
420
|
+
use_bin_env: true,
|
421
|
+
tag: 'nrser/blah:latest',
|
422
|
+
file: './prod.Dockerfile',
|
423
|
+
build_args: {
|
424
|
+
yarn_version: '1.3.2',
|
425
|
+
},
|
426
|
+
yarn_cache: true,
|
427
|
+
yarn_cache_file: './yarn-cache.tgz',
|
428
|
+
)
|
429
|
+
# => "/usr/bin/env docker build . -t nrser/blah:latest
|
430
|
+
# --file ./prod.Dockerfile --build-arg yarn_version=1.3.2
|
431
|
+
# --build-arg yarn_cache_file=./yarn-cache.tgz"
|
432
|
+
# (Line-breaks added for readability; output is one line)
|
334
433
|
```
|
335
434
|
|
336
|
-
|
435
|
+
******************************************************************************
|
337
436
|
|
338
|
-
`%{key}` and `%<key>s` are replaced with `<%= key %>`, and `%{key?}` and `%<key?>s` are replaced with `<%= key? %>` for optional keywords.
|
339
437
|
|
340
|
-
|
438
|
+
#### `printf`-Style Short-Hand (`%s`, `%{key}`, `%<key>s`)
|
341
439
|
|
342
|
-
|
343
|
-
Cmds "./test/echo_cmd.rb %{key}", key: "hello world!"
|
344
|
-
```
|
440
|
+
Cmds also supports a [printf][]-style short-hand. Sort-of.
|
345
441
|
|
346
|
-
|
442
|
+
[printf]: https://en.wikipedia.org/wiki/Printf_format_string
|
347
443
|
|
348
|
-
|
349
|
-
Cmds "./test/echo_cmd.rb %<key>s", key: "hello world!"
|
350
|
-
```
|
444
|
+
It's a clumsy hack from when I was first writing this library, and I've pretty moved to using the ERB-style, but there are still some examples that use it, and I guess it still works (to whatever extent it ever really did), so it's probably good to mention it.
|
351
445
|
|
352
|
-
|
446
|
+
It pretty much just replaces some special patterns with their ERB-equivalent via the {Cmds.replace_shortcuts} method before moving on to ERB processing:
|
353
447
|
|
354
|
-
|
355
|
-
|
356
|
-
|
448
|
+
1. `%s` => `<%= arg %>`
|
449
|
+
2. `%{key}` => `<%= key %>`
|
450
|
+
3. `%{key?}` => `<%= key? %>`
|
451
|
+
4. `%<key>s` => `<%= key %>`
|
452
|
+
5. `%<key?>s` => `<%= key? %>`
|
357
453
|
|
358
|
-
|
454
|
+
And the escaping versions, where you can put anothe `%` in front to get the literal intead of the subsitution:
|
359
455
|
|
360
|
-
|
456
|
+
1. `%%s` => `%s`
|
457
|
+
2. `%%{key}` => `%{key}`
|
458
|
+
3. `%%{key?}` => `%{key?}`
|
459
|
+
4. `%%<key>s` => `%<key>s`
|
460
|
+
5. `%%<key?>s` => `%<key?>s`
|
361
461
|
|
362
|
-
|
363
|
-
Cmds.sub "%%s" # => "%s"
|
364
|
-
Cmds.sub "%%%<key>s" # => "%%<key>s"
|
365
|
-
```
|
462
|
+
That's it. No `printf` formatting beyond besides `s` (string).
|
366
463
|
|
367
|
-
note that unlike `sprintf`, which has a much more general syntax, this is only necessary for patterns that exactly match a shortcut, not `%` in general:
|
368
464
|
|
369
|
-
|
370
|
-
|
371
|
-
|
465
|
+
-----------------------------------------------------------------------------
|
466
|
+
Old docs I haven't cleaned up yet...
|
467
|
+
-----------------------------------------------------------------------------
|
372
468
|
|
469
|
+
### execution
|
470
|
+
|
471
|
+
you can provide three types of arguments when executing a command:
|
373
472
|
|
473
|
+
1. positional arguments for substitution
|
474
|
+
2. keyword arguments for substitution
|
475
|
+
3. input to stdin
|
374
476
|
|
375
|
-
|
477
|
+
all `Cmds` instance execution methods have the same form for accepting these:
|
478
|
+
|
479
|
+
1. positional arguments are provided in an optional array that must be the first argument:
|
480
|
+
|
481
|
+
`Cmds "cp <%= arg %> <%= arg %>", [src_path, dest_path]`
|
482
|
+
|
483
|
+
note that the arguments need to be enclosed in square braces. Cmds does **NOT** use \*splat for positional arguments because it would make a `Hash` final parameter ambiguous.
|
484
|
+
|
485
|
+
2. keyword arguments are provided as optional hash that must be the last argument:
|
486
|
+
|
487
|
+
`Cmds "cp <%= src %> <%= dest %>", src: src_path, dest: dest_path`
|
488
|
+
|
489
|
+
in this case, curly braces are not required since Ruby turns the trailing keywords into a `Hash` provided as the last argument (or second-to-last argument in the case of a block included in the method signature).
|
490
|
+
|
491
|
+
3. input and output is handled with blocks:
|
492
|
+
|
493
|
+
`Cmds(“wc -l”){ “one\ntwo\nthree\n” }
|
494
|
+
|
495
|
+
Cmds.stream './test/tick.rb <%= times %>', times: times do |io|
|
496
|
+
io.on_out do |line|
|
497
|
+
# do something with the output line
|
498
|
+
end
|
499
|
+
|
500
|
+
io.on_err do |line|
|
501
|
+
# do something with the error line
|
502
|
+
end
|
503
|
+
end`
|
504
|
+
|
505
|
+
|
506
|
+
### Reuse Commands
|
376
507
|
|
377
508
|
```
|
378
509
|
playbook = Cmds.new "ansible-playbook -i %{inventory} %{playbook}"
|
@@ -393,8 +524,7 @@ prod_playbook.call playbook: "setup.yml"
|
|
393
524
|
```
|
394
525
|
|
395
526
|
|
396
|
-
|
397
|
-
## defaults
|
527
|
+
### defaults
|
398
528
|
|
399
529
|
NEEDS TEST
|
400
530
|
|
@@ -411,22 +541,20 @@ prod_playbook.call playbook: "setup.yml", inventory: "inventory/prod"
|
|
411
541
|
```
|
412
542
|
|
413
543
|
|
414
|
-
|
415
|
-
## input
|
544
|
+
### input
|
416
545
|
|
417
546
|
```
|
418
547
|
c = Cmds.new("wc", input: "blah blah blah).call
|
419
548
|
```
|
420
549
|
|
421
550
|
|
551
|
+
### future..?
|
422
552
|
|
423
|
-
|
424
|
-
|
425
|
-
### exec
|
553
|
+
#### exec
|
426
554
|
|
427
555
|
want to be able to use to exec commands
|
428
556
|
|
429
|
-
|
557
|
+
#### formatters
|
430
558
|
|
431
559
|
kinda like `sprintf` formatters or string escape helpers in Rails, they would be exposed as functions in ERB and as format characters in the shorthand versions:
|
432
560
|
|