qbash 0.5.0 → 0.7.0

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 +40 -38
  3. data/README.md +14 -4
  4. data/lib/qbash.rb +71 -41
  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: 9e872574e62d1c8a6c104b3073529e89aef6bb4cbf0e72d96c464b91e82d96ee
4
- data.tar.gz: 020a400a253c7467a8d4bf5fcd7bcab4991174ba500b792e5afb2cb80214c2f7
3
+ metadata.gz: d9269eaeacc7fe858cd6e08abbcb5b66ed09b3f4bf06bbca5ea976e19cc2f125
4
+ data.tar.gz: bb0ce33d753a822af98417da51d716a5726f29df839da3c7b17bb51bdf37c9e4
5
5
  SHA512:
6
- metadata.gz: c104aba85b47d33cac8a92acf3d6c917dc40a9c813e04c65dec7c2d694860d9ae34118e3042f9d37f0fd596162b7e647074bd2c7275409b8abb0aefbd3cfa68e
7
- data.tar.gz: 76a9724d3477772a856ede1693668b665afc53b07d442e01b9380d1a6a7e90a163f1d88991a9232f9aa4b062c62e5006df5bdb805939c6d73a3b735abcca4744
6
+ metadata.gz: 3cee929894765836118f8bdbd5a505a0259586d15c5337d804806dbae2f994aa8f2f184d49e9e0d67edc42dea6d2c4b255e14a28276339f619f1635292e4b27f
7
+ data.tar.gz: e1241fe9604fd13a4c8352a56b408be3fe442c5518d13280496ca2f002e1b66579ba31911db8452a46b673dfb8b3a55a0e7911b24486c4e1b29ab9e4b6fdc07f
data/Gemfile.lock CHANGED
@@ -10,13 +10,13 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- addressable (2.8.7)
14
- public_suffix (>= 2.0.2, < 7.0)
13
+ addressable (2.8.8)
14
+ public_suffix (>= 2.0.2, < 8.0)
15
15
  ansi (1.5.0)
16
16
  ast (2.4.3)
17
17
  backtrace (0.4.1)
18
18
  base64 (0.3.0)
19
- bigdecimal (3.3.1)
19
+ bigdecimal (4.0.1)
20
20
  builder (3.3.0)
21
21
  crack (1.0.1)
22
22
  bigdecimal
@@ -33,39 +33,41 @@ GEM
33
33
  mini_mime (~> 1.1)
34
34
  multi_test (~> 1.1)
35
35
  sys-uname (~> 1.3)
36
- cucumber-ci-environment (10.0.1)
37
- cucumber-core (15.4.0)
38
- cucumber-gherkin (> 27, < 40)
39
- cucumber-messages (> 26, < 33)
40
- cucumber-tag-expressions (> 5, < 9)
41
- cucumber-cucumber-expressions (18.0.1)
36
+ cucumber-ci-environment (11.0.0)
37
+ cucumber-core (16.1.1)
38
+ cucumber-gherkin (> 36, < 40)
39
+ cucumber-messages (> 31, < 33)
40
+ cucumber-tag-expressions (> 6, < 9)
41
+ cucumber-cucumber-expressions (19.0.0)
42
42
  bigdecimal
43
- cucumber-gherkin (34.0.0)
44
- cucumber-messages (> 25, < 29)
45
- cucumber-html-formatter (21.15.1)
46
- cucumber-messages (> 19, < 28)
47
- cucumber-messages (27.2.0)
43
+ cucumber-gherkin (38.0.0)
44
+ cucumber-messages (>= 31, < 33)
45
+ cucumber-html-formatter (22.3.0)
46
+ cucumber-messages (> 23, < 33)
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)
51
51
  docile (1.4.1)
52
- elapsed (0.2.0)
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
- ffi (1.17.2-aarch64-linux-gnu)
57
- ffi (1.17.2-arm-linux-gnu)
58
- ffi (1.17.2-arm64-darwin)
59
- ffi (1.17.2-x64-mingw-ucrt)
60
- ffi (1.17.2-x86-linux-gnu)
61
- ffi (1.17.2-x86_64-darwin)
62
- ffi (1.17.2-x86_64-linux-gnu)
57
+ ffi (1.17.3-aarch64-linux-gnu)
58
+ ffi (1.17.3-arm-linux-gnu)
59
+ ffi (1.17.3-arm64-darwin)
60
+ ffi (1.17.3-x64-mingw-ucrt)
61
+ ffi (1.17.3-x86-linux-gnu)
62
+ ffi (1.17.3-x86_64-darwin)
63
+ ffi (1.17.3-x86_64-linux-gnu)
63
64
  hashdiff (1.2.1)
64
65
  json (2.18.0)
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)
@@ -79,36 +81,36 @@ GEM
79
81
  ruby-progressbar
80
82
  multi_test (1.1.0)
81
83
  net-ping (2.0.8)
82
- nokogiri (1.18.10)
84
+ nokogiri (1.19.0)
83
85
  mini_portile2 (~> 2.8.2)
84
86
  racc (~> 1.4)
85
- nokogiri (1.18.10-aarch64-linux-gnu)
87
+ nokogiri (1.19.0-aarch64-linux-gnu)
86
88
  racc (~> 1.4)
87
- nokogiri (1.18.10-arm-linux-gnu)
89
+ nokogiri (1.19.0-arm-linux-gnu)
88
90
  racc (~> 1.4)
89
- nokogiri (1.18.10-arm64-darwin)
91
+ nokogiri (1.19.0-arm64-darwin)
90
92
  racc (~> 1.4)
91
- nokogiri (1.18.10-x64-mingw-ucrt)
93
+ nokogiri (1.19.0-x64-mingw-ucrt)
92
94
  racc (~> 1.4)
93
- nokogiri (1.18.10-x86_64-darwin)
95
+ nokogiri (1.19.0-x86_64-darwin)
94
96
  racc (~> 1.4)
95
- nokogiri (1.18.10-x86_64-linux-gnu)
97
+ nokogiri (1.19.0-x86_64-linux-gnu)
96
98
  racc (~> 1.4)
97
99
  parallel (1.27.0)
98
- parser (3.3.10.0)
100
+ parser (3.3.10.1)
99
101
  ast (~> 2.4.1)
100
102
  racc
101
- prism (1.6.0)
103
+ prism (1.8.0)
102
104
  psych (5.3.1)
103
105
  date
104
106
  stringio
105
- public_suffix (6.0.2)
107
+ public_suffix (7.0.2)
106
108
  racc (1.8.1)
107
109
  rainbow (3.1.1)
108
110
  rake (13.3.1)
109
111
  random-port (0.7.6)
110
112
  tago (~> 0.0)
111
- rdoc (7.0.3)
113
+ rdoc (7.1.0)
112
114
  erb
113
115
  psych (>= 4.0.0)
114
116
  tsort
@@ -125,9 +127,9 @@ GEM
125
127
  rubocop-ast (>= 1.48.0, < 2.0)
126
128
  ruby-progressbar (~> 1.7)
127
129
  unicode-display_width (>= 2.4.0, < 4.0)
128
- rubocop-ast (1.48.0)
130
+ rubocop-ast (1.49.0)
129
131
  parser (>= 3.3.7.2)
130
- prism (~> 1.4)
132
+ prism (~> 1.7)
131
133
  rubocop-minitest (0.38.2)
132
134
  lint_roller (~> 1.1)
133
135
  rubocop (>= 1.75.0, < 2.0)
@@ -153,11 +155,11 @@ GEM
153
155
  sys-uname (1.4.1)
154
156
  ffi (~> 1.1)
155
157
  memoist3 (~> 1.0.0)
156
- tago (0.3.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)
160
- unicode-emoji (4.1.0)
162
+ unicode-emoji (4.2.0)
161
163
  w3c_validators (1.3.7)
162
164
  json (>= 1.8)
163
165
  nokogiri (~> 1.6)
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
  #
@@ -67,8 +67,30 @@ module Kernel
67
67
  # # Process will be terminated when block exits
68
68
  # end
69
69
  #
70
+ # == Changing Working Directory
71
+ #
72
+ # # Execute command in a specific directory
73
+ # files = qbash('ls -la', chdir: '/tmp')
74
+ #
75
+ # # Useful for commands that operate on the current directory
76
+ # qbash('git status', chdir: '/path/to/repo')
77
+ #
70
78
  # For command with multiple arguments, you can use +Shellwords.escape()+ to
71
- # 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)
72
94
  #
73
95
  # Read this <a href="https://github.com/yegor256/qbash">README</a> file for more details.
74
96
  #
@@ -76,87 +98,95 @@ module Kernel
76
98
  # @param [String] stdin The +stdin+ to provide to the command
77
99
  # @param [Array] opts List of bash options, like "--login" and "--noprofile"
78
100
  # @param [Hash] env Hash of environment variables
79
- # @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
80
103
  # @param [Array] accept List of accepted exit codes (accepts all if the list is +nil+)
81
104
  # @param [Boolean] both If set to TRUE, the function returns an array +(stdout, code)+
82
105
  # @param [Integer] level Logging level (use +Logger::DEBUG+, +Logger::INFO+, +Logger::WARN+, or +Logger::ERROR+)
106
+ # @param [String] chdir Directory to change to before running the command (or +nil+ to use current directory)
83
107
  # @return [String] Everything that was printed to the +stdout+ by the command
84
- def qbash(*cmd, opts: [], stdin: '', env: {}, log: Loog::NULL, accept: [0], both: false, level: Logger::DEBUG)
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
85
111
  env.each { |k, v| raise "env[#{k}] is nil" if v.nil? }
86
112
  cmd = cmd.reject { |a| a.nil? || (a.is_a?(String) && a.empty?) }.join(' ')
87
- logit =
88
- 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|
89
128
  msg = msg.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?').gsub(/\n$/, '')
90
- mtd =
91
- case level
92
- when Logger::DEBUG
93
- :debug
94
- when Logger::INFO
95
- :info
96
- when Logger::WARN
97
- :warn
98
- when Logger::ERROR
99
- :error
100
- else
101
- raise "Unknown log level #{level}"
102
- end
103
- if log.nil?
129
+ if target.nil?
104
130
  # nothing to print
105
- elsif log.respond_to?(mtd)
106
- log.__send__(mtd, msg)
131
+ elsif target.respond_to?(mtd)
132
+ target.__send__(mtd, msg)
107
133
  else
108
- log.print("#{msg}\n")
134
+ target.print("#{msg}\n")
109
135
  end
110
136
  end
111
- buf = ''
137
+ buf = +''
112
138
  e = 1
113
139
  start = Time.now
114
140
  bash = ['/bin/bash'] + opts + ['-c', cmd]
115
- Open3.popen2e(env, *bash) do |sin, sout, ctrl|
141
+ popen = chdir.nil? ? [env, *bash] : [env, *bash, { chdir: }]
142
+ Open3.send(:popen3, *popen) do |sin, sout, serr, ctrl|
116
143
  pid = ctrl.pid
117
- logit["+ #{cmd} /##{pid}"]
144
+ printer[stderr, "+ #{cmd} /##{pid}"]
118
145
  consume =
119
- lambda do
146
+ lambda do |stream, target, buffer|
120
147
  loop do
121
148
  sleep 0.001
122
- break if sout.closed? || sout.eof?
123
- ln = sout.gets # together with the \n at the end
149
+ break if stream.closed? || stream.eof?
150
+ ln = stream.gets
124
151
  next if ln.nil?
125
152
  next if ln.empty?
126
- buf += ln
127
- ln = "##{ctrl.pid}: #{ln}"
128
- logit[ln]
153
+ buffer << ln if buffer
154
+ printer[target, "##{pid}: #{ln}"]
129
155
  rescue IOError => e
130
- logit[e.message]
156
+ printer[stderr, e.message]
131
157
  break
132
158
  end
133
159
  end
134
160
  sin.write(stdin)
135
161
  sin.close
136
162
  if block_given?
137
- watch = Thread.new { consume.call }
163
+ watch = Thread.new { consume.call(sout, stdout, buf) }
138
164
  watch.abort_on_exception = true
139
165
  begin
140
166
  yield pid
141
167
  ensure
142
168
  sout.close
169
+ serr&.close
143
170
  watch.join(0.01)
144
171
  watch.kill if watch.alive?
145
172
  attempt = 1
146
173
  since = Time.now
147
174
  loop do
148
- Process.kill(0, pid) # should be dead already (raising Errno::ESRCH)
149
- Process.kill('TERM', pid) # let's try to kill it
150
- 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}"]
151
178
  sleep(0.1)
152
179
  attempt += 1
153
180
  rescue Errno::ESRCH
154
- 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
155
185
  break
156
186
  end
157
187
  end
158
188
  else
159
- consume.call
189
+ consume.call(sout, stdout, buf)
160
190
  end
161
191
  e = ctrl.value.exitstatus
162
192
  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.5.0'
12
+ s.version = '0.7.0'
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.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko