vtparser 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b142e3d52113ff288e56ef70f3a948e71233e3dbf6dd52078c168b205c0f5c69
4
- data.tar.gz: 5a76607662cc257ad2b963d2ea8d52d86c3f7e1e5176e9b289966c611d649cb0
3
+ metadata.gz: cbb3fedd9971980bdb4389680f7ad7f52a82ec573b9bdecc3a30ecd1fb22f102
4
+ data.tar.gz: 5f12efd5ffe0f23656dcd2520f835b64815a423f9cbb7353b98eac372b0a2303
5
5
  SHA512:
6
- metadata.gz: e2861798a37ed651916d3bdd2c5a8c5e990ffc66a92c1e2b006bcfd75fe80e38288af7ca1118681a805573d2b9fb87f01bd1842f6e5232d7e599abc61d0c7255
7
- data.tar.gz: 2f1b3fa73f48e839c818c0d433d2d42edc9caa1a9afff6eb4ea6e8e13fd0453405cf364a404b9605346e47a8a625f3c7bd38babe4c9434b629714aca8d331e9b
6
+ metadata.gz: 60c450670c31b4fee417acdefab3f6ed5287351929a6afcbdda46d68d4ba72e5db76731184136d014b9c656b026649fbed0fe5811740532b5402ce7961946fc2
7
+ data.tar.gz: 2d7ec6bdb03a5ef18df04e461cc199a9fe60d4b838d39f772db14d9aec70b95cbc90f5fc2b2cf42314a9a91d0aff70e5ca48f7185de754ead3e70061acdb501d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2024-10-05
4
+
5
+ - Added more examples `colorswap.rb`, `analyze.rb`, `roundtrip.rb`
6
+ - Renamed KeyboardEvent to KeyEvent
7
+ - Added support for BEL instead of ESC to terminate OSC sequences (xterm uses this)
8
+ - Encapsulate action in class Action and added instance methods
9
+ - Fixed serialization issue for private mode CSI commands
10
+ - Added PtySupport::spawn to easily spawn a command and link stdin/out to the parser
11
+ - Switch PTY to `raw` mode to capture keys, disable echo.
12
+ - Added support for piping output to files from examples.
13
+
3
14
  ## [0.2.0] - 2024-10-03
4
15
 
5
16
  - Add keyevent handling
data/README.md CHANGED
@@ -32,10 +32,10 @@ See the minimal example below:
32
32
  require_relative '../lib/vtparser'
33
33
 
34
34
  # Instantiate the parser with a block to handle actions
35
- parser = VTParser.new do |action, ch, intermediate_chars, params|
35
+ parser = VTParser.new do |action|
36
36
 
37
37
  # For this minimal example, we'll just turn everything back strings to print
38
- print VTParser::to_ansi(action, ch, intermediate_chars, params)
38
+ print action.to_ansi
39
39
 
40
40
  end
41
41
 
@@ -50,6 +50,9 @@ Further samples in the [`examples directory`](https://github.com/coezbek/vtparse
50
50
 
51
51
  - [`echo_keys.rb`](https://github.com/coezbek/vtparser/tree/main/examples/echo_keys.rb): Echoes the keys pressed by the user
52
52
  - [`indent_cli.rb`](https://github.com/coezbek/vtparser/tree/main/examples/indent_cli.rb): Indents the output of simple command line tools
53
+ - [`colorswap.rb`](https://github.com/coezbek/vtparser/tree/main/examples/colorswap.rb): Swaps the colors red / green in the input program
54
+ - [`analyze.rb`](https://github.com/coezbek/vtparser/tree/main/examples/analyze.rb): Output all VT100 escape sequences written by the subprocess.
55
+ - [`roundtrip.rb`](https://github.com/coezbek/vtparser/tree/main/examples/roundtrip.rb): Runs the given command and compares characters written by the command to the output of running the characters through the parser and serializing the actions back `to_ansi`. If the parser works correctly the output should be the same.
53
56
 
54
57
  ## Limitations
55
58
 
@@ -59,6 +62,10 @@ Further samples in the [`examples directory`](https://github.com/coezbek/vtparse
59
62
 
60
63
  - The parser only outputs full `actions`. So triggering an event for the `ESC` key doesn't work (as expected).
61
64
 
65
+ - The parser does not emit actions for commands which are 'interrupted' by another command."
66
+
67
+ - The parser does not explain any of the actions.
68
+
62
69
  ## Development
63
70
 
64
71
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,21 @@
1
+ require_relative '../lib/vtparser'
2
+ require 'pty'
3
+ require 'tty-prompt' # for winsize call below
4
+
5
+ #
6
+ # This example demonstrates how to use the VTParser to analyze the VT100 escape sequences outputted by a program.
7
+ #
8
+
9
+ # Get the command from ARGV
10
+ command = ARGV.join(' ')
11
+ if command.empty?
12
+ puts "Usage: ruby indent_cli.rb '<command>'"
13
+ exit 1
14
+ end
15
+
16
+ # Instantiate the parser with a block to handle actions
17
+ parser = VTParser.new do |action|
18
+ puts action.inspect + "\r\n"
19
+ end
20
+
21
+ parser.spawn(command)
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # This script outputs alternating red and green text to demonstrate the color-swapping functionality.
5
+ #
6
+
7
+ # ANSI escape sequences for colors
8
+ COLOR_RED = "\e[31m"
9
+ COLOR_GREEN = "\e[32m"
10
+ RESET_COLOR = "\e[0m"
11
+
12
+ # Infinite loop to print red and green text alternately
13
+ loop do
14
+ print "\r#{COLOR_RED}This is red text#{RESET_COLOR} "
15
+ sleep 1
16
+ print "\r#{COLOR_GREEN}This is green text#{RESET_COLOR}"
17
+ sleep 1
18
+ end
@@ -0,0 +1,40 @@
1
+ require 'pty'
2
+ require 'rainbow/refinement' # for colorizing output
3
+ using Rainbow
4
+ require_relative '../lib/vtparser'
5
+
6
+ #
7
+ # 'swap_colors_cli.rb' - Example for vtparser
8
+ #
9
+ # This example demonstrates how to use VTParser to swap colors (red to green, green to red) of a simple tty program.
10
+ #
11
+ # Run with `ruby swap_colors_cli.rb <command>`, where <command> is the command you want to run.
12
+ #
13
+
14
+ # Get the command from ARGV
15
+ command = ARGV.join(' ')
16
+ if command.empty?
17
+ puts "Usage: ruby swap_colors_cli.rb '<command>'"
18
+ exit 1
19
+ end
20
+
21
+ # VT100 color codes for red and green
22
+ COLOR_RED = "\e[31m"
23
+ COLOR_GREEN = "\e[32m"
24
+ RESET_COLOR = "\e[0m"
25
+
26
+ # VTParser block to swap red and green colors in the output
27
+ parser = VTParser.new do |action, ch, intermediate_chars, params|
28
+ to_output = VTParser::to_ansi(action, ch, intermediate_chars, params)
29
+
30
+ case to_output
31
+ when COLOR_RED
32
+ print COLOR_GREEN # Swap red to green
33
+ when COLOR_GREEN
34
+ print COLOR_RED # Swap green to red
35
+ else
36
+ print to_output # Default behavior for other output
37
+ end
38
+ end
39
+
40
+ parser.spawn(command)
@@ -31,19 +31,25 @@ line_indent_length = 6
31
31
  # Use VTParser to process the VT100 escape sequences outputted by nested program and prepend the line_indent text.
32
32
  #
33
33
  first_line = true
34
- parser = VTParser.new do |action, ch, intermediate_chars, params|
34
+ parser = VTParser.new do |action|
35
+
36
+ ch = action.ch
37
+ intermediate_chars = action.intermediate_chars
38
+ params = action.params
39
+ action_type = action.action_type
40
+ to_output = action.to_ansi
41
+
35
42
  print line_indent if first_line
36
43
  first_line = false
37
44
 
38
- if $DEBUG && (action != :print || !(ch =~ /\P{Cc}/))
39
- puts "action: #{action}, ch: #{ch.inspect}, ch0x: 0x#{ "%02x" % ch.ord}, intermediate_chars: #{intermediate_chars}, params: #{params}"
45
+ if $DEBUG && (action_type != :print || !(ch =~ /\P{Cc}/))
46
+ puts action.inspect
40
47
  end
41
- to_output = VTParser::to_ansi(action, ch, intermediate_chars, params)
42
48
 
43
49
  # Handle newlines, carriage returns, and cursor movement
44
- case action
50
+ case action_type
45
51
  when :print, :execute, :put, :osc_put
46
- if ch == "\n" || ch == "\r"
52
+ if ch == "\r" # || ch == "\n"
47
53
  print ch
48
54
  print line_indent
49
55
  next
@@ -61,34 +67,61 @@ parser = VTParser.new do |action, ch, intermediate_chars, params|
61
67
  end
62
68
  end
63
69
 
70
+ if $DEBUG && (action_type != :print || !(ch =~ /\P{Cc}/))
71
+ puts "\r\n"
72
+ puts action.inspect
73
+ puts "\r\n"
74
+ # sleep 5
75
+ end
76
+
64
77
  print to_output
65
78
  end
66
79
 
67
- #
68
- # Spawn the given command using PTY::spawn, and connect pipes.
69
- #
70
80
  begin
71
81
  PTY.spawn(command) do |stdout_and_stderr, stdin, pid|
72
82
 
73
- # Start separate thread to pipe stdin to the child process
74
- Thread.new do
75
- while pid != nil
76
- stdin.write(STDIN.readpartial(1024)) # Requires user to press enter!
83
+ # Input Thread
84
+ input_thread = Thread.new do
85
+
86
+ STDIN.raw do |io|
87
+ loop do
88
+ break if pid.nil?
89
+ begin
90
+ if io.wait_readable(0.1)
91
+ data = io.read_nonblock(1024)
92
+ stdin.write data
93
+ end
94
+ rescue IO::WaitReadable
95
+ # No input available right now
96
+ rescue EOFError
97
+ break
98
+ rescue Errno::EIO
99
+ break
100
+ end
101
+ end
77
102
  end
78
- rescue => e
79
- puts "Error: #{e}"
80
- exit(0)
81
103
  end
82
104
 
83
105
  # Pipe stdout and stderr to the parser
84
106
  begin
107
+
108
+ begin
109
+ winsize = $stdout.winsize
110
+ rescue Errno::ENOTTY
111
+ winsize = [0, 120] # Default to 120 columns
112
+ end
85
113
  # Ensure the child process has the proper window size, because
86
114
  # - tools such as yarn use it to identify tty mode
87
115
  # - some tools use it to determine the width of the terminal for formatting
88
- stdout_and_stderr.winsize = [$stdout.winsize.first, $stdout.winsize.last - line_indent_length]
89
-
116
+ stdout_and_stderr.winsize = [winsize.first, winsize.last - line_indent_length]
117
+
90
118
  stdout_and_stderr.each_char do |char|
91
119
 
120
+ char = block.call(char) if block_given?
121
+ next if char.nil?
122
+
123
+ # puts Action.inspect_char(char) + "\r\n"
124
+ # Pass to parser
92
125
  parser.parse(char)
93
126
 
94
127
  end
@@ -99,13 +132,12 @@ begin
99
132
  # Wait for the child process to exit
100
133
  Process.wait(pid)
101
134
  pid = nil
102
- exit_status = $?.exitstatus
103
- # result = exit_status == 0
104
-
105
- # Clear the line, reset the cursor to the start of the line
106
- print "\e[2K\e[1G"
135
+ input_thread.join
107
136
  end
108
137
 
109
138
  rescue PTY::ChildExited => e
110
139
  puts "The child process exited: #{e}"
111
- end
140
+ end
141
+
142
+ # Clear and reset the cursor to the start of the line
143
+ puts "\e[2K\e[1G"
data/examples/progress.rb CHANGED
@@ -6,6 +6,9 @@
6
6
 
7
7
  require 'ruby-progressbar'
8
8
 
9
+ # Hide the cursor
10
+ print "\e[?25l"
11
+
9
12
  loop do
10
13
  progressbar = ProgressBar.create
11
14
  99.times {
@@ -0,0 +1,83 @@
1
+ require_relative '../lib/vtparser'
2
+ require 'pty'
3
+ require 'tty-prompt' # for winsize call below
4
+
5
+ COLOR_GREEN = "\e[32m"
6
+ RESET_COLOR = "\e[0m"
7
+
8
+ #
9
+ # This example checks if the output of VTParser is identical to the data fed into parser.
10
+ #
11
+ # Examples you can try:
12
+ #
13
+ # ruby roundtrip.rb 'vim' # Exit with :q
14
+ # ruby roundtrip.rb 'ls -la'
15
+ # ruby roundtrip.rb 'less' # Exit with q
16
+ # ruby roundtrip.rb 'top'
17
+ # Screensavers:
18
+ # ruby roundtrip.rb 'cmatrix' # sudo apt-get install cmatrix
19
+ # ruby roundtrip.rb 'neo' # install from https://github.com/st3w/neo
20
+ # Note: that upon termination a mismatch will be detected, because neo will cancel an on-going CSI sequence by sending an ESC character.
21
+ #
22
+
23
+ # Get the command from ARGV
24
+ command = ARGV.join(' ')
25
+ if command.empty?
26
+ puts "Usage: ruby indent_cli.rb '<command>'"
27
+ exit 1
28
+ end
29
+
30
+ captured = []
31
+ previous_actions = []
32
+ previous_characters = []
33
+
34
+ # Instantiate the parser with a block to handle actions
35
+ parser = VTParser.new do |action|
36
+
37
+ from_parser = action.to_ansi
38
+ from_parser.each_char.with_index do |char, i|
39
+ if captured.empty?
40
+ puts "ERROR: Parser has extra character after parsing: #{char.inspect}\r\n"
41
+ exit(1)
42
+ end
43
+ if char != captured.first
44
+ puts "\r\n"
45
+ puts "ERROR: Parser output does not match input with index #{i}: #{char.inspect} != #{captured.first.inspect}\r\n"
46
+ puts "Current captured characters: #{captured.inspect}\r\n"
47
+ puts "Current parser output: #{from_parser.inspect}\r\n"
48
+ puts "Previous characters: #{previous_characters.join.inspect}\r\n"
49
+
50
+ puts "\r\n"
51
+ previous_actions.each_with_index do |prev_action, i|
52
+ puts "Previous action -#{(previous_actions.length - i).to_s.rjust(2)}: #{prev_action.inspect}\r\n"
53
+ end
54
+ puts "Current action : #{action.inspect}\r\n"
55
+
56
+ exit(1)
57
+ end
58
+ matched_char = captured.shift
59
+ previous_characters << matched_char
60
+ if previous_characters.size > 20
61
+ previous_characters.shift
62
+ end
63
+
64
+ print "#{COLOR_GREEN}.#{RESET_COLOR}"
65
+ end
66
+
67
+ previous_actions << action
68
+ if previous_actions.size > 20
69
+ previous_actions.shift
70
+ end
71
+
72
+ end
73
+
74
+ parser.spawn(command) do |char|
75
+
76
+ captured << char
77
+
78
+ next char
79
+ end
80
+
81
+ puts
82
+ puts "#{COLOR_GREEN}SUCCESS: Parser output matches input.#{RESET_COLOR}"
83
+
@@ -99,7 +99,7 @@ module Keymap
99
99
  yield key if key
100
100
  #when :print, :ignore
101
101
  # Regular printable characters
102
- #yield KeyboardEvent.new(ch)
102
+ #yield KeyEvent.new(ch)
103
103
  when :esc_dispatch
104
104
  # ESC sequences without intermediates
105
105
  if intermediate_chars == ''
@@ -120,13 +120,13 @@ module Keymap
120
120
 
121
121
  def map_control_character(ch)
122
122
  if SINGLE_KEY_EVENT.key?(ch)
123
- return KeyboardEvent.new(SINGLE_KEY_EVENT[ch])
123
+ return KeyEvent.new(SINGLE_KEY_EVENT[ch])
124
124
  elsif ch.ord.between?(0x01, 0x1A)
125
125
  # Ctrl+A to Ctrl+Z
126
126
  key = (ch.ord + 96).chr
127
- return KeyboardEvent.new(key, :ctrl)
127
+ return KeyEvent.new(key, :ctrl)
128
128
  else
129
- return KeyboardEvent.new(ch)
129
+ return KeyEvent.new(ch)
130
130
  end
131
131
  end
132
132
 
@@ -134,14 +134,14 @@ module Keymap
134
134
  case final_char
135
135
  when 'Z'
136
136
  # Shift+Tab
137
- return KeyboardEvent.new(:tab, :shift)
137
+ return KeyEvent.new(:tab, :shift)
138
138
  when "\e"
139
139
  # Double ESC
140
- return KeyboardEvent.new(:esc)
140
+ return KeyEvent.new(:esc)
141
141
  else
142
142
  # Meta key (Alt) combinations
143
143
  if final_char.ord.between?(0x20, 0x7E)
144
- return KeyboardEvent.new(final_char, :meta)
144
+ return KeyEvent.new(final_char, :meta)
145
145
  else
146
146
  # Handle other ESC sequences if necessary
147
147
  end
@@ -168,7 +168,7 @@ module Keymap
168
168
  end
169
169
 
170
170
  if key
171
- return KeyboardEvent.new(key, *modifiers)
171
+ return KeyEvent.new(key, *modifiers)
172
172
  else
173
173
  # Handle unrecognized sequences
174
174
  end
@@ -1,14 +1,76 @@
1
1
  require_relative "keymap"
2
+ require_relative "pty_support"
3
+
4
+ class Action
5
+
6
+ attr_reader :action_type, :ch, :private_mode_intermediate_char, :intermediate_chars, :params
7
+
8
+ def initialize(action_type, ch, private_mode_intermediate_char, intermediate_chars, params)
9
+ @action_type = action_type
10
+ @ch = ch
11
+ @intermediate_chars = intermediate_chars
12
+ @private_mode_intermediate_char = private_mode_intermediate_char
13
+ @params = params
14
+ end
15
+
16
+ def to_s
17
+ to_ansi
18
+ end
19
+
20
+ def inspect
21
+ "ansi: #{to_ansi.inspect.ljust(16)} " +
22
+ "action: #{@action_type.to_s.ljust(12)} #{Action.inspect_char(@ch)} " +
23
+ "private mode: #{"'#{@private_mode_intermediate_char}'".ljust(3)} " +
24
+ "params: '#{@params.inspect.ljust(20)}' " +
25
+ "intermediate_chars: '#{@intermediate_chars}'"
26
+ end
27
+
28
+ def self.inspect_char(ch)
29
+ "ch: #{ch.inspect.ljust(4)} (ord=#{ch ? ch.ord.to_s.rjust(4) : " "}, hex=0x#{ch ? ("%02x" % ch.ord).rjust(6) : " "})"
30
+ end
31
+
32
+ def to_ansi
33
+
34
+ case @action_type
35
+ when :print, :execute, :put, :osc_put, :ignore
36
+ # Output the character
37
+ return @ch if @ch
38
+ when :hook
39
+ return "\eP#{@intermediate_chars}"
40
+ when :esc_dispatch
41
+ return "\e#{@intermediate_chars}#{@ch}"
42
+ when :csi_dispatch
43
+ # Output ESC [ followed by parameters, intermediates, and final character
44
+ return "\e[#{@private_mode_intermediate_char}#{@params.join(';')}#{@intermediate_chars}#{@ch}"
45
+ when :osc_start
46
+ return "\e]"
47
+ when :osc_end
48
+ return '' # "\x07" # BEL character to end OSC
49
+ when :unhook
50
+ return "" # \e must come from ESCAPE state
51
+ when :clear # Clear action is called when a command is interrupted by a new command (there is unfinished content in the parser)
52
+
53
+ else
54
+ raise "Unknown action type: #{@action_type}"
55
+ end
56
+
57
+ raise
58
+ end
59
+
60
+ end
61
+
2
62
 
3
63
  class VTParser
4
- attr_reader :intermediate_chars, :params
64
+ attr_reader :private_mode_intermediate_char, :intermediate_chars, :params
5
65
 
6
66
  include Keymap
67
+ include PtySupport
7
68
 
8
69
  def initialize(&block)
9
70
  @callback = block
10
71
  @state = :GROUND
11
72
  @intermediate_chars = ''
73
+ @private_mode_intermediate_char = ''
12
74
  @params = []
13
75
  @ignore_flagged = false
14
76
  initialize_states
@@ -68,7 +130,7 @@ class VTParser
68
130
  0x3a => :CSI_IGNORE,
69
131
  (0x30..0x39) => [:param, :CSI_PARAM],
70
132
  0x3b => [:param, :CSI_PARAM],
71
- (0x3c..0x3f) => [:collect, :CSI_PARAM],
133
+ (0x3c..0x3f) => [:private_mode_collect, :CSI_PARAM],
72
134
  (0x40..0x7e) => [:csi_dispatch, :GROUND],
73
135
  },
74
136
  :CSI_PARAM => {
@@ -149,7 +211,9 @@ class VTParser
149
211
  },
150
212
  :OSC_STRING => {
151
213
  :on_entry => :osc_start,
152
- (0x00..0x17) => :ignore,
214
+ (0x00..0x06) => :ignore,
215
+ (0x07) => [:osc_put, :GROUND], # BEL character for xterm compatibility
216
+ (0x08..0x17) => :ignore,
153
217
  0x19 => :ignore,
154
218
  (0x1c..0x1f) => :ignore,
155
219
  (0x20..0x7f) => :osc_put,
@@ -253,15 +317,15 @@ class VTParser
253
317
 
254
318
  def handle_action(action, ch)
255
319
  case action
256
- when :execute, :print, :esc_dispatch, :csi_dispatch, :hook, :put, :unhook, :osc_start, :osc_put, :osc_end
257
- @callback.call(action, ch, intermediate_chars, params) if @callback
258
- when :ignore
259
- # Do nothing
260
- @callback.call(action, ch, intermediate_chars, params) if @callback
320
+ when :private_mode_collect
321
+ raise "Private mode intermediate char already set" unless @private_mode_intermediate_char.empty?
322
+ @private_mode_intermediate_char = ch
323
+ return
261
324
  when :collect
262
325
  unless @ignore_flagged
263
326
  @intermediate_chars << ch
264
327
  end
328
+ return
265
329
  when :param
266
330
  if ch == ';'
267
331
  @params << 0
@@ -271,39 +335,25 @@ class VTParser
271
335
  end
272
336
  @params[-1] = @params[-1] * 10 + (ch.ord - '0'.ord)
273
337
  end
338
+ return
274
339
  when :clear
340
+
341
+ # Warning: If ESC is sent in the middle of a command, the command is cleared and there is no callback being called.
342
+
275
343
  @intermediate_chars = ''
344
+ @private_mode_intermediate_char = ''
276
345
  @params = []
277
346
  @ignore_flagged = false
278
- else
279
- @callback.call(:error, ch, intermediate_chars, params) if @callback
280
- end
281
- end
282
347
 
283
- def self.to_ansi(action, ch, intermediate_chars, params)
284
-
285
- case action
286
- when :print, :execute, :put, :osc_put, :ignore
287
- # Output the character
288
- return ch if ch
289
- when :hook
290
- return "\eP#{intermediate_chars}"
291
- when :esc_dispatch
292
- return "\e#{intermediate_chars}#{ch}"
293
- when :csi_dispatch
294
- # Output ESC [ followed by parameters, intermediates, and final character
295
- return "\e[#{params.join(';')}#{intermediate_chars}#{ch}"
296
- when :osc_start
297
- return "\e]"
298
- when :osc_end
299
- return "\x07" # BEL character to end OSC
300
- when :unhook
301
- return "" # \e must come from ESCAPE state
302
348
  else
303
- raise "Unknown action: #{action}"
304
- end
349
+ # when :execute, :print, :esc_dispatch, :csi_dispatch, :hook, :put, :unhook, :osc_start, :osc_put, :osc_end, :ignore
350
+ @callback.call(Action.new(action, ch, private_mode_intermediate_char, intermediate_chars, params)) if @callback
305
351
 
306
- raise
352
+ @intermediate_chars = ''
353
+ @private_mode_intermediate_char = ''
354
+ @params = []
355
+ @ignore_flagged = false
356
+ end
307
357
  end
308
358
 
309
359
  end
@@ -0,0 +1,81 @@
1
+ #
2
+ # This module can only be included in class which has a parse method to receive the next character read.
3
+ #
4
+ module PtySupport
5
+
6
+ #
7
+ # Spawn the given command using PTY::spawn, connect pipes and calls parse for each character read.
8
+ #
9
+ # Caution: While this command is running, STDIN will be in raw mode.
10
+ #
11
+ # If a block is given, it will be called for each character read, and the character
12
+ # will be replaced by the block's return value. If nil is returned by block, the character will be dropped.
13
+ #
14
+ def spawn(command, &block)
15
+
16
+ begin
17
+ PTY.spawn(command) do |stdout_and_stderr, stdin, pid|
18
+
19
+ # Input Thread
20
+ input_thread = Thread.new do
21
+
22
+ STDIN.raw do |io|
23
+ loop do
24
+ break if pid.nil?
25
+ begin
26
+ if io.wait_readable(0.1)
27
+ data = io.read_nonblock(1024)
28
+ stdin.write data
29
+ end
30
+ rescue IO::WaitReadable
31
+ # No input available right now
32
+ rescue EOFError
33
+ break
34
+ rescue Errno::EIO
35
+ break
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # Pipe stdout and stderr to the parser
42
+ begin
43
+
44
+ begin
45
+ winsize = $stdout.winsize
46
+ rescue Errno::ENOTTY
47
+ winsize = [0, 120] # Default to 120 columns
48
+ end
49
+ # Ensure the child process has the proper window size, because
50
+ # - tools such as yarn use it to identify tty mode
51
+ # - some tools use it to determine the width of the terminal for formatting
52
+ stdout_and_stderr.winsize = winsize
53
+
54
+ stdout_and_stderr.each_char do |char|
55
+
56
+ char = block.call(char) if block_given?
57
+ next if char.nil?
58
+
59
+ # puts Action.inspect_char(char) + "\r\n"
60
+ # Pass to parser
61
+ parse(char)
62
+
63
+ end
64
+ rescue Errno::EIO
65
+ # End of output
66
+ end
67
+
68
+ # Wait for the child process to exit
69
+ Process.wait(pid)
70
+ pid = nil
71
+ input_thread.join
72
+ return exit_status = $?.exitstatus
73
+
74
+ end
75
+
76
+ rescue PTY::ChildExited => e
77
+ puts "The child process exited: #{e}"
78
+ end
79
+ end
80
+
81
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vtparser
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vtparser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Oezbek
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-03 00:00:00.000000000 Z
11
+ date: 2024-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-prompt
@@ -90,14 +90,19 @@ files:
90
90
  - CHANGELOG.md
91
91
  - README.md
92
92
  - Rakefile
93
+ - examples/analyze.rb
94
+ - examples/colors.rb
95
+ - examples/colorswap.rb
93
96
  - examples/echo_keys.rb
94
97
  - examples/indent_cli.rb
95
98
  - examples/minimal.rb
96
99
  - examples/progress.rb
100
+ - examples/roundtrip.rb
97
101
  - examples/spinner.rb
98
102
  - lib/vtparser.rb
99
103
  - lib/vtparser/keymap.rb
100
104
  - lib/vtparser/parser.rb
105
+ - lib/vtparser/pty_support.rb
101
106
  - lib/vtparser/version.rb
102
107
  homepage: https://github.com/coezbek/vtparser
103
108
  licenses: []