Class: Cmds
- Inherits:
-
Object
- Object
- Cmds
- Defined in:
- lib/cmds/util.rb,
lib/cmds.rb,
lib/cmds/pipe.rb,
lib/cmds/debug.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
Overview
debug logging stuff
Defined Under Namespace
Modules: Debug Classes: ERBContext, IOHandler, Pipe, Result, ShellEruby
Constant Summary collapse
- VERSION =
"0.0.6"
Instance Attribute Summary collapse
-
#args ⇒ Object
readonly
Returns the value of attribute args.
-
#assert ⇒ Object
readonly
Returns the value of attribute assert.
-
#input ⇒ Object
readonly
Returns the value of attribute input.
-
#kwds ⇒ Object
readonly
Returns the value of attribute kwds.
-
#template ⇒ Object
readonly
Returns the value of attribute template.
Class Method Summary collapse
- .assert(template, *subs, &input_block) ⇒ Object
-
.capture(template, *subs, &input_block) ⇒ Result
create a new Cmd from template and subs and call it.
-
.debug(msg, values = {}) ⇒ Object
log a debug message along with an optional hash of values.
- .error?(template, *subs, &input_block) ⇒ Boolean
-
.esc(str) ⇒ Object
shortcut for Shellwords.escape.
-
.expand_option_hash(hash) ⇒ Object
escape option hash.
-
.expand_sub(sub) ⇒ Object
expand one of the substitutions.
- .ok?(template, *subs, &input_block) ⇒ Boolean
-
.options(subs, input_block) ⇒ Object
::sub.
-
.replace_shortcuts(template) ⇒ Object
::options.
- .stream(template, *subs, &input_block) ⇒ Object
- .stream!(template, *subs, &input_block) ⇒ Object
-
.sub(template, args = [], kwds = {}) ⇒ String
substitute values into a command template, escaping them for the shell and offering convenient expansions for some structures.
Instance Method Summary collapse
-
#call ⇒ Object
instance methods ================.
-
#capture(*subs, &input_block) ⇒ Object
invokes the command and returns a Result with the captured outputs.
-
#curry(*subs, &input_block) ⇒ Object
returns a new
Cmdswith the subs and input block merged in. - #error? ⇒ Boolean
-
#initialize(template, opts = {}) ⇒ Cmds
constructor
A new instance of Cmds.
- #ok? ⇒ Boolean
-
#proxy ⇒ Object
def assert capture.raise_error end.
-
#stream(*subs, &input_block) ⇒ Object
stream inputs and/or outputs.
Constructor Details
#initialize(template, opts = {}) ⇒ Cmds
25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/cmds.rb', line 25 def initialize template, opts = {} Cmds.debug "Cmds constructed", template: template, options: opts @template = template @args = opts[:args] || [] @kwds = opts[:kwds] || {} @input = opts[:input] || nil @assert = opts[:assert] || false end |
Instance Attribute Details
#args ⇒ Object (readonly)
Returns the value of attribute args.
23 24 25 |
# File 'lib/cmds.rb', line 23 def args @args end |
#assert ⇒ Object (readonly)
Returns the value of attribute assert.
23 24 25 |
# File 'lib/cmds.rb', line 23 def assert @assert end |
#input ⇒ Object (readonly)
Returns the value of attribute input.
23 24 25 |
# File 'lib/cmds.rb', line 23 def input @input end |
#kwds ⇒ Object (readonly)
Returns the value of attribute kwds.
23 24 25 |
# File 'lib/cmds.rb', line 23 def kwds @kwds end |
#template ⇒ Object (readonly)
Returns the value of attribute template.
23 24 25 |
# File 'lib/cmds.rb', line 23 def template @template end |
Class Method Details
.assert(template, *subs, &input_block) ⇒ Object
39 40 41 42 43 44 |
# File 'lib/cmds/sugar.rb', line 39 def self.assert template, *subs, &input_block new( template, (subs, input_block).merge!(assert: true) ).capture end |
.capture(template, *subs, &input_block) ⇒ Result
create a new Cmd from template and subs and call it
27 28 29 |
# File 'lib/cmds/sugar.rb', line 27 def self.capture template, *subs, &input_block new(template, (subs, input_block)).capture 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 |
.error?(template, *subs, &input_block) ⇒ Boolean
35 36 37 |
# File 'lib/cmds/sugar.rb', line 35 def self.error? template, *subs, &input_block new(template, (subs, input_block)).error? end |
.esc(str) ⇒ Object
shortcut for Shellwords.escape
also makes it easier to change or customize or whatever
9 10 11 |
# File 'lib/cmds/util.rb', line 9 def self.esc str Shellwords.escape str end |
.expand_option_hash(hash) ⇒ 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.
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 |
# File 'lib/cmds/util.rb', line 41 def self. hash hash.map {|key, values| # keys need to be strings key = key.to_s unless key.is_a? String [key, values] }.sort {|(key_a, values_a), (key_b, values_b)| # sort by the (now string) keys key_a <=> key_b }.map {|key, values| # for simplicity's sake, treat all values like an array values = [values] unless values.is_a? Array # keys of length 1 expand to `-x v` form = if key.length == 1 values.map {|value| if value.nil? "-#{ esc key }" else "-#{ esc key } #{ esc value}" end } # longer keys expand to `--key=value` form else values.map {|value| if value.nil? "--#{ esc key }" else "--#{ esc key }=#{ esc value }" end } end }.flatten.join ' ' end |
.expand_sub(sub) ⇒ Object
expand one of the substitutions
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/cmds/util.rb', line 80 def self. sub case sub when nil # nil is just an empty string, NOT an empty string bash token '' when Hash sub else esc sub.to_s end end |
.ok?(template, *subs, &input_block) ⇒ Boolean
31 32 33 |
# File 'lib/cmds/sugar.rb', line 31 def self.ok? template, *subs, &input_block new(template, (subs, input_block)).ok? end |
.options(subs, input_block) ⇒ Object
::sub
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 |
# File 'lib/cmds/util.rb', line 137 def self. subs, input_block args = [] kwds = {} input = input_block.nil? ? nil : input_block.call case subs.length when 0 # nothing to do when 1 # can either be a hash, which is interpreted as a keywords, # or an array, which is interpreted as positional arguments case subs[0] when Hash kwds = subs[0] when Array args = subs[0] else raise TypeError.new NRSER.squish " first *subs arg must be Array or Hash, not \#{ subs[0].inspect }\n BLOCK\n end\n\n when 2\n # first arg needs to be an array, second a hash\n unless subs[0].is_a? Array\n raise TypeError.new NRSER.squish <<-BLOCK\n first *subs arg needs to be an array, not \#{ subs[0].inspect }\n BLOCK\n end\n\n unless subs[1].is_a? Hash\n raise TypeError.new NRSER.squish <<-BLOCK\n second *subs arg needs to be a Hash, not \#{ subs[1].inspect }\n BLOCK\n end\n\n args, kwds = subs\n else\n raise ArgumentError.new NRSER.squish <<-BLOCK\n must provide one or two *subs arguments, received \#{ 1 + subs.length }\n BLOCK\n end\n\n return {\n args: args,\n kwds: kwds,\n input: input,\n }\nend\n" |
.replace_shortcuts(template) ⇒ Object
::options
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 |
# File 'lib/cmds/util.rb', line 189 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 |
.stream(template, *subs, &input_block) ⇒ Object
46 47 48 |
# File 'lib/cmds/sugar.rb', line 46 def self.stream template, *subs, &input_block Cmds.new(template).stream *subs, &input_block end |
.stream!(template, *subs, &input_block) ⇒ Object
50 51 52 |
# File 'lib/cmds/sugar.rb', line 50 def self.stream! template, *subs, &input_block Cmds.new(template, assert: true).stream *subs, &input_block end |
.sub(template, args = [], kwds = {}) ⇒ String
substitute values into a command template, escaping them for the shell and offering convenient expansions for some structures. uses ERB templating, so logic is supported as well.
check out the README for details on use.
127 128 129 130 131 132 133 134 135 |
# File 'lib/cmds/util.rb', line 127 def self.sub template, args = [], kwds = {} raise TypeError.new("args must be an Array") unless args.is_a? Array raise TypeError.new("kwds must be an Hash") unless kwds.is_a? Hash context = ERBContext.new(args, kwds) erb = ShellEruby.new replace_shortcuts(template) NRSER.squish erb.result(context.get_binding) end |
Instance Method Details
#call ⇒ Object
instance methods
57 |
# File 'lib/cmds/sugar.rb', line 57 alias_method :call, :capture |
#capture(*subs, &input_block) ⇒ Object
invokes the command and returns a Result with the captured outputs
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 |
# File 'lib/cmds/capture.rb', line 3 def capture *subs, &input_block Cmds.debug "entering Cmds#capture", subs: subs, input_block: input_block # merge any stored args and kwds and replace input if provided = subs, input_block Cmds.debug "merged options:", options: # build the command string cmd = Cmds.sub @template, [:args], [:kwds] Cmds.debug "built command string: #{ cmd.inspect }" out = '' err = '' Cmds.debug "calling Cmds#really_stream..." status = really_stream cmd, 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#really_stream completed", status: status # build a Result # result = Cmds::Result.new cmd, status, out_reader.value, err_reader.value result = Cmds::Result.new 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 |
#curry(*subs, &input_block) ⇒ Object
returns a new Cmds with the subs and input block merged in
227 228 229 |
# File 'lib/cmds/util.rb', line 227 def curry *subs, &input_block self.class.new @template, (subs, input_block) end |
#error? ⇒ Boolean
63 64 65 |
# File 'lib/cmds/sugar.rb', line 63 def error? stream != 0 end |
#ok? ⇒ Boolean
59 60 61 |
# File 'lib/cmds/sugar.rb', line 59 def ok? stream == 0 end |
#proxy ⇒ Object
def assert capture.raise_error end
71 72 73 74 75 |
# File 'lib/cmds/sugar.rb', line 71 def proxy stream do |io| io.in = $stdin end end |
#stream(*subs, &input_block) ⇒ Object
stream inputs and/or outputs
originally inspired by
https://nickcharlton.net/posts/ruby-subprocesses-with-stdout-stderr-streams.html
with major modifications from looking at Ruby's open3 module.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/cmds/stream.rb', line 10 def stream *subs, &input_block Cmds.debug "entering Cmds#stream", subs: subs, input_block: input_block # use `merge_options` to get the args and kwds (we will take custom # care of input in _stream) = subs, nil # build the command string cmd = Cmds.sub @template, [:args], [:kwds] # call the internal function really_stream cmd, , &input_block end |