Class: Cmds

Inherits:
Object
  • Object
show all
Defined in:
lib/cmds.rb,
lib/cmds/pipe.rb,
lib/cmds/util.rb,
lib/cmds/debug.rb,
lib/cmds/spawn.rb,
lib/cmds/sugar.rb,
lib/cmds/result.rb,
lib/cmds/stream.rb,
lib/cmds/capture.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/shell_escape.rb,
lib/cmds/util/tokenize_value.rb,
lib/cmds/util/tokenize_option.rb,
lib/cmds/util/tokenize_options.rb

Overview

Definitions

Defined Under Namespace

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

Shell Quoting and Escaping Methods collapse

QUOTE_TYPES =

Quote "name" keys :single and :double mapped to their character.

Returns:

  • (Hash<Symbol, String>)
{
  single: %{'},
  double: %{"},
}.freeze
QUOTE_VALUES =

List containing just ' and ".

Returns:

  • (Array<String>)
QUOTE_TYPES.values.freeze

Constant Summary collapse

ROOT =

Absolute, expanded path to the gem's root directory.

Returns:

  • (Pathname)
(Pathname.new( __FILE__ ).dirname / '..' / '..').expand_path
VERSION =

Library version string.

Returns:

  • (String)
"0.2.7"
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.

{
  # Alphabetical...
  
  # positional arguments for a command
  args: [],
  
  # what to join array option values with when using `array_mode = :join`
  array_join_string: ',',
  
  # what to do with array option values
  array_mode: :join,
  
  # Don't asset (raise error if exit code is not 0)
  assert: false,
  
  # Don't change directories
  chdir: nil,
  
  # No additional environment
  env: {},
  
  # Stick ENV var defs inline at beginning of command
  env_mode: :inline,
  
  # What to do with `false` *option* values (not `false` values as regular
  # values or inside collections)
  # 
  # Just leave them out all-together
  false_mode: :omit,
  
  # Flatten nested array values to a single array.
  # 
  # Many CLI commands accept arrays in some form or another, but I'm hard
  # pressed to think of one that accepts nested arrays. Flattening can make
  # it simpler to generate values.
  # 
  flatten_array_values: true,
  
  # how to format a command string for execution
  format: :squish,
  
  hash_mode: :join,
  
  # Join hash keys and values with `:`
  hash_join_string: ':',
  
  # No input
  input: nil,
  
  # keyword arguments for a command
  kwds: {},

  # What to use to separate "long" opt names (more than one character) from
  # their values. I've commonly seen '=' (`--name=VALUE`)
  # and ' ' (`--name VALUE`).
  long_opt_separator: '=',
  
  # What to use to separate "short" opt names (single character) from their
  # values. I've commonly seen ' ' (`-x VALUE`) and '' (`-xVALUE`).
  short_opt_separator: ' ',
  
}.map { |k, v| [k, v.freeze] }.to_h.freeze
TOKENIZE_OPT_KEYS =
[
  :array_mode,
  :array_join_string,
  :false_mode,
  :flatten_array_values,
  :hash_mode,
  :hash_join_string,
  :long_opt_separator,
  :short_opt_separator,
].freeze

Instance Attribute Summary collapse

Execution Instance Methods collapse

Spawn Methods collapse

Shell Quoting and Escaping Methods collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(template, **opts) ⇒ Cmds

Construct a Cmds instance.

Parameters:

  • template (String)

    String template to use when creating the command string to send to the shell via #prepare.

    Allows ERB (positional and keyword), %s (positional) and %{name} (keyword) placeholders.

    Available as the #template attribute.

  • args: (Array<Object>)

    Positional arguments to interpolate into the template on #prepare.

    Available as the #args attribute.

  • assert: (Boolean)

    When true, execution will raise an error if the command doesn't exit successfully (if the command exits with any status other than 0).

    Available as the #assert attribute.

  • chdir: (nil | String | Pathname)

    Optional directory to change into when executing.

    Available as the #chdir attribute.

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

    Hash of environment variables to set when executing the command.

    Available as the #env attribute.

  • env_mode: (:inline, :spawn_arg)

    Controls how the env vars are added to the command.

    • :inline adds them to the top of the prepared string. This is nice if you want do print the command out and paste it into a terminal. This is the default.

    • :spawn_arg passes them as an argument to Process.spawn. In this case they will not be included in the output of #prepare (or #render).

    Available as the #env_mode attribute.

  • format: (nil, :squish, :pretty, #call)

    Dictates how to format the rendered template string before passing off to the shell.

    This feature lets you write templates in a more relaxed manner without \ line-endings all over the place.

    • nil performs *no formatting at all.

    • :squish reduces any consecutive whitespace (including newlines) to a single space. This is the default.

    • :pretty tries to keep the general formatting but make it acceptable to the shell by adding \ at the end of lines. See pretty_format.

    • An object that responds to #call will be called with the command string as it's only argument for custom formatting.

    See format for more details.

    Available as the #format attribute.

  • input: (nil | String | #read)

    Input to send to the command on execution. Can be a string or an IO-like object that responds to #read.

    Available as the #input attribute.

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

    Keyword arguments to shell escape and interpolate into the template on #prepare.

    Available as the #kwds attribute.



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/cmds.rb', line 229

def initialize  template, **opts
  opts = defaults opts
  
  Cmds.debug "Cmd constructing...",
    template: template,
    opts: opts

  @template = template
  
  # Assign options to instance variables
  opts.each { |key, value|
    instance_variable_set "@#{ key }", value
  }
  
  # An internal cache of the last result of calling {#prepare}, or `nil` if
  # {#prepare} has never been called. Kinda funky but ends up being useful.
  @last_prepared_cmd = nil
end

Instance Attribute Details

#argsArray<Object> (readonly)

base/common positional parameters to render into the command template.

defaults to [].

#prepare and the methods that invoke it (like #capture, #stream, etc.) accept *args, which will be appended to these values to create the final array for rendering.

Returns:

  • (Array<Object>)


52
53
54
# File 'lib/cmds.rb', line 52

def args
  @args
end

#assertBoolean (readonly)

if true, will execution will raise an error on non-zero exit code.

defaults to false.

Returns:

  • (Boolean)


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

def assert
  @assert
end

#chdirnil, String | Pathname (readonly)

Optional directory to run the command in, set by the :chdir option in #initialize.

Returns:

  • (nil)

    If the command will not change directory to run (default behavior).

  • (String | Pathname)

    If the command will change directory to run.



130
131
132
# File 'lib/cmds.rb', line 130

def chdir
  @chdir
end

#envHash{String | Symbol => String} (readonly)

Environment variables to set for command execution.

defaults to {}.

Returns:

  • (Hash{String | Symbol => String})


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

def env
  @env
end

#env_mode:inline, :spawn_arg (readonly)

How environment variables will be set for command execution - inline at the top of the command, or passed to Process.spawn as an argument.

See the inline

Returns:

  • (:inline, :spawn_arg)


105
106
107
# File 'lib/cmds.rb', line 105

def env_mode
  @env_mode
end

#format:squish | :pretty (readonly)

format specifier symbol:

  • :squish
    • collapse rendered command string to one line.
  • :pretty
    • clean up and backslash suffix line endings.

defaults to :squish.

Returns:

  • (:squish | :pretty)


118
119
120
# File 'lib/cmds.rb', line 118

def format
  @format
end

#inputString | #read (readonly)

string or readable IO-like object to use as default input to the command.

#prepare and the methods that invoke it (like #capture, #stream, etc.) accept an optional block that will override this value if present.

Returns:

  • (String | #read)


77
78
79
# File 'lib/cmds.rb', line 77

def input
  @input
end

#kwdsHash{Symbol => Object} (readonly)

base/common keyword parameters to render into the command template.

defaults to {}.

#prepare and the methods that invoke it (like #capture, #stream, etc.) accept **kwds, which will be merged on top of these values to create the final hash for rendering.

Returns:

  • (Hash{Symbol => Object})


65
66
67
# File 'lib/cmds.rb', line 65

def kwds
  @kwds
end

#last_prepared_cmdnil, String (readonly)

The results of the last time #prepare was called on the instance.

A little bit funky, I know, but it turns out to be quite useful.

Returns:

  • (nil)

    If #prepare has never been called.

  • (String)

    If #prepare has been called.



143
144
145
# File 'lib/cmds.rb', line 143

def last_prepared_cmd
  @last_prepared_cmd
end

#templateString (readonly)

ERB stirng template (with Cmds-specific extensions) for the command.

Returns:

  • (String)


38
39
40
# File 'lib/cmds.rb', line 38

def template
  @template
end

Class Method Details

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

create a new Cmds and



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

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

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

create a new Cmds 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.



65
66
67
# File 'lib/cmds/sugar.rb', line 65

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

.check_status(cmd, status, err = nil) ⇒ nil

raise an error unless the exit status is 0.

Parameters:

  • cmd (String)

    the command sting that was executed.

  • status (Fixnum)

    the command's exit status.

Returns:

  • (nil)

Raises:

  • (SystemCallError)

    if exit status is not 0.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/cmds/util.rb', line 128

def self.check_status cmd, status, err = nil
  unless status.equal? 0
    msg = NRSER.squish <<-END
      command `#{ cmd }` exited with status #{ status }
    END
    
    if err
      msg += " and stderr:\n\n" + err
    end
    
    # Remove NULL bytes (not sure how they get in there...)
    msg = msg.delete("\000")
    
    raise SystemCallError.new msg, status
  end
end

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

captures a new Cmds, 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:



159
160
161
# File 'lib/cmds/sugar.rb', line 159

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:



180
181
182
# File 'lib/cmds/sugar.rb', line 180

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.



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/cmds/util/defaults.rb', line 98

def self.defaults opts, keys = '*', extras = {}
  if keys == '*'
    DEFAULTS.dup
  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:



198
199
200
# File 'lib/cmds/sugar.rb', line 198

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)


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

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

.esc(str) ⇒ String

Shortcut for Shellwords.escape

Also makes it easier to change or customize or whatever.

Parameters:

  • str (#to_s)

Returns:

  • (String)

See Also:



38
39
40
# File 'lib/cmds/util/shell_escape.rb', line 38

def self.esc str
  Shellwords.escape str
end

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

Formats a command string.

Parameters:

  • string (String)

    Command string to format.

  • with (nil, :squish, :pretty, #call) (defaults to: :squish)

    How to format the command string.



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

def self.format string, with = :squish
  case with
  when nil
    string
    
  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 Cmds from template with parameters and call 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.

    Don't provide input here and via input keyword arg.

Returns:

  • (Result)

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



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

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

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

creates a new Cmds, 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:

  • Cmd.out


120
121
122
# File 'lib/cmds/sugar.rb', line 120

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

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

creates a new Cmds, 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:

  • Cmd.out!


141
142
143
# File 'lib/cmds/sugar.rb', line 141

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

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

create a new Cmds instance with the template and parameters and calls #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
43
44
45
46
47
48
# File 'lib/cmds/sugar.rb', line 40

def self.prepare template, *args, **kwds, &options_block
  options = if options_block
    options_block.call
  else
    {}
  end
  
  Cmds.new(template, **options).prepare *args, **kwds
end

.pretty_format(string) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/cmds/util.rb', line 63

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

.quote_dance(string, quote_type) ⇒ return_type

Format a string to be a shell token by wrapping it in either single or double quotes and replacing instances of that quote with what I'm calling a "quote dance":

  1. Closing the type of quote in use
  2. Quoting the type of quote in use with the other type of quote
  3. Then opening up the type in use again and keeping going.

WARNING: Does NOT escape anything except the quotes! So if you double-quote a string with shell-expansion terms in it and pass it to the shell THEY WILL BE EVALUATED

Examples:

Single quoting string containing single quotes


Cmds.quote_dance %{you're}, :single
# => %{'you'"'"'re'}

Double quoting string containing double quotes


Cmds.quote_dance %{hey "ho" let's go}, :double
# => %{"hey "'"'"ho"'"'" let's go"}

Parameters:

  • string (String)

    String to quote.

Returns:

  • (return_type)

    @todo Document return value.



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/cmds/util/shell_escape.rb', line 72

def self.quote_dance string, quote_type
  outside = QUOTE_TYPES.fetch quote_type
  inside = QUOTE_VALUES[QUOTE_VALUES[0] == outside ? 1 : 0]
  
  outside +
  string.gsub(
    outside,
    outside + inside + outside + inside + outside
  ) +
  outside
end

.replace_shortcuts(template) ⇒ Object



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
# File 'lib/cmds/util.rb', line 81

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

.single_quote(string) ⇒ String

Single quote a string for use in the shell.

Parameters:

  • string (String)

    String to quote.

Returns:

  • (String)

    Single-quoted string.



93
94
95
# File 'lib/cmds/util/shell_escape.rb', line 93

def self.single_quote string
  quote_dance string, :single
end

.spawn(cmd, env: {}, input: nil, **spawn_opts, &io_block) ⇒ Fixnum

Low-level static method to spawn and stream inputs and/or outputs using threads.

This is the core execution functionality of the whole library - everything ends up here.

WARNING - This method runs the cmd string AS IS - no escaping, formatting, interpolation, etc. are done at this point.

The whole rest of the library is built on top of this method to provide that stuff, and if you're using this library, you probably want to use that stuff.

You should not need to use this method directly unless you are extending the library's functionality.

Originally inspired by

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

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

At the end of the day ends up calling Process.spawn.

Parameters:

  • cmd (String)

    SHELL-READY command string. This is important - whatever you feed in here will be run AS IS - no escaping, formatting, etc.

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

    Hash of ENV vars to provide for the command.

    We convert symbol keys to strings, but other than that just pass it through to Process.spawn, which I think will #to_s everything.

    Pretty much you want to have everything be strings or symbols for this to make any sense but we're not checking shit at the moment.

    If the #env_mode is :inline it should have already prefixed cmd with the definitions and not provide this keyword (or provide {}).

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

    String or readable input, or nil (meaning no input).

    Allows Cmds instances can pass their @input instance variable.

    Don't provide input here and via io_block.

  • **spawn_opts (Hash<Symbol, Object>)

    Any additional options are passed as the options to Process.spawn

  • &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.

    Don't provide input here and via input keyword arg.

Returns:

  • (Fixnum)

    Command exit status.

Raises:

  • (ArgumentError)

    If &io_block has arity greater than 1.

  • (ArgumentError)

    If input is provided via the input keyword arg and the io_block.



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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/cmds/spawn.rb', line 116

def self.spawn  cmd,
                env: {},
                input: nil,
                **spawn_opts,
                &io_block
  Cmds.debug "entering Cmds#spawn",
    cmd: cmd,
    env: env,
    input: input,
    spawn_opts: spawn_opts,
    io_block: io_block
  
  # Process.spawn doesn't like a `nil` chdir
  if spawn_opts.key?( :chdir ) && spawn_opts[:chdir].nil?
    spawn_opts.delete :chdir
  end
  
  # 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
      
      # Check that `:input` kwd wasn't provided.
      unless input.nil?
        raise ArgumentError,
          "Don't call Cmds.spawn with `:input` keyword arg and a block"
      end
      
      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
      unless handler.in.nil?
        
        # Check that `:input` kwd wasn't provided.
        unless input.nil?
          raise ArgumentError,
            "Don't call Cmds.spawn with `:input` keyword arg and a block"
        end
        
        input = handler.in
      end
      
    else
      # bad block provided
      raise ArgumentError.new NRSER.squish <<-BLOCK
        provided input block must have arity 0 or 1
      BLOCK
    end # case io_block.arity
  end # if io_block

  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 }..."
    
    dest = handler.public_send sym
    
    # see if hanlder.out or hanlder.err is a Proc
    if dest.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: dest
      spawn_opts[sym] = dest
      # 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 NRSER.squish <<-BLOCK
              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 = NRSER.squish <<-BLOCK
      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



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

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

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



102
103
104
# File 'lib/cmds/sugar.rb', line 102

def self.stream! template, *args, **kwds, &io_block
  Cmds.new(template).stream! *args, **kwds, &io_block
end

.tokenize(*values, **opts) ⇒ String

tokenize values for the shell. each values is tokenized individually and the results are joined with a space.

Parameters:

  • *values (Array<Object>)

    values to tokenize.

Returns:

  • (String)

    tokenized string ready for the shell.



26
27
28
29
30
31
32
33
34
35
# File 'lib/cmds/util.rb', line 26

def self.tokenize *values, **opts
  values.map {|value|
    case value
    when Hash
      tokenize_options value, **opts
    else
      tokenize_value value, **opts
    end
  }.flatten.join ' '
end

.tokenize_option(name, value, **opts) ⇒ Array<String>

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

Parameters:

  • name (String)

    String name (one or more characters).

  • value (*)

    Value of the option.

  • **opts (Hash)
  • [Symbol] (Hash)

    a customizable set of options

  • [String] (Hash)

    a customizable set of options

Returns:

  • (Array<String>)

    List of individual shell token strings, escaped for use.

Raises:

  • (ArgumentError)
    1. If name is the wrong type or empty.
    2. If any options have bad values.


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
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
# File 'lib/cmds/util/tokenize_option.rb', line 43

def self.tokenize_option name, value, **opts
  # Set defaults for any options not passed
  opts = defaults opts, TOKENIZE_OPT_KEYS
  
  # Validate `name`
  unless name.is_a?(String) && name.length > 0
    raise ArgumentError.new NRSER.squish <<-END
      `name` must be a String of length greater than zero,
      found #{ name.inspect }
    END
  end
  
  # Set type (`:short` or `:long`) prefix and name/value separator depending
  # on if name is "short" (single character) or "long" (anything else)
  # 
  type, prefix, separator = if name.length == 1
    # -b <value> style (short)
    [ :short, '-', opts[:short_opt_separator] ]
  else
    # --blah=<value> style (long)
    [ :long, '--', opts[:long_opt_separator] ]
  end
  
  case value
  
  # Special cases (booleans), where we may want to emit an option name but
  # no value (depending on options)
  # 
  when true
    # `-b` or `--blah` style token
    [prefix + esc(name)]
    
  when false
    case opts[:false_mode]
    when :omit, :ignore
      # Don't emit any token for a false boolean
      []
    
    when :negate, :no
      # Emit `--no-blah` style token
      # 
      if type == :long
        # Easy one
        ["--no-#{ esc(name) }"]
      
      else
        # Short option... there seems to be little general consensus on how
        # to handle these guys; I feel like the most common is to invert the
        # case, which only makes sense for languages that have lower and
        # upper case :/
        case opts[:false_short_opt_mode]
        
        when :capitalize, :cap, :upper, :upcase
          # Capitalize the name
          # 
          # {x: false} => ["-X"]
          # 
          # This only really makes sense for lower case a-z, so raise if it's
          # not in there
          unless "a" <= name <= "z"
            raise ArgumentError.new binding.erb <<-END
              Can't negate CLI option `<%= name %>` by capitalizing name.
              
              Trying to tokenize option `<%= name %>` with `false` value and:
              
              1.  `:false_mode` is set to `<%= opts[:false_mode] %>`, which
                  tells {Cmds.tokenize_option} to emit a "negating" name with
                  no value like
                  
                      {update: false} => --no-update
                  
              2.  `:false_short_opt_mode` is set to `<%= opts[:false_short_opt_mode] %>`,
                  which means negate through capitalizing the name character,
                  like:
                  
                      {u: false} => -U
              
              3.  But this is only implemented for names in `a-z`
              
              Either change the {Cmds} instance configuration or provide a
              different CLI option name or value.
            END
          end
          
          # Emit {x: false} => ['-X'] style
          ["-#{ name.upcase }"]
        
        when :long
          # Treat it the same as a long option,
          # emit {x: false} => ['--no-x'] style
          # 
          # Yeah, I've never seen it anywhere else, but it seems reasonable I
          # guess..?
          #
          ["--no-#{ esc(name) }"]
        
        when :string
          # Use the string 'false' as a value
          [prefix + esc( name ) + separator + 'false']
        
        when String
          # It's some custom string to use
          [prefix + esc( name ) + separator + esc( string )]
          
        else
          raise ArgumentError.new binding.erb <<-END
            Bad `:false_short_opt_mode` value:
            
                <%= opts[:false_short_opt_mode].pretty_inspect %>
            
            Should be
            
            1.  :capitalize (or :cap, :upper, :upcase)
            2.  :long
            3.  :string
            4.  any String
            
          END
          
        end # case opts[:false_short_opt_mode]
      end # if :long else
    else
      raise ArgumentError.new NRSER.squish <<-END
        bad :false_mode option: #{ opts[:false_mode] },
        should be :omit or :no
      END
    end
  
  # General case
  else
    # Tokenize the value, which may
    # 
    # 1.  Result in more than one token, like when `:array_mode` is `:repeat`
    #     (in which case we want to emit multiple option tokens)
    # 
    # 2.  Result in zero tokens, like when `value` is `nil`
    #     (in which case we want to emit no option tokens)
    # 
    # and map the resulting tokens into option tokens
    # 
    tokenize_value( value, **opts ).map { |token|
      prefix + esc(name) + separator + token
    }
  
  end # case value
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, TOKENIZE_OPT_KEYS
  
  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, **opts
    
  }.flatten.join ' '
end

.tokenize_value(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)
  • [Symbol] (Hash)

    a customizable set of options

  • [String] (Hash)

    a customizable set of options

Returns:

  • (Array<String>)

    List of individual shell token strings.

Raises:

  • (ArgumentError)

    If options are set to bad values.



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
# File 'lib/cmds/util/tokenize_value.rb', line 59

def self.tokenize_value value, **opts
  opts = defaults opts, TOKENIZE_OPT_KEYS
    
  case value
  when nil
    # `nil` values produces no tokens
    []
    
  when Array
    # The PITA one...
    # 
    # May produce one or multiple tokens.
    # 
    
    # Flatten the array value if option is set
    value = value.flatten if opts[:flatten_array_values]
    
    case opts[:array_mode]
    when :repeat
      # Encode each entry as it's own token
      # 
      # [1, 2, 3] => ["1", "2", "3"]
      # 
      
      # Pass entries back through for individual tokenization and flatten
      # so we are sure to return a single-depth array
      value.map { |entry| tokenize_value entry, **opts }.flatten
      
    when :join
      # Encode all entries as one joined string token
      # 
      # [1, 2, 3] => ["1,2,3"]
      # 
      
      [esc( value.join opts[:array_join_string] )]
      
    when :json
      # Encode JSON dump as single token, single-quoted
      # 
      # [1, 2, 3] => ["'[1,2,3]'"]
      
      [single_quote( JSON.dump value )]
      
    else
      # SOL
      raise ArgumentError.new binding.erb <<-END
        Bad `:array_mode` option:
        
            <%= opts[:array_mode].pretty_inspect %>
        
        Should be :join, :repeat or :json
        
      END
      
    end # case opts[:array_mode]
  
  when Hash
    # Much the same as array
    # 
    # May produce one or multiple tokens.
    # 
    
    case opts[:hash_mode]
    when :join
      # Join the key and value using the option and pass the resulting array
      # back through to be handled as configured
      tokenize_value \
        value.map { |k, v| [k, v].join opts[:hash_join_string] },
        **opts
    
    when :json
      # Encode JSON dump as single token, single-quoted
      # 
      # [1, 2, 3] => [%{'{"a":1,"b":2,"c":3}'}]
      
      [single_quote( JSON.dump value )]
      
    else
      # SOL
      raise ArgumentError.new binding.erb <<-END
        Bad `:hash_mode` option:
        
            <%= opts[:hash_mode].pretty_inspect %>
        
        Should be :join, or :json
        
      END
    end
  
  else
    # We let {Cmds.esc} handle it, and return that as a single token
    [esc(value)]
    
  end
end

Instance Method Details

#capture(*args, **kwds, &input_block) ⇒ Cmds::Result Also known as: call

executes the command and returns a Result with the captured outputs.

Parameters:

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

  • &input_block (#call)

    optional block that returns a string or readable object to override @input.

Returns:

  • (Cmds::Result)

    result of execution with command string, status, stdout and stderr.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
# File 'lib/cmds/capture.rb', line 21

def capture *args, **kwds, &input_block
  Cmds.debug "entering Cmds#capture",
    args: args,
    kwds: kwds,
    input: input
  
  # extract input from block via `call` if one is provided,
  # otherwise default to instance variable (which may be `nil`)
  input = input_block.nil? ? input : input_block.call
  
  Cmds.debug "configured input",
    input: input
  
  # strings output will be concatenated onto
  out = ''
  err = ''

  Cmds.debug "calling Cmds.spawn..."
  
  status = spawn(*args, **kwds) do |io|
    # send the input to stream, which sends it to spawn
    io.in = input

    # and concat the output lines as they come in
    io.on_out do |line|
      out += line
    end

    io.on_err do |line|
      err += line
    end
  end
  
  Cmds.debug "Cmds.spawn completed",
    status: status

  # build a Result
  # result = Cmds::Result.new cmd, status, out_reader.value, err_reader.value
  result = Cmds::Result.new last_prepared_cmd, status, out, err

  # tell the Result to assert if the Cmds has been told to, which will
  # raise a SystemCallError with the exit status if it was non-zero
  result.assert if assert

  return result
end

#chomp(*args, **kwds, &input_block) ⇒ String

captures and chomps stdout (sugar for #out(*subs, &input_block).chomp).

Parameters:

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

  • &input_block (#call)

    optional block that returns a string or readable object to override @input.

Returns:

  • (String)

    the command's chomped stdout.

See Also:



415
416
417
# File 'lib/cmds.rb', line 415

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

#chomp!(*args, **kwds, &input) ⇒ String

captures and chomps stdout, raising an error if the command failed. (sugar for #out!(*subs, &input_block).chomp).

Parameters:

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

  • &input_block (#call)

    optional block that returns a string or readable object to override @input.

Returns:

  • (String)

    the command's chomped stdout.

Raises:

  • (SystemCallError)

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

See Also:



436
437
438
# File 'lib/cmds.rb', line 436

def chomp! *args, **kwds, &input
  out!(*args, **kwds, &input).chomp
end

#curry(*args, **kwds, &input_block) ⇒ Object

returns a new Cmds with the parameters and input merged in



257
258
259
260
261
262
263
264
265
266
267
# File 'lib/cmds.rb', line 257

def curry *args, **kwds, &input_block
  self.class.new template, {
    args: (self.args + args),
    kwds: (self.kwds.merge kwds),
    input: (input_block ? input_block.call : self.input),
    assert: self.assert,
    env: self.env,
    format: self.format,
    chdir: self.chdir,
  }
end

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

proxy through to class method defaults.



115
116
117
# File 'lib/cmds/util/defaults.rb', line 115

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

#err(*args, **kwds, &input_block) ⇒ String

captures and returns stdout (sugar for #capture(*subs, &input_block).err).

Parameters:

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

  • &input_block (#call)

    optional block that returns a string or readable object to override @input.

Returns:

  • (String)

    the command's stderr.

See Also:



454
455
456
# File 'lib/cmds.rb', line 454

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

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

execute command and return true if it failed.

Parameters:

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

  • &input_block (#call)

    optional block that returns a string or readable object to override @input.

Returns:

  • (Boolean)

    true if exit code was not 0.



355
356
357
# File 'lib/cmds.rb', line 355

def error? *args, **kwds, &io_block
  stream(*args, **kwds, &io_block) != 0
end

#ok?(*args, **kwds, &io_block) ⇒ Boolean

execute command and return true if it exited successfully.

Parameters:

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

  • &input_block (#call)

    optional block that returns a string or readable object to override @input.

Returns:

  • (Boolean)

    true if exit code was 0.



341
342
343
# File 'lib/cmds.rb', line 341

def ok? *args, **kwds, &io_block
  stream(*args, **kwds, &io_block) == 0
end

#out(*args, **kwds, &input_block) ⇒ String

captures and returns stdout (sugar for #capture(*args, **kwds, &input_block).out).

Parameters:

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

  • &input_block (#call)

    optional block that returns a string or readable object to override @input.

Returns:

  • (String)

    the command's stdout.

See Also:



380
381
382
# File 'lib/cmds.rb', line 380

def out *args, **kwds, &input_block
  capture(*args, **kwds, &input_block).out
end

#out!(*args, **kwds, &input) ⇒ String

captures and returns stdout (sugar for #capture(*args, **kwds, &input_block).out).

Parameters:

Returns:

  • (String)

    the command's stdout.

Raises:

  • (SystemCallError)

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

See Also:



398
399
400
# File 'lib/cmds.rb', line 398

def out! *args, **kwds, &input
  capture(*args, **kwds, &input).assert.out
end

#prepare(*args, **kwds) ⇒ String

prepare a shell-safe command string for execution.

Returns:

  • (String)

    the prepared command string.



321
322
323
# File 'lib/cmds.rb', line 321

def prepare *args, **kwds
  @last_prepared_cmd = Cmds.format render(*args, **kwds), self.format
end

#proxyObject



360
361
362
363
364
# File 'lib/cmds.rb', line 360

def proxy
  stream do |io|
    io.in = $stdin
  end
end

#render(*args, **kwds) ⇒ String

Note:

the returned string is not formatted for shell execution. Cmds passes this string through format before execution, which addresses newlines in the rendered string through "squishing" everything down to one line or adding \ to line ends.

render parameters into @template.

Returns:

  • (String)

    the rendered command string.



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/cmds.rb', line 283

def render *args, **kwds
  # Create the context for ERB
  context = Cmds::ERBContext.new(
    (self.args + args),
    
    self.kwds.merge( kwds ),
    
    tokenize_options_opts: TOKENIZE_OPT_KEYS.
      each_with_object( {} ) { |key, hash|
        value = instance_variable_get "@#{ key}"
        hash[key] = value unless value.nil?
      }
  )
  
  erb = Cmds::ShellEruby.new Cmds.replace_shortcuts( self.template )
  
  rendered = NRSER.dedent erb.result(context.get_binding)
  
  if self.env_mode == :inline && !self.env.empty?
    rendered = self.env.sort_by {|name, value|
      name
    }.map {|name, value|
      "#{ name }=#{ Cmds.esc value }"
    }.join("\n\n") + "\n\n" + rendered
  end
  
  rendered
end

#stream(*args, **kwds, &io_block) ⇒ Fixnum

stream a command.

Parameters:

  • &io_block (nil | String | #read)

    string or readable IO-like object to use as input to the command.

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

Returns:

  • (Fixnum)

    command exit status.



13
14
15
16
17
18
19
20
# File 'lib/cmds/stream.rb', line 13

def stream *args, **kwds, &io_block
  Cmds.debug "entering Cmds#stream",
    args: args,
    kwds: kwds,
    io_block: io_block
  
  spawn *args, **kwds, &io_block
end

#stream!(*args, **kwds, &io_block) ⇒ Object

stream and raise an error if exit code is not 0.

Parameters:

  • *args (Array<Object>)

    positional parameters to append to those in @args for rendering into the command string.

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

    keyword parameters that override those in @kwds for rendering into the command string.

  • &io_block (nil | String | #read)

    string or readable IO-like object to use as input to the command.

Raises:

  • (SystemCallError)

    if exit status is not 0.



32
33
34
35
36
37
38
# File 'lib/cmds/stream.rb', line 32

def stream! *args, **kwds, &io_block
  status = stream *args, **kwds, &io_block
  
  Cmds.check_status last_prepared_cmd, status
  
  status
end