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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +9 -2
- data/examples/analyze.rb +21 -0
- data/examples/colors.rb +18 -0
- data/examples/colorswap.rb +40 -0
- data/examples/indent_cli.rb +56 -24
- data/examples/progress.rb +3 -0
- data/examples/roundtrip.rb +83 -0
- data/lib/vtparser/keymap.rb +8 -8
- data/lib/vtparser/parser.rb +84 -34
- data/lib/vtparser/pty_support.rb +81 -0
- data/lib/vtparser/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbb3fedd9971980bdb4389680f7ad7f52a82ec573b9bdecc3a30ecd1fb22f102
|
4
|
+
data.tar.gz: 5f12efd5ffe0f23656dcd2520f835b64815a423f9cbb7353b98eac372b0a2303
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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.
|
data/examples/analyze.rb
ADDED
@@ -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)
|
data/examples/colors.rb
ADDED
@@ -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)
|
data/examples/indent_cli.rb
CHANGED
@@ -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
|
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 && (
|
39
|
-
puts
|
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
|
50
|
+
case action_type
|
45
51
|
when :print, :execute, :put, :osc_put
|
46
|
-
if ch == "\
|
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
|
-
#
|
74
|
-
Thread.new do
|
75
|
-
|
76
|
-
|
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 = [
|
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
|
-
|
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
@@ -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
|
+
|
data/lib/vtparser/keymap.rb
CHANGED
@@ -99,7 +99,7 @@ module Keymap
|
|
99
99
|
yield key if key
|
100
100
|
#when :print, :ignore
|
101
101
|
# Regular printable characters
|
102
|
-
#yield
|
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
|
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
|
127
|
+
return KeyEvent.new(key, :ctrl)
|
128
128
|
else
|
129
|
-
return
|
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
|
137
|
+
return KeyEvent.new(:tab, :shift)
|
138
138
|
when "\e"
|
139
139
|
# Double ESC
|
140
|
-
return
|
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
|
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
|
171
|
+
return KeyEvent.new(key, *modifiers)
|
172
172
|
else
|
173
173
|
# Handle unrecognized sequences
|
174
174
|
end
|
data/lib/vtparser/parser.rb
CHANGED
@@ -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) => [:
|
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..
|
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 :
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
-
|
304
|
-
|
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
|
-
|
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
|
data/lib/vtparser/version.rb
CHANGED
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.
|
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-
|
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: []
|