Module: Cmds

Defined in:
lib/cmds/debug.rb,
lib/cmds/cmd.rb,
lib/cmds/pipe.rb,
lib/cmds/util.rb,
lib/cmds/spawn.rb,
lib/cmds/sugar.rb,
lib/cmds/result.rb,
lib/cmds/version.rb,
lib/cmds/io_handler.rb,
lib/cmds/erb_context.rb,
lib/cmds/shell_eruby.rb,
lib/cmds/util/params.rb,
lib/cmds/util/defaults.rb,
lib/cmds/util/tokenize_option.rb,
lib/cmds/util/tokenize_options.rb

Overview

debug logging stuff

Defined Under Namespace

Modules: Debug Classes: Cmd, ERBContext, IOHandler, Params, Pipe, Result, ShellEruby

Constant Summary collapse

VERSION =
"0.1.2"
DEFAULTS =

hash of common default values used in method options.

don't use them directly -- use defaults.

the values themselves are frozen so we don't have to worry about cloning them before providing them for use.

the constant Hash itself is not frozen -- you can mutate this to change the default options for ALL Cmds method calls... just be aware of what you're doing. not recommended outside of quick hacks and small scripts since other pieces and parts you don't even know about may depend on said behavior.

{
  # positional arguments for a command
  args: [],
  
  # keyword arguments for a command
  kwds: {},
  
  # how to format a command string for execution
  format: :squish,
  
  # what to do with array option values
  array_mode: :join,
  
  # what to join array option values with when using `array_mode = :join`
  array_join_string: ',',
  
  # what to do with false array values
  false_mode: :omit,
}.map {|k, v| [k, v.freeze]}.to_h

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.assert(template, *args, **kwds, &io_block) ⇒ Object

create a new Cmd and



86
87
88
# File 'lib/cmds/sugar.rb', line 86

def self.assert template, *args, **kwds, &io_block
  Cmd.new(template).capture(*args, **kwds, &io_block).assert
end

.capture(template, *args, **kwds, &input_block) ⇒ Result

create a new Cmd from template with parameters and call capture on it.

Parameters:

  • &input_block (#call)

    optional block that returns a string or IO-like readable object to be used as input for the execution.

  • template (String)

    ERB template parameters are rendered into to create the command string.

  • *args (Array<Object>)

    positional parameters for rendering into the template.

  • **kwds (Hash{Symbol => Object})

    keyword parameters for rendering into the template.

Returns:

  • (Result)

    result with command string, exist status, stdout and stderr.



59
60
61
# File 'lib/cmds/sugar.rb', line 59

def self.capture template, *args, **kwds, &input_block
  Cmd.new(template).capture *args, **kwds, &input_block
end

.chomp(template, *args, **kwds, &input_block) ⇒ String

captures a new Cmd, captures and chomps stdout (sugar for Cmds.out(template, *args, **kwds, &input_block).chomp).

Parameters:

  • template (String)

    ERB template parameters are rendered into to create the command string.

  • *args (Array<Object>)

    positional parameters for rendering into the template.

  • **kwds (Hash{Symbol => Object})

    keyword parameters for rendering into the template.

  • &input_block (#call)

    optional block that returns a string or IO-like readable object to be used as input for the execution.

Returns:

  • (String)

    the command's chomped stdout.

See Also:



153
154
155
# File 'lib/cmds/sugar.rb', line 153

def self.chomp template, *args, **kwds, &input_block
  out(template, *args, **kwds, &input_block).chomp
end

.chomp!(template, *args, **kwds, &input_block) ⇒ String

captures and chomps stdout, raising an error if the command fails. (sugar for Cmds.out!(template, *args, **kwds, &input_block).chomp).

Parameters:

  • template (String)

    ERB template parameters are rendered into to create the command string.

  • *args (Array<Object>)

    positional parameters for rendering into the template.

  • **kwds (Hash{Symbol => Object})

    keyword parameters for rendering into the template.

  • &input_block (#call)

    optional block that returns a string or IO-like readable object to be used as input for the execution.

Returns:

  • (String)

    the command's chomped stdout.

Raises:

  • (SystemCallError)

    if the command fails (non-zero exit status).

See Also:



174
175
176
# File 'lib/cmds/sugar.rb', line 174

def self.chomp! template, *args, **kwds, &input_block
  out!(template, *args, **kwds, &input_block).chomp
end

.debug(msg, values = {}) ⇒ Object

log a debug message along with an optional hash of values.



96
97
98
99
100
# File 'lib/cmds/debug.rb', line 96

def self.debug msg, values = {}
  # don't even bother unless debug logging is turned on
  return unless Debug.on?
  Debug.logger.debug Debug.format(msg, values)
end

.defaults(opts, keys = '*', extras = {}) ⇒ Hash<Symbol, Object>

merge an method call options hash with common defaults for the module.

this makes it easy to use the same defaults in many different methods without repeating the declarations everywhere.

Parameters:

  • opts (Hash)

    hash of overrides provided by method caller.

  • keys (Array<Symbol>, '*') (defaults to: '*')

    keys for the defaults you want to use.

  • extras (Hash<Symbol, Object>) (defaults to: {})

    extra keys and values to add to the returned defaults.

Returns:

  • (Hash<Symbol, Object>)

    defaults to use in the method call.



52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/cmds/util/defaults.rb', line 52

def self.defaults opts, keys = '*', extras = {}
  if keys == '*'
    DEFAULTS
  else      
    keys.
      map {|key|
        [key, DEFAULTS.fetch(key)]
      }.
      to_h
  end.
    merge!(extras).
    merge!(opts)
end

.err(template, *args, **kwds, &input_block) ⇒ String

captures and returns stderr (sugar for Cmds.capture(template, *args, **kwds, &input_block).err).

Parameters:

  • template (String)

    ERB template parameters are rendered into to create the command string.

  • *args (Array<Object>)

    positional parameters for rendering into the template.

  • **kwds (Hash{Symbol => Object})

    keyword parameters for rendering into the template.

  • &input_block (#call)

    optional block that returns a string or IO-like readable object to be used as input for the execution.

Returns:

  • (String)

    the command's stderr.

See Also:



192
193
194
# File 'lib/cmds/sugar.rb', line 192

def self.err template, *args, **kwds, &input_block
  capture(template, *args, **kwds, &input_block).err
end

.error?(template, *args, **kwds, &io_block) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/cmds/sugar.rb', line 80

def self.error? template, *args, **kwds, &io_block
  Cmd.new(template).error? *args, **kwds, &io_block
end

.esc(str) ⇒ Object

shortcut for Shellwords.escape

also makes it easier to change or customize or whatever



15
16
17
# File 'lib/cmds/util.rb', line 15

def self.esc str
  Shellwords.escape str
end

.expand_sub(sub) ⇒ Object

expand one of the substitutions



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/cmds/util.rb', line 20

def self.expand_sub sub
  case sub
  when nil
    # nil is just an empty string, NOT an empty string bash token
    ''
  when Hash
    tokenize_options sub
  else
    esc sub.to_s
  end
end

.format(string, with = :squish) ⇒ Object

formats a command string



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/cmds/util.rb', line 33

def self.format string, with = :squish
  case with
  when :squish
    NRSER.squish string
    
  when :pretty
    pretty_format string
  
  else
    with.call string
  end
end

.ok?(template, *args, **kwds, &io_block) ⇒ Result

create a new Cmd from template with parameters and call Cmds::Cmd#ok? on it.

Parameters:

  • template (String)

    ERB template parameters are rendered into to create the command string.

  • *args (Array<Object>)

    positional parameters for rendering into the template.

  • **kwds (Hash{Symbol => Object})

    keyword parameters for rendering into the template.

  • &io_block (#call & (#arity ∈ {0, 1}))

    optional block to handle io. behavior depends on arity:

    • arity 0
      • block is called and expected to return an object suitable for input (nil, String or IO-like).
    • arity 1
      • block is called with the IOHandler instance for the execution, which it can use to handle input and outputs.

Returns:

  • (Result)

    result with command string, exist status, stdout and stderr.



75
76
77
# File 'lib/cmds/sugar.rb', line 75

def self.ok? template, *args, **kwds, &io_block
  Cmd.new(template).ok? *args, **kwds, &io_block
end

.out(template, *args, **kwds, &input_block) ⇒ String

creates a new Cmd, captures and returns stdout (sugar for Cmds.capture(template, *args, **kwds, &input_block).out).

Parameters:

  • template (String)

    ERB template parameters are rendered into to create the command string.

  • *args (Array<Object>)

    positional parameters for rendering into the template.

  • **kwds (Hash{Symbol => Object})

    keyword parameters for rendering into the template.

  • &input_block (#call)

    optional block that returns a string or IO-like readable object to be used as input for the execution.

Returns:

  • (String)

    the command's stdout.

See Also:



114
115
116
# File 'lib/cmds/sugar.rb', line 114

def self.out template, *args, **kwds, &input_block
  Cmd.new(template).out *args, **kwds, &input_block
end

.out!(template, *args, **kwds, &input_block) ⇒ String

creates a new Cmd, captures and returns stdout. raises an error if the command fails.

Parameters:

  • template (String)

    ERB template parameters are rendered into to create the command string.

  • *args (Array<Object>)

    positional parameters for rendering into the template.

  • **kwds (Hash{Symbol => Object})

    keyword parameters for rendering into the template.

  • &input_block (#call)

    optional block that returns a string or IO-like readable object to be used as input for the execution.

Returns:

  • (String)

    the command's stdout.

Raises:

  • (SystemCallError)

    if the command fails (non-zero exit status).

See Also:



135
136
137
# File 'lib/cmds/sugar.rb', line 135

def self.out! template, *args, **kwds, &input_block
  Cmd.new(template).out! *args, **kwds, &input_block
end

.prepare(template, *args, **kwds) ⇒ String

create a new Cmd instance with the template and parameters and calls Cmds::Cmd#prepare.

Parameters:

  • template (String)

    ERB template parameters are rendered into to create the command string.

  • *args (Array<Object>)

    positional parameters for rendering into the template.

  • **kwds (Hash{Symbol => Object})

    keyword parameters for rendering into the template.

Returns:

  • (String)

    rendered and formatted command string ready to be executed.



40
41
42
# File 'lib/cmds/sugar.rb', line 40

def self.prepare template, *args, **kwds
  Cmd.new(template).prepare *args, **kwds
end

.pretty_format(string) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/cmds/util.rb', line 46

def self.pretty_format string
  string = string.gsub(/\n(\s*\n)+\n/, "\n\n")
  
  string.lines.map {|line|
    line = line.rstrip
    
    if line.end_with? '\\'
      line
    elsif line == ''
      '\\'
    elsif line =~ /\s$/
      line + '\\'
    else
      line + ' \\'
    end
  }.join("\n")
end

.replace_shortcuts(template) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/cmds/util.rb', line 64

def self.replace_shortcuts template
  template
    .gsub(
      # %s => <%= arg %>
      /(?<=\A|[[:space:]])\%s(?=\Z|[[:space:]])/,
      '<%= arg %>'
    )
    .gsub(
      # %%s => %s (escpaing)
      /(?<=\A|[[:space:]])(\%+)\%s(?=\Z|[[:space:]])/,
      '\1s'
    )
    .gsub(
      # %{key} => <%= key %>, %{key?} => <%= key? %>
      /(?<=\A|[[:space:]])\%\{([a-zA-Z_]+\??)\}(?=\Z|[[:space:]])/,
      '<%= \1 %>'
    )
    .gsub(
      # %%{key} => %{key}, %%{key?} => %{key?} (escpaing)
      /(?<=\A|[[:space:]])(\%+)\%\{([a-zA-Z_]+\??)\}(?=\Z|[[:space:]])/,
      '\1{\2}\3'
    )
    .gsub(
      # %<key>s => <%= key %>, %<key?>s => <%= key? %>
      /(?<=\A|[[:space:]])\%\<([a-zA-Z_]+\??)\>s(?=\Z|[[:space:]])/,
      '<%= \1 %>'
    )
    .gsub(
      # %%<key>s => %<key>s, %%<key?>s => %<key?>s (escaping)
      /(?<=\A|[[:space:]])(\%+)\%\<([a-zA-Z_]+\??)\>s(?=\Z|[[:space:]])/,
      '\1<\2>s'
    )
end

.spawn(cmd, input = nil, env = {}, &io_block) ⇒ Object

internal core function to spawn and stream inputs and/or outputs using threads.

originally inspired by

https://nickcharlton.net/posts/ruby-subprocesses-with-stdout-stderr-streams.html

with major modifications from looking at Ruby's open3 module.

Parameters:

  • cmd (String)

    shell-ready command string.

  • input (nil | String | #read) (defaults to: nil)

    string or readable input. here so that Cmds instances can pass their @input instance variable -- &io_block overrides it.

  • env (Hash{Symbol | String => Object}) (defaults to: {})

    blah

  • &io_block (#call & (#arity ∈ {0, 1}))

    optional block to handle io. behavior depends on arity:

    • arity 0
      • block is called and expected to return an object suitable for input (nil, String or IO-like).
    • arity 1
      • block is called with the IOHandler instance for the execution, which it can use to handle input and outputs.

Raises:

  • (ArgumentError)

    if &io_block has arity greater than 1.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/cmds/spawn.rb', line 48

def self.spawn cmd, input = nil, env = {}, &io_block
  Cmds.debug "entering Cmds#really_stream",
    cmd: cmd,
    input: input,
    io_block: io_block

  # create the handler that will be yielded to the input block
  handler = Cmds::IOHandler.new

  # handle input
  # 
  # if a block was provided it overrides the `input` argument.
  # 
  if io_block
    case io_block.arity
    when 0
      # when the input block takes no arguments it returns the input
      input = io_block.call
    when 1
      # when the input block takes one argument, give it the handler and
      # ignore the return value
      io_block.call handler

      # if input was assigned to the handler in the block, use it as input
      input = handler.in unless handler.in.nil?
    else
      # bad block provided
      raise ArgumentError.new <<-BLOCK.squish
        provided input block must have arity 0 or 1
      BLOCK
    end # case io_block.arity
  end # if io_block

  # hash of options that will be passed to `spawn`
  spawn_opts = {}

  Cmds.debug "looking at input...",
    input: input

  # (possibly) create the input pipe... this will be nil if the provided
  # input is io-like. in this case it will be used directly in the 
  # `spawn` options.
  in_pipe = case input
  when nil, String
    Cmds.debug "input is a String or nil, creating pipe..."

    in_pipe = Cmds::Pipe.new "INPUT", :in
    spawn_opts[:in] = in_pipe.r

    # don't buffer input
    in_pipe.w.sync = true
    in_pipe

  else
    Cmds.debug "input should be io-like, setting spawn opt.",
      input: input
    if input == $stdin
      Cmds.debug "input is $stdin."
    end
    spawn_opts[:in] = input
    nil

  end # case input

  # (possibly) create the output pipes.
  # 
  # `stream` can be told to send it's output to either:
  # 
  # 1.  a Proc that will invoked with each line.
  # 2.  an io-like object that can be provided as `spawn`'s `:out` or 
  #     `:err` options.
  # 
  # in case (1) a `Cmds::Pipe` wrapping read and write piped `IO` instances
  # will be created and assigned to the relevant of `out_pipe` or `err_pipe`.
  # 
  # in case (2) the io-like object will be sent directly to `spawn` and
  # the relevant `out_pipe` or `err_pipe` will be `nil`.
  #
  out_pipe, err_pipe = [
    ["ERROR", :err],
    ["OUTPUT", :out],
  ].map do |name, sym|
    Cmds.debug "looking at #{ name }..."
    # see if hanlder.out or hanlder.err is a Proc
    if handler.send(sym).is_a? Proc
      Cmds.debug "#{ name } is a Proc, creating pipe..."
      pipe = Cmds::Pipe.new name, sym
      # the corresponding :out or :err option for spawn needs to be
      # the pipe's write handle
      spawn_opts[sym] = pipe.w
      # return the pipe
      pipe

    else
      Cmds.debug "#{ name } should be io-like, setting spawn opt.",
        output: handler.send(sym)
      spawn_opts[sym] = handler.send(sym)
      # the pipe is nil!
      nil
    end
  end # map outputs

  Cmds.debug "spawning...",
    env: env,
    cmd: cmd,
    opts: spawn_opts

  pid = Process.spawn env.map {|k, v| [k.to_s, v]}.to_h,
                      cmd,
                      spawn_opts

  Cmds.debug "spawned.",
    pid: pid

  wait_thread = Process.detach pid
  wait_thread[:name] = "WAIT"

  Cmds.debug "wait thread created.",
    thread: wait_thread

  # close child ios if created
  # the spawned process will read from in_pipe.r so we don't need it
  in_pipe.r.close if in_pipe
  # and we don't need to write to the output pipes, that will also happen
  # in the spawned process
  [out_pipe, err_pipe].each {|pipe| pipe.w.close if pipe}

  # create threads to handle any pipes that were created

  in_thread = if in_pipe
    Thread.new do
      Thread.current[:name] = in_pipe.name
      Cmds.debug "thread started, writing input..."

      in_pipe.w.write input unless input.nil?

      Cmds.debug "write done, closing in_pipe.w..."
      in_pipe.w.close

      Cmds.debug "thread done."
    end # Thread
  end

  out_thread, err_thread = [out_pipe, err_pipe].map do |pipe|
    if pipe
      Thread.new do
        Thread.current[:name] = pipe.name
        Cmds.debug "thread started"

        loop do
          Cmds.debug "blocking on gets..."
          line = pipe.r.gets
          if line.nil?
            Cmds.debug "received nil, output done."
          else
            Cmds.debug <<-BLOCK.squish
              received #{ line.bytesize } bytes, passing to handler.
            BLOCK
          end
          handler.thread_send_line pipe.sym, line
          break if line.nil?
        end

        Cmds.debug "reading done, closing pipe.r (unless already closed)..."
        pipe.r.close unless pipe.r.closed?

        Cmds.debug "thread done."
      end # thread
    end # if pipe
  end # map threads

  Cmds.debug "handing off main thread control to the handler..."
  begin
    handler.start

    Cmds.debug "handler done."

  ensure
    # wait for the threads to complete
    Cmds.debug "joining threads..."

    [in_thread, out_thread, err_thread, wait_thread].each do |thread|
      if thread
        Cmds.debug "joining #{ thread[:name] } thread..."
        thread.join
      end
    end

    Cmds.debug "all threads done."
  end

  status = wait_thread.value.exitstatus
  Cmds.debug "exit status: #{ status.inspect }"

  Cmds.debug "checking @assert and exit status..."
  if @assert && status != 0
    # we don't necessarily have the err output, so we can't include it
    # in the error message
    msg = <<-BLOCK.squish
      streamed command `#{ cmd }` exited with status #{ status }
    BLOCK

    raise SystemCallError.new msg, status
  end

  Cmds.debug "streaming completed."

  return status
end

.stream(template, *subs, &input_block) ⇒ Object



91
92
93
# File 'lib/cmds/sugar.rb', line 91

def self.stream template, *subs, &input_block
  Cmds::Cmd.new(template).stream *subs, &input_block
end

.stream!(template, *subs, &input_block) ⇒ Object



96
97
98
# File 'lib/cmds/sugar.rb', line 96

def self.stream! template, *subs, &input_block
  Cmds::Cmd.new(template, assert: true).stream *subs, &input_block
end

.tokenize_option(name, value, opts = {}) ⇒ Array<String>

turn an option name and value into an array of shell-escaped string token suitable for use in a command.

Parameters:

  • name (String)

    string name (one or more characters).

  • value (*)

    value of the option.

  • opts (Hash) (defaults to: {})
  • [Symbol] (Hash)

    a customizable set of options

  • [String] (Hash)

    a customizable set of options

Returns:

  • (Array<String>)

    string tokens.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/cmds/util/tokenize_option.rb', line 38

def self.tokenize_option name, value, opts = {}
  opts = defaults opts, [:array_mode, :array_join_string, :false_mode]
  
  unless name.is_a?(String) && name.length > 0
    raise ArgumentError.new <<-END.squish
      `name` must be a String of length greater than zero,
      found #{ name.inspect }
    END
  end
  
  prefix, separator = if name.length == 1
    # -b <value> style
    ['-', ' ']
  else
    # --blah=<value> style
    ['--', '=']
  end
    
  case value
  when nil
    []
    
  when Array
    # the PITA one
    case opts[:array_mode]
    when :repeat
      # `-b 1 -b 2 -b 3` / `--blah=1 --blah=2 --blah=3` style
      value.flatten.map {|v|
        prefix + esc(name) + separator + esc(v)
      }
      
    when :join
      # `-b 1,2,3` / `--blah=1,2,3` style
      [ prefix + 
        esc(name) + 
        separator + 
        esc(value.join opts[:array_join_string]) ]
      
    when :json
      [prefix + esc(name) + separator + esc(JSON.dump value)]
      
    else
      # SOL
      raise ArgumentError.new <<-END.squish
        bad array_mode option: #{ opts[:array_mode] }, 
        should be :repeat, :join or :json
      END
      
    end
    
  when true
    # `-b` or `--blah`
    [prefix + esc(name)]
    
  when false
    case opts[:false_mode]
    when :omit
      # don't emit any token for a false boolean
      []
    when :no
      # `--no-blah` style
      # 
      # but there's not really a great way to handle short names...
      # we use `--no-b`
      # 
      ["--no-#{ esc(name) }"]
      
    else
      raise ArgumentError.new <<-END.squish
        bad :false_mode option: #{ opts[:false_mode] }, 
        should be :omit or :no
      END
    end
    
  else
    # we let .esc handle it
    [prefix + esc(name) + separator + esc(value)]
    
  end
end

.tokenize_options(hash, opts = {}) ⇒ Object

escape option hash.

this is only useful for the two common option styles:

  • single character keys become -<char> <value>

    {x: 1}    => "-x 1"
    
  • longer keys become --<key>=<value> options

    {blah: 2} => "--blah=2"
    

if you have something else, you're going to have to just put it in the cmd itself, like:

Cmds "blah -assholeOptionOn:%{s}", "ok"

or whatever similar shit said command requires.

however, if the value is an Array, it will repeat the option for each value:

{x:     [1, 2, 3]} => "-x 1 -x 2 -x 3"
{blah:  [1, 2, 3]} => "--blah=1 --blah=2 --blah=3"

i can't think of any right now, but i swear i've seen commands that take opts that way.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/cmds/util/tokenize_options.rb', line 32

def self.tokenize_options hash, opts = {}
  opts = defaults opts, [:array_mode, :array_join_string, :false_mode]
  
  hash.map {|key, value|
    # keys need to be strings
    key = key.to_s unless key.is_a? String

    [key, value]

  }.sort {|(key_a, value_a), (key_b, value_b)|
    # sort by the (now string) keys
    key_a <=> key_b

  }.map {|key, value|
    tokenize_option key, value
    
  }.flatten.join ' '
end

Instance Method Details

#defaults(opts, keys = '*', extras = {}) ⇒ Object

proxy through to class method defaults.



68
69
70
# File 'lib/cmds/util/defaults.rb', line 68

def defaults opts, keys = '*', extras = {}
  self.class.defaults opts, keys, extras
end