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