cmds 0.2.2 → 0.2.3

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 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