cmds 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 82780e4858c704d9607db8032e99818163e80220
4
- data.tar.gz: e0ba46030000a9c252940a1a80beb1f528ea3a6c
3
+ metadata.gz: 78e95949457351a76889d9559cace8e0d6357027
4
+ data.tar.gz: 1741db7b86690a11676165ccf6941696a24a63c5
5
5
  SHA512:
6
- metadata.gz: 0c3bd920b4afeaaabc83d967aaaffbe7c24a084d2164532b741dbb96c0e03385452136ecfe3712069a716326d3725aab9a38ea5c387f9a043118235f0c16ab47
7
- data.tar.gz: 4597c1e73ead17c16c850cd0d6043b0c3092c25499f858026f2ff3324975dafb0ce496f48e1eb2febd78a79308436cd5c0eaf8bedfd9e4e63d55634c497bb18f
6
+ metadata.gz: 74d33994938b16b3fc3c33c4ef915e21d729efb50d80f8c303329a7d0513f382a9c29093bdb4c1deb719833b81476389444e2e65922a6249103a254c4482d8e8
7
+ data.tar.gz: de650778601f8e6c1ed8faa751edc532badf978c281111c8d7e937635f1a699d1b6e265dc11ce5788e2a736f4e871fb9ebdeaee6d441f09be05616c93c7f872a
data/README.md CHANGED
@@ -1,93 +1,112 @@
1
- # cmds
1
+ Cmds
2
+ =============================================================================
2
3
 
3
- cmds tries to make it easier to read, write and remember using shell commands in Ruby.
4
+ `Cmds` tries to make it easier to read, write and remember using shell commands in Ruby.
4
5
 
5
- it treats generating shell the in a similar fashion to generating SQL or HTML.
6
+ It treats generating shell the in a similar fashion to generating SQL or HTML.
6
7
 
8
+ 1. [Status](#status)
9
+ 3. [Real-World Examples](#real-world-examples)
10
+ 2. [Installation](#installation)
7
11
 
8
- ## status
9
12
 
10
- eh, 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.
13
+ -----------------------------------------------------------------------------
14
+ Status
15
+ -----------------------------------------------------------------------------
11
16
 
17
+ Ya know, before you get too excited...
12
18
 
13
- ## installation
19
+ 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.
14
20
 
15
- Add this line to your application's Gemfile:
16
-
17
- ```
18
- gem 'cmds'
19
- ```
20
-
21
-
22
- And then execute:
23
-
24
- ```
25
- $ bundle
26
- ```
27
-
28
-
29
- Or install it yourself as:
30
-
31
- ```
32
- $ gem install cmds
33
- ```
34
21
 
22
+ -----------------------------------------------------------------------------
23
+ Real-World Examples
24
+ -----------------------------------------------------------------------------
35
25
 
26
+ - Instead of
27
+
28
+ ```Ruby
29
+ `psql -U #{ db_config['username'] || ENV['USER'] } #{ db_config['database']} < #{ filepath.shellescape }`
30
+ ```
31
+
32
+ write
33
+
34
+ ```Ruby
35
+ Cmds 'psql %{opts} %{db} < %{dump}',
36
+ db: db_config['database'],
37
+ dump: filepath,
38
+ opts: {
39
+ username: db_config['username'] || ENV['USER']
40
+ }
41
+ ```
42
+
43
+ to run a command like
44
+
45
+ ```bash
46
+ psql --username=nrser that_db < ./some/file/path
47
+ ```
48
+
49
+
50
+ - Instead of
51
+
52
+ ```Ruby
53
+ `aws s3 sync s3://#{ PROD_APP_NAME } #{ s3_path.shellescape }`
54
+ ```
55
+
56
+ write
57
+
58
+ ```Ruby
59
+ Cmds 'aws s3 sync %{uri} %{path}',
60
+ uri: "s3://#{ PROD_APP_NAME }",
61
+ path: s3_path
62
+ ```
36
63
 
37
- ## real-world examples
38
64
 
39
- instead of
65
+ - Instead of
66
+
67
+ ```Ruby
68
+ `PGPASSWORD=#{ config[:password].shellescape } pg_dump -U #{ config[:username].shellescape } -h #{ config[:host].shellescape } -p #{ config[:port] } #{ config[:database].shellescape } > #{ filepath.shellescape }`
69
+ ```
70
+
71
+ write
72
+
73
+ ```Ruby
74
+ Cmds 'PGPASSWORD=%{password} pg_dump %{opts} %{database} > %{filepath}',
75
+ password: config[:password],
76
+ database: config[:database],
77
+ filepath: filepath,
78
+ opts: {
79
+ username: config[:username],
80
+ host: config[:host],
81
+ port: config[:port],
82
+ }
83
+ ```
40
84
 
41
- ```
42
- `psql -U #{ db_config['username'] || ENV['USER'] } #{ db_config['database']} < #{ filepath.shellescape }`
43
- ```
85
+ -----------------------------------------------------------------------------
86
+ Installation
87
+ -----------------------------------------------------------------------------
44
88
 
45
- write
89
+ Add this line to your application's `Gemfile`:
46
90
 
47
91
  ```
48
- Cmds 'psql %{opts} %{db} < %{dump}',
49
- db: db_config['database'],
50
- dump: filepath,
51
- opts: {
52
- username: db_config['username'] || ENV['USER']
53
- }
92
+ gem 'cmds'
54
93
  ```
55
94
 
56
- instead of
57
-
58
- ```
59
- `aws s3 sync s3://#{ PROD_APP_NAME } #{ s3_path.shellescape }`
60
- ```
61
95
 
62
- write
96
+ And then execute:
63
97
 
64
98
  ```
65
- Cmds 'aws s3 sync %{uri} %{path}', uri: "s3://#{ PROD_APP_NAME }"
66
- path: s3_path
99
+ $ bundle
67
100
  ```
68
101
 
69
- instead of
70
-
71
- ```
72
- `PGPASSWORD=#{ config[:password].shellescape } pg_dump -U #{ config[:username].shellescape } -h #{ config[:host].shellescape } -p #{ config[:port] } #{ config[:database].shellescape } > #{ filepath.shellescape }`
73
- ```
74
102
 
75
- write
103
+ Or install it yourself as:
76
104
 
77
105
  ```
78
- Cmds 'PGPASSWORD=%{password} pg_dump %{opts} %{database} > %{filepath}',
79
- password: config[:password],
80
- database: config[:database],
81
- filepath: filepath,
82
- opts: {
83
- username: config[:username],
84
- host: config[:host],
85
- port: config[:port],
86
- }
106
+ $ gem install cmds
87
107
  ```
88
108
 
89
109
 
90
-
91
110
  ## architecture
92
111
 
93
112
  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. the Cmds` `augmented with a health helping of connivence methods for creating and executing a `Cmds` instance in common ways.
@@ -136,7 +155,7 @@ all `Cmds` instance execution methods have the same form for accepting these:
136
155
 
137
156
  `Cmds "cp <%= arg %> <%= arg %>", [src_path, dest_path]`
138
157
 
139
- 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.
158
+ 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.
140
159
 
141
160
  2. keyword arguments are provided as optional hash that must be the last argument:
142
161
 
@@ -288,9 +307,15 @@ Cmds "blah <%= maybe? %> <%= arg %>", ["value"]
288
307
  Cmds "blah <%= maybe? %> <%= arg %>", ["value"], maybe: "yes!"
289
308
  ```
290
309
 
291
- ### shortcuts
310
+ ************************************************************************
311
+
312
+
313
+ ### Shortcuts
314
+
315
+ `Cmds` has (limited, custom) support for [printf][]-style shortcuts.
316
+
317
+ [printf]: https://en.wikipedia.org/wiki/Printf_format_string
292
318
 
293
- there are support for `sprintf`-style shortcuts.
294
319
 
295
320
  **positional**
296
321
 
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require 'nrser'
5
+ require 'cmds'
6
+
7
+
8
+ require 'nrser/refinements'
9
+ using NRSER
10
+
11
+
12
+ # You can add fixtures and/or initialization code here to make experimenting
13
+ # with your gem easier. You can also use a different console, if you like.
14
+
15
+ # (If you use this, don't forget to add pry to your Gemfile!)
16
+ begin
17
+ require "pry"
18
+ rescue LoadError => error
19
+ puts "Failed to load `pry` gem - add it to your Gemfile or edit this file"
20
+
21
+ require "irb"
22
+ IRB.start
23
+ else
24
+ Pry.start
25
+ end
@@ -28,4 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "yard", "~> 0.9.9"
29
29
  spec.add_development_dependency "redcarpet"
30
30
  spec.add_development_dependency 'github-markup'
31
+
32
+ # A better REPL
33
+ spec.add_development_dependency 'pry'
31
34
  end
@@ -208,37 +208,19 @@ class Cmds
208
208
  #
209
209
  # Available as the {#kwds} attribute.
210
210
  #
211
- def initialize template,
212
- args: [],
213
- assert: false,
214
- chdir: nil,
215
- env: {},
216
- env_mode: :inline,
217
- format: :squish,
218
- input: nil,
219
- kwds: {}
211
+ def initialize template, **opts
212
+ opts = defaults opts
213
+
220
214
  Cmds.debug "Cmd constructing...",
221
215
  template: template,
222
- opts: {
223
- args: args,
224
- kwds: kwds,
225
- input: input,
226
- assert: assert,
227
- env: env,
228
- format: format,
229
- env_mode: env_mode,
230
- chdir: chdir,
231
- }
216
+ opts: opts
232
217
 
233
218
  @template = template
234
- @args = args.freeze
235
- @kwds = kwds.freeze
236
- @input = input
237
- @assert = assert
238
- @env = env.freeze
239
- @format = format
240
- @env_mode = env_mode
241
- @chdir = chdir
219
+
220
+ # Assign options to instance variables
221
+ opts.each { |key, value|
222
+ instance_variable_set "@#{ key }", value
223
+ }
242
224
 
243
225
  # An internal cache of the last result of calling {#prepare}, or `nil` if
244
226
  # {#prepare} has never been called. Kinda funky but ends up being useful.
@@ -274,8 +256,21 @@ class Cmds
274
256
  # the rendered command string.
275
257
  #
276
258
  def render *args, **kwds
277
- context = Cmds::ERBContext.new((self.args + args), self.kwds.merge(kwds))
278
- erb = Cmds::ShellEruby.new Cmds.replace_shortcuts(self.template)
259
+ # Create the context for ERB
260
+ context = Cmds::ERBContext.new(
261
+ (self.args + args),
262
+
263
+ self.kwds.merge( kwds ),
264
+
265
+ tokenize_options_opts: TOKENIZE_OPT_KEYS.
266
+ each_with_object( {} ) { |key, hash|
267
+ value = instance_variable_get "@#{ key}"
268
+ hash[key] = value unless value.nil?
269
+ }
270
+ )
271
+
272
+ erb = Cmds::ShellEruby.new Cmds.replace_shortcuts( self.template )
273
+
279
274
  rendered = NRSER.dedent erb.result(context.get_binding)
280
275
 
281
276
  if self.env_mode == :inline && !self.env.empty?
@@ -2,10 +2,11 @@ class Cmds
2
2
  class ERBContext < BasicObject
3
3
  attr_reader :args
4
4
 
5
- def initialize args, kwds
5
+ def initialize args, kwds, tokenize_options_opts: {}
6
6
  @args = args
7
7
  @kwds = kwds
8
8
  @arg_index = 0
9
+ @tokenize_options_opts = tokenize_options_opts
9
10
  end
10
11
 
11
12
  def method_missing sym, *args, &block
@@ -5,7 +5,7 @@ class Cmds
5
5
  # leaves `<%== %>` raw) that calls `Cmds.expand_sub` on the value
6
6
  class ShellEruby < Erubis::EscapedEruby
7
7
  def escaped_expr code
8
- "::Cmds.tokenize(#{code.strip})"
8
+ "::Cmds.tokenize(#{code.strip}, **@tokenize_options_opts)"
9
9
  end
10
10
  end
11
11
  end # class Cmds
@@ -37,8 +37,14 @@ class Cmds
37
37
  # @return [String]
38
38
  # rendered and formatted command string ready to be executed.
39
39
  #
40
- def self.prepare template, *args, **kwds
41
- Cmds.new(template).prepare *args, **kwds
40
+ def self.prepare template, *args, **kwds, &options_block
41
+ options = if options_block
42
+ options_block.call
43
+ else
44
+ {}
45
+ end
46
+
47
+ Cmds.new(template, **options).prepare *args, **kwds
42
48
  end
43
49
 
44
50
 
@@ -29,14 +29,14 @@ class Cmds
29
29
  # @return [String]
30
30
  # tokenized string ready for the shell.
31
31
  #
32
- def self.tokenize *values
32
+ def self.tokenize *values, **opts
33
33
  values.map {|value|
34
34
  case value
35
35
  when nil
36
36
  # nil is just an empty string, NOT an empty string bash token
37
37
  ''
38
38
  when Hash
39
- tokenize_options value
39
+ tokenize_options value, **opts
40
40
  else
41
41
  esc value.to_s
42
42
  end
@@ -30,7 +30,24 @@ class Cmds
30
30
 
31
31
  # what to do with false array values
32
32
  false_mode: :omit,
33
- }.map {|k, v| [k, v.freeze]}.to_h
33
+
34
+ # Stick ENV var defs inline at beginning of command
35
+ env_mode: :inline,
36
+
37
+ # No additional environment
38
+ env: {},
39
+
40
+ # Don't change directories
41
+ chdir: nil,
42
+
43
+ # Don't asset (raise error if exit code is not 0)
44
+ assert: false,
45
+
46
+ # No input
47
+ input: nil,
48
+
49
+ }.map { |k, v| [k, v.freeze] }.to_h.freeze
50
+
34
51
 
35
52
  # merge an method call options hash with common defaults for the module.
36
53
  #
@@ -51,7 +68,7 @@ class Cmds
51
68
  #
52
69
  def self.defaults opts, keys = '*', extras = {}
53
70
  if keys == '*'
54
- DEFAULTS
71
+ DEFAULTS.dup
55
72
  else
56
73
  keys.
57
74
  map {|key|
@@ -59,13 +76,15 @@ class Cmds
59
76
  }.
60
77
  to_h
61
78
  end.
62
- merge!(extras).
63
- merge!(opts)
79
+ merge!( extras ).
80
+ merge!( opts )
64
81
  end
65
82
 
83
+
66
84
  # proxy through to class method {Cmds.defaults}.
67
85
  #
68
86
  def defaults opts, keys = '*', extras = {}
69
87
  self.class.defaults opts, keys, extras
70
88
  end
89
+
71
90
  end
@@ -4,6 +4,8 @@ require 'nrser/refinements'
4
4
  require_relative "defaults"
5
5
 
6
6
  class Cmds
7
+ TOKENIZE_OPT_KEYS = [:array_mode, :array_join_string, :false_mode]
8
+
7
9
  # turn an option name and value into an array of shell-escaped string
8
10
  # token suitable for use in a command.
9
11
  #
@@ -13,7 +15,7 @@ class Cmds
13
15
  # @param [*] value
14
16
  # value of the option.
15
17
  #
16
- # @param [Hash] opts
18
+ # @param [Hash] **opts
17
19
  # @option [Symbol] :array_mode (:multiple)
18
20
  # one of:
19
21
  #
@@ -33,8 +35,10 @@ class Cmds
33
35
  # @return [Array<String>]
34
36
  # string tokens.
35
37
  #
36
- def self.tokenize_option name, value, opts = {}
37
- opts = defaults opts, [:array_mode, :array_join_string, :false_mode]
38
+ def self.tokenize_option name, value, **opts
39
+ pp opts: opts
40
+
41
+ opts = defaults opts, TOKENIZE_OPT_KEYS
38
42
 
39
43
  unless name.is_a?(String) && name.length > 0
40
44
  raise ArgumentError.new NRSER.squish <<-END
@@ -72,7 +76,7 @@ class Cmds
72
76
  esc(value.join opts[:array_join_string]) ]
73
77
 
74
78
  when :json
75
- [prefix + esc(name) + separator + esc(JSON.dump value)]
79
+ [prefix + esc(name) + separator + "'" + JSON.dump(value).gsub(%{'}, %{'"'"'}) + "'"]
76
80
 
77
81
  else
78
82
  # SOL
@@ -29,8 +29,8 @@ class Cmds
29
29
  # i can't think of any right now, but i swear i've seen commands that take
30
30
  # opts that way.
31
31
  #
32
- def self.tokenize_options hash, opts = {}
33
- opts = defaults opts, [:array_mode, :array_join_string, :false_mode]
32
+ def self.tokenize_options hash, **opts
33
+ opts = defaults opts, TOKENIZE_OPT_KEYS
34
34
 
35
35
  hash.map {|key, value|
36
36
  # keys need to be strings
@@ -43,7 +43,7 @@ class Cmds
43
43
  key_a <=> key_b
44
44
 
45
45
  }.map {|key, value|
46
- tokenize_option key, value
46
+ tokenize_option key, value, **opts
47
47
 
48
48
  }.flatten.join ' '
49
49
  end # .tokenize_options
@@ -1,3 +1,3 @@
1
1
  class Cmds
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -207,4 +207,48 @@ describe "Cmds.prepare" do
207
207
  ).to eq "git add x y z"
208
208
  end
209
209
  end
210
+
211
+ describe "options with list values" do
212
+
213
+ context "default behavior (:join)" do
214
+ it "outputs a comma-separated list" do
215
+ expect(
216
+ Cmds.prepare 'blah <%= opts %>', opts: {list: ['a', 'b', 'see']}
217
+ ).to eq 'blah --list=a,b,see'
218
+ end
219
+ end
220
+
221
+ context "specify :repeat behavior" do
222
+ it "outputs repeated options" do
223
+ expect(
224
+ Cmds.prepare 'blah <%= opts %>',
225
+ opts: { list: ['a', 'b', 'see'] } {
226
+ { array_mode: :repeat }
227
+ }
228
+ ).to eq 'blah --list=a --list=b --list=see'
229
+ end
230
+ end
231
+
232
+ context "specify :json behavior" do
233
+ it "outputs JSON-encoded options" do
234
+ expect(
235
+ Cmds.prepare 'blah <%= opts %>',
236
+ opts: { list: ['a', 'b', 'see'] } {
237
+ { array_mode: :json }
238
+ }
239
+ ).to eq %{blah --list='["a","b","see"]'}
240
+ end
241
+
242
+ it "handles single quotes in the string" do
243
+ expect(
244
+ Cmds.prepare 'blah <%= opts %>',
245
+ opts: { list: ["you're the best"] } {
246
+ { array_mode: :json }
247
+ }
248
+ ).to eq %{blah --list='["you'"'"'re the best"]'}
249
+ end
250
+ end
251
+
252
+ end # "options with list values"
253
+
210
254
  end # ::sub
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmds
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - nrser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-16 00:00:00.000000000 Z
11
+ date: 2017-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nrser
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  description:
140
154
  email:
141
155
  - neil@ztkae.com
@@ -152,6 +166,7 @@ files:
152
166
  - Rakefile
153
167
  - ansible/dev.yml
154
168
  - ansible/hosts
169
+ - bin/console
155
170
  - bin/rake
156
171
  - bin/rspec
157
172
  - cmds.gemspec