tarazed 0.2.1 → 0.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7770f7ce8eac23698c2c294317d339ac24135b86b9f8203ee43f63229c1c4d4
4
- data.tar.gz: d3aecd412f31417f961d10e07b683c07d2feeea3d5596b8d6218fde0a01e117d
3
+ metadata.gz: c41400c6ac1b8380eac0eaebe756d80425c06b4273f709e8902467276d622d80
4
+ data.tar.gz: 1a859372283bf896d16bacfd9b444ed02ad891279cdd0315cc8d072d8d7852f9
5
5
  SHA512:
6
- metadata.gz: 5c6164054701b25e94dac45e3cd5359126f591d450a9ad1c817b9e2537c3117f77286c23354cc9a1b5585d7e991ca919f29085f5fcbab1d414305e97358f0e2a
7
- data.tar.gz: 660218a61bf1017d59d90219d9beb040847ccaa8154b2f5dabdca23a0dff72d30a92e4537619a32808dec6d9ea65d64b5c67281e618f389d7978d3ab9c38d7d5
6
+ metadata.gz: 3f007338660554ed33a141db5cf1b2fe4143837828cb5dbe723e083c4f9eba82d45593eef7c8c6ba3083a67150da1353515b34a9ff055a195e50cce788b83b5a
7
+ data.tar.gz: 912b5f4857e5cb99296ecfb7666eaeeb3c853397af6b8df1a94233f79c4aac3c0c9a3f5deb035d0f86d15ff260daaaa47ffddfb530da71f1eb36eb136f096191
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.3 - 2026-09-18
6
+
7
+ - Report the executable basename from string commands that include arguments.
8
+
9
+ ## 0.2.2 - 2026-09-17
10
+
11
+ - Drain final Windows ConPTY output to EOF after the primary process exits without racing resize or close.
12
+
5
13
  ## 0.2.1 - 2026-09-16
6
14
 
7
15
  - Report and cache natural Windows ConPTY process exit status through the cross-platform `PTY#status` API.
data/lib/tarazed/pty.rb CHANGED
@@ -14,7 +14,16 @@ module Tarazed
14
14
  end
15
15
 
16
16
  @initial_cwd = File.expand_path(cwd)
17
- @command_name = File.basename(Array(command).first.to_s)
17
+ executable = if command.is_a?(Array)
18
+ command.first
19
+ elsif Gem.win_platform?
20
+ value = command.to_s.lstrip
21
+ value.start_with?('"') ? value[/\A"([^"]*)"/, 1] : value.split(/\s/, 2).first
22
+ else
23
+ Shellwords.split(command.to_s).first
24
+ end
25
+ executable = executable.to_s.tr("\\", "/") if Gem.win_platform?
26
+ @command_name = File.basename(executable.to_s)
18
27
  @grid = Grid.new(columns: columns, rows: rows, scrollback: scrollback)
19
28
  if Gem.win_platform?
20
29
  require_relative "windows/conpty"
@@ -8,7 +8,7 @@ module Tarazed
8
8
  module_function
9
9
 
10
10
  def path(shell)
11
- name = File.basename(shell.to_s).to_sym
11
+ name = File.basename(shell.to_s).downcase.delete_suffix(".exe").to_sym
12
12
  file = NAMES.fetch(name) { raise ArgumentError, "unsupported shell: #{shell}" }
13
13
  File.expand_path("../../assets/shell-integration/#{file}", __dir__)
14
14
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tarazed
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
@@ -22,7 +22,10 @@ module Tarazed
22
22
  P, I, U, N, V = Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_UINT, Fiddle::TYPE_SIZE_T,
23
23
  Fiddle::TYPE_VOID
24
24
  STILL_ACTIVE = 259
25
- private_constant :P, :I, :U, :N, :V
25
+ WAIT_OBJECT_0 = 0
26
+ WAIT_TIMEOUT = 258
27
+ EXIT_POLL_MILLISECONDS = 10
28
+ private_constant :P, :I, :U, :N, :V, :WAIT_OBJECT_0, :WAIT_TIMEOUT, :EXIT_POLL_MILLISECONDS
26
29
 
27
30
  class Status
28
31
  attr_reader :exitstatus
@@ -41,6 +44,7 @@ module Tarazed
41
44
  attr_reader :pid
42
45
 
43
46
  def initialize(command: nil, columns: 80, rows: 24, cwd: nil, env: {}, kernel: nil)
47
+ @console_lock, @lifecycle_lock = Mutex.new, Mutex.new
44
48
  raise Error, "ConPTY requires 64-bit Windows Ruby" unless Fiddle::SIZEOF_VOIDP == 8
45
49
 
46
50
  @kernel = kernel || Library.new("kernel32.dll")
@@ -55,6 +59,7 @@ module Tarazed
55
59
  attributes = process_attributes(@console)
56
60
  @process, @pid = spawn(command, cwd, env, attributes)
57
61
  start_reader
62
+ start_exit_watcher
58
63
  rescue Fiddle::DLError, LoadError => error
59
64
  release
60
65
  raise Error, "ConPTY is unavailable: #{error.message}"
@@ -116,7 +121,12 @@ module Tarazed
116
121
  end
117
122
 
118
123
  def resize(columns, rows)
119
- check_hresult(function(:ResizePseudoConsole, [P, I], I).call(@console, coordinate(columns, rows)))
124
+ size = coordinate(columns, rows)
125
+ @console_lock.synchronize do
126
+ return unless @console
127
+
128
+ check_hresult(function(:ResizePseudoConsole, [P, I], I).call(@console, size))
129
+ end
120
130
  end
121
131
 
122
132
  def status
@@ -127,17 +137,20 @@ module Tarazed
127
137
  def alive? = !@closed && !status
128
138
 
129
139
  def close
130
- return self if @closed
131
-
132
- safely { refresh_status }
133
- @closed = true
134
- close_handle(@input_write)
135
- @input_write = nil
136
- close_console
137
- @reader&.join(2)
138
- close_handle(@output_read)
139
- close_handle(@process)
140
- @output_read = @process = nil
140
+ @lifecycle_lock.synchronize do
141
+ return self if @closed
142
+
143
+ safely { refresh_status }
144
+ @closed = true
145
+ close_handle(@input_write)
146
+ @input_write = nil
147
+ close_console
148
+ @watcher&.join
149
+ @reader&.join(2)
150
+ close_handle(@output_read)
151
+ close_handle(@process)
152
+ @output_read = @process = nil
153
+ end
141
154
  self
142
155
  end
143
156
 
@@ -241,20 +254,43 @@ module Tarazed
241
254
  end
242
255
  end
243
256
 
257
+ def start_exit_watcher
258
+ wait = function(:WaitForSingleObject, [P, U], U, need_gvl: false)
259
+ @watcher = Thread.new do
260
+ loop do
261
+ result = wait.call(@process, EXIT_POLL_MILLISECONDS)
262
+ break if @closed
263
+ if result == WAIT_OBJECT_0
264
+ close_console
265
+ break
266
+ end
267
+ raise SystemCallError.new("WaitForSingleObject", Fiddle.last_error) unless result == WAIT_TIMEOUT
268
+ end
269
+ end
270
+ end
271
+
244
272
  def close_console
245
- return unless @console
273
+ console = @console_lock.synchronize do
274
+ value = @console
275
+ @console = nil
276
+ value
277
+ end
278
+ return unless console
246
279
 
247
- function(:ClosePseudoConsole, [P], V, need_gvl: false).call(@console)
248
- @console = nil
280
+ function(:ClosePseudoConsole, [P], V, need_gvl: false).call(console)
249
281
  end
250
282
 
251
283
  def release
252
- safely { close_handle(@input_write) }
253
- safely { close_console }
254
- @reader&.join(2)
255
- safely { close_handle(@output_read) }
256
- safely { close_handle(@process) }
257
- @input_write = @output_read = @process = nil
284
+ @lifecycle_lock.synchronize do
285
+ @closed = true
286
+ safely { close_handle(@input_write) }
287
+ safely { close_console }
288
+ @watcher&.join
289
+ @reader&.join(2)
290
+ safely { close_handle(@output_read) }
291
+ safely { close_handle(@process) }
292
+ @input_write = @output_read = @process = nil
293
+ end
258
294
  end
259
295
 
260
296
  def safely
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tarazed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yudai Takada