qbash 0.6.0 → 0.7.1

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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +8 -6
  3. data/README.md +14 -4
  4. data/lib/qbash.rb +64 -42
  5. data/qbash.gemspec +1 -1
  6. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76514b64336276e5a7a6f930cdde69aa8f92a368385e661b8b930fcf56b74269
4
- data.tar.gz: 551650cc2ab252c771d99b6cf7a1e57a432d24b13b660df5c80549e1156a12c1
3
+ metadata.gz: d7f1f277669b1692b1cb45029b2bfc954bc4aef15f50597eb2317b6487f064d1
4
+ data.tar.gz: 3bbdb5a60c288b6058b04bdadf622f8dcd0081604987ddcfa62b0e0b872d02a3
5
5
  SHA512:
6
- metadata.gz: b0b790b282910bc8730657f264042caddaf3d960933be65ef5a9541277ca8d0f7e0d1f5371643eadee77f3f9b367f67db744ad99a6edbdf2a89c55b9ec0e63df
7
- data.tar.gz: 904d66cdf396205812115e1f45d90a903fe65fa9bdc9bc66c62f410beb521261f3dd830f87831996dc650ade478d0a7601e29ccb390d7dc3b29dd1b2a156a86c
6
+ metadata.gz: 8f4c268dc6631f3907ca914711208debd8acaf089fb21a5bd957f18ecb4e3a494f0c57152af6a081803b854bc39344b94fe814cf44ed13ccaf0183f25ff5f4ea
7
+ data.tar.gz: 4daa2853fbbcda067f829106648f72528183d885f0e8dba68d2be3710171d50115aaeadb539e7aea489e813704e6962fd4addd3a03259024beba4f4fc06decf5
data/Gemfile.lock CHANGED
@@ -38,13 +38,13 @@ GEM
38
38
  cucumber-gherkin (> 36, < 40)
39
39
  cucumber-messages (> 31, < 33)
40
40
  cucumber-tag-expressions (> 6, < 9)
41
- cucumber-cucumber-expressions (18.0.1)
41
+ cucumber-cucumber-expressions (19.0.0)
42
42
  bigdecimal
43
- cucumber-gherkin (37.0.1)
44
- cucumber-messages (>= 31, < 32)
43
+ cucumber-gherkin (38.0.0)
44
+ cucumber-messages (>= 31, < 33)
45
45
  cucumber-html-formatter (22.3.0)
46
46
  cucumber-messages (> 23, < 33)
47
- cucumber-messages (31.2.0)
47
+ cucumber-messages (32.0.1)
48
48
  cucumber-tag-expressions (8.1.0)
49
49
  date (3.5.1)
50
50
  diff-lcs (1.6.2)
@@ -52,6 +52,7 @@ GEM
52
52
  elapsed (0.2.2)
53
53
  loog (~> 0.6)
54
54
  tago (~> 0.1)
55
+ ellipsized (0.3.0)
55
56
  erb (6.0.1)
56
57
  ffi (1.17.3-aarch64-linux-gnu)
57
58
  ffi (1.17.3-arm-linux-gnu)
@@ -65,7 +66,8 @@ GEM
65
66
  language_server-protocol (3.17.0.5)
66
67
  lint_roller (1.1.0)
67
68
  logger (1.7.0)
68
- loog (0.6.1)
69
+ loog (0.7.2)
70
+ ellipsized
69
71
  logger (~> 1.0)
70
72
  memoist3 (1.0.0)
71
73
  mini_mime (1.1.5)
@@ -153,7 +155,7 @@ GEM
153
155
  sys-uname (1.4.1)
154
156
  ffi (~> 1.1)
155
157
  memoist3 (~> 1.0.0)
156
- tago (0.6.0)
158
+ tago (0.7.0)
157
159
  tsort (0.2.0)
158
160
  unicode-display_width (3.2.0)
159
161
  unicode-emoji (~> 4.1)
data/README.md CHANGED
@@ -28,13 +28,23 @@ Then, you can use [qbash] global function:
28
28
 
29
29
  ```ruby
30
30
  require 'qbash'
31
- stdout = qbash('echo "Hello, world!"', log: $stdout)
31
+ stdout = qbash('echo "Hello, world!"')
32
32
  ```
33
33
 
34
34
  If the command fails, an exception is raised.
35
35
 
36
- The function automatically merges `stderr` with `stdout`
37
- (you can't change this).
36
+ By default, `stderr` merges with the `stdout` logger.
37
+ You can redirect it elsewhere:
38
+
39
+ ```ruby
40
+ # Redirect stderr to a separate logger
41
+ out = Loog::Buffer.new
42
+ err = Loog::Buffer.new
43
+ qbash('cmd', stdout: out, stderr: err)
44
+
45
+ # Discard stderr completely
46
+ qbash('cmd', stderr: nil)
47
+ ```
38
48
 
39
49
  It's possible to provide the standard input and environment variables:
40
50
 
@@ -48,7 +58,7 @@ It's possible to configure the logging facility too, for example,
48
58
 
49
59
  ```ruby
50
60
  require 'loog'
51
- qbash('echo "Hello, world!"', log: Loog::VERBOSE)
61
+ qbash('echo "Hello, world!"', stdout: Loog::VERBOSE)
52
62
  ```
53
63
 
54
64
  You can also make it return both stdout and exit code,
data/lib/qbash.rb CHANGED
@@ -51,12 +51,12 @@ module Kernel
51
51
  #
52
52
  # == Logging
53
53
  #
54
- # # Enable detailed logging to stdout
55
- # qbash('ls -la', log: $stdout)
54
+ # # Enable detailed logging to console
55
+ # qbash('ls -la', stdout: $stdout)
56
56
  #
57
57
  # # Use custom logger with specific level
58
58
  # logger = Logger.new($stdout)
59
- # qbash('make all', log: logger, level: Logger::INFO)
59
+ # qbash('make all', stdout: logger, level: Logger::INFO)
60
60
  #
61
61
  # == Process Control
62
62
  #
@@ -76,7 +76,21 @@ module Kernel
76
76
  # qbash('git status', chdir: '/path/to/repo')
77
77
  #
78
78
  # For command with multiple arguments, you can use +Shellwords.escape()+ to
79
- # properly escape each argument. Stderr automatically merges with stdout.
79
+ # properly escape each argument.
80
+ #
81
+ # == Stderr Handling
82
+ #
83
+ # By default, stderr merges with stdout. You can redirect it elsewhere:
84
+ #
85
+ # # Merge stderr with stdout (default)
86
+ # output = qbash('cmd', stderr: :stdout)
87
+ #
88
+ # # Redirect stderr to a separate logger
89
+ # err_log = Loog::Buffer.new
90
+ # output = qbash('cmd', stderr: err_log)
91
+ #
92
+ # # Discard stderr completely
93
+ # output = qbash('cmd', stderr: nil)
80
94
  #
81
95
  # Read this <a href="https://github.com/yegor256/qbash">README</a> file for more details.
82
96
  #
@@ -84,90 +98,98 @@ module Kernel
84
98
  # @param [String] stdin The +stdin+ to provide to the command
85
99
  # @param [Array] opts List of bash options, like "--login" and "--noprofile"
86
100
  # @param [Hash] env Hash of environment variables
87
- # @param [Loog|IO] log Logging facility with +.debug()+ method (or +$stdout+, or nil if should go to +/dev/null+)
101
+ # @param [Loog|IO] stdout Logging facility with +.debug()+ method (or +$stdout+, or nil if should go to +/dev/null+)
102
+ # @param [Loog|IO] stderr Where to send stderr
88
103
  # @param [Array] accept List of accepted exit codes (accepts all if the list is +nil+)
89
104
  # @param [Boolean] both If set to TRUE, the function returns an array +(stdout, code)+
90
105
  # @param [Integer] level Logging level (use +Logger::DEBUG+, +Logger::INFO+, +Logger::WARN+, or +Logger::ERROR+)
91
106
  # @param [String] chdir Directory to change to before running the command (or +nil+ to use current directory)
92
107
  # @return [String] Everything that was printed to the +stdout+ by the command
93
- def qbash(*cmd, opts: [], stdin: '', env: {}, log: Loog::NULL, accept: [0], both: false, level: Logger::DEBUG,
94
- chdir: nil)
108
+ def qbash(*cmd, opts: [], stdin: '', env: {}, stdout: Loog::NULL, stderr: nil, accept: [0], both: false,
109
+ level: Logger::DEBUG, chdir: nil)
110
+ stderr ||= stdout
95
111
  env.each { |k, v| raise "env[#{k}] is nil" if v.nil? }
96
112
  cmd = cmd.reject { |a| a.nil? || (a.is_a?(String) && a.empty?) }.join(' ')
97
- logit =
98
- lambda do |msg|
113
+ mtd =
114
+ case level
115
+ when Logger::DEBUG
116
+ :debug
117
+ when Logger::INFO
118
+ :info
119
+ when Logger::WARN
120
+ :warn
121
+ when Logger::ERROR
122
+ :error
123
+ else
124
+ raise "Unknown log level #{level}"
125
+ end
126
+ printer =
127
+ lambda do |target, msg|
99
128
  msg = msg.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?').gsub(/\n$/, '')
100
- mtd =
101
- case level
102
- when Logger::DEBUG
103
- :debug
104
- when Logger::INFO
105
- :info
106
- when Logger::WARN
107
- :warn
108
- when Logger::ERROR
109
- :error
110
- else
111
- raise "Unknown log level #{level}"
112
- end
113
- if log.nil?
129
+ if target.nil?
114
130
  # nothing to print
115
- elsif log.respond_to?(mtd)
116
- log.__send__(mtd, msg)
131
+ elsif target.respond_to?(mtd)
132
+ target.__send__(mtd, msg)
117
133
  else
118
- log.print("#{msg}\n")
134
+ target.print("#{msg}\n")
119
135
  end
120
136
  end
121
- buf = ''
137
+ buf = +''
122
138
  e = 1
123
139
  start = Time.now
124
140
  bash = ['/bin/bash'] + opts + ['-c', cmd]
125
141
  popen = chdir.nil? ? [env, *bash] : [env, *bash, { chdir: }]
126
- Open3.popen2e(*popen) do |sin, sout, ctrl|
142
+ Open3.send(:popen3, *popen) do |sin, sout, serr, ctrl|
127
143
  pid = ctrl.pid
128
- logit["+ #{cmd} /##{pid}"]
144
+ printer[stderr, "+ #{cmd} /##{pid}"]
129
145
  consume =
130
- lambda do
146
+ lambda do |stream, target, buffer|
131
147
  loop do
132
148
  sleep 0.001
133
- break if sout.closed? || sout.eof?
134
- ln = sout.gets # together with the \n at the end
149
+ break if stream.closed? || stream.eof?
150
+ ln = stream.gets
135
151
  next if ln.nil?
136
152
  next if ln.empty?
137
- buf += ln
138
- ln = "##{ctrl.pid}: #{ln}"
139
- logit[ln]
153
+ buffer << ln if buffer
154
+ printer[target, "##{pid}: #{ln}"]
140
155
  rescue IOError => e
141
- logit[e.message]
156
+ printer[stderr, e.message]
142
157
  break
143
158
  end
144
159
  end
145
160
  sin.write(stdin)
146
161
  sin.close
147
162
  if block_given?
148
- watch = Thread.new { consume.call }
163
+ watch = Thread.new { consume.call(sout, stdout, buf) }
149
164
  watch.abort_on_exception = true
150
165
  begin
151
166
  yield pid
152
167
  ensure
153
168
  sout.close
169
+ serr&.close
154
170
  watch.join(0.01)
155
171
  watch.kill if watch.alive?
156
172
  attempt = 1
157
173
  since = Time.now
158
174
  loop do
159
- Process.kill(0, pid) # should be dead already (raising Errno::ESRCH)
160
- Process.kill('TERM', pid) # let's try to kill it
161
- logit["Tried to stop ##{pid} with SIGTERM (attempt no.#{attempt}, #{since.ago}): #{cmd}"]
175
+ Process.kill(0, pid)
176
+ Process.kill('TERM', pid)
177
+ printer[stderr, "Tried to stop ##{pid} with SIGTERM (attempt no.#{attempt}, #{since.ago}): #{cmd}"]
162
178
  sleep(0.1)
163
179
  attempt += 1
164
180
  rescue Errno::ESRCH
165
- logit["Process ##{pid} reacted to SIGTERM, after #{attempt} attempts and #{since.ago}"] if attempt > 1
181
+ if attempt > 1
182
+ printer[stderr,
183
+ "Process ##{pid} reacted to SIGTERM, after #{attempt} attempts and #{since.ago}"]
184
+ end
166
185
  break
167
186
  end
168
187
  end
169
188
  else
170
- consume.call
189
+ thread = Thread.new { consume.call(serr, stderr, nil) }
190
+ thread.abort_on_exception = true
191
+ consume.call(sout, stdout, buf)
192
+ thread.join
171
193
  end
172
194
  e = ctrl.value.exitstatus
173
195
  if !accept.nil? && !accept.include?(e)
data/qbash.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
10
10
  s.required_ruby_version = '>=3.2'
11
11
  s.name = 'qbash'
12
- s.version = '0.6.0'
12
+ s.version = '0.7.1'
13
13
  s.license = 'MIT'
14
14
  s.summary = 'Quick Executor of a BASH Command'
15
15
  s.description =
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qbash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko