vtparser 0.1.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 +15 -0
- data/README.md +31 -7
- data/examples/analyze.rb +21 -0
- data/examples/colors.rb +18 -0
- data/examples/colorswap.rb +40 -0
- data/examples/echo_keys.rb +27 -0
- data/examples/indent_cli.rb +82 -23
- data/examples/minimal.rb +15 -0
- data/examples/progress.rb +19 -0
- data/examples/roundtrip.rb +83 -0
- data/examples/spinner.rb +19 -0
- data/lib/vtparser/keymap.rb +177 -0
- data/lib/vtparser/parser.rb +88 -34
- data/lib/vtparser/pty_support.rb +81 -0
- data/lib/vtparser/version.rb +1 -1
- metadata +29 -6
- data/.bash_history +0 -117
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,20 @@
|
|
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
|
+
|
14
|
+
## [0.2.0] - 2024-10-03
|
15
|
+
|
16
|
+
- Add keyevent handling
|
17
|
+
|
3
18
|
## [0.1.0] - 2024-10-01
|
4
19
|
|
5
20
|
- Initial release
|
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# VT 100 Parser Gem
|
2
2
|
|
3
|
-
This gem is a parser for VT100 terminal escape sequences. It is based on the C code from https://github.com/haberman/vtparse
|
3
|
+
This gem is a parser for VT100 terminal escape sequences. It is based on the C code from https://github.com/haberman/vtparse/ and implements the statemachine from https://www.vt100.net/emu/dec_ansi_parser.
|
4
|
+
|
5
|
+
The purpose of this Gem is to have a relatively easy way to filter/modify the output of child/sub-processes (for instance launched via `PTY::spawn`) which use animation or colors.
|
6
|
+
|
7
|
+
Uses keyboard mapping logic from https://github.com/vidarh/keyboard_map/
|
4
8
|
|
5
9
|
## Background on VT100 Escape Sequences
|
6
10
|
|
@@ -22,26 +26,46 @@ gem install vtparser
|
|
22
26
|
|
23
27
|
## Basic Usage
|
24
28
|
|
25
|
-
See the minimal example below
|
29
|
+
See the minimal example below:
|
26
30
|
|
27
31
|
```ruby
|
28
|
-
|
32
|
+
require_relative '../lib/vtparser'
|
29
33
|
|
30
34
|
# Instantiate the parser with a block to handle actions
|
31
|
-
parser = VTParser.new do |action
|
35
|
+
parser = VTParser.new do |action|
|
32
36
|
|
33
37
|
# For this minimal example, we'll just turn everything back strings to print
|
34
|
-
print
|
38
|
+
print action.to_ansi
|
35
39
|
|
36
40
|
end
|
37
41
|
|
38
|
-
# Sample input containing ANSI escape sequences
|
39
|
-
input = "\e[31mHello, \e[1mWorld!\e[0m"
|
42
|
+
# Sample input containing ANSI escape sequences (red text, bold text)
|
43
|
+
input = "\e[31mHello, \e[1mWorld!\e[0m\n"
|
40
44
|
|
41
45
|
# Parse the input
|
42
46
|
parser.parse(input)
|
43
47
|
```
|
44
48
|
|
49
|
+
Further samples in the [`examples directory`](https://github.com/coezbek/vtparser/tree/main/examples):
|
50
|
+
|
51
|
+
- [`echo_keys.rb`](https://github.com/coezbek/vtparser/tree/main/examples/echo_keys.rb): Echoes the keys pressed by the user
|
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.
|
56
|
+
|
57
|
+
## Limitations
|
58
|
+
|
59
|
+
- The parser is based on the implementation https://github.com/haberman/vtparse/ and based on a state machine which precedes Unicode. As such it does not have state transitions for Unicode characters. Rather, it will output them as `:ignore` actions. In case unicode characters are used inside escape sequences, the parser will likely not be able to handle them correctly.
|
60
|
+
|
61
|
+
- The state machine does not expose all input characters to the implementation in relationship to the `DSC` (Device Control String) sequences. In particular the "Final Character" is swallowed by the statemachine from https://www.vt100.net/emu/dec_ansi_parser. To circumvent this limitation, I have modified the parser to expose the final character as intermediate_chars to the `:hook` action.
|
62
|
+
|
63
|
+
- The parser only outputs full `actions`. So triggering an event for the `ESC` key doesn't work (as expected).
|
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
|
+
|
45
69
|
## Development
|
46
70
|
|
47
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)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
require_relative '../lib/vtparser'
|
3
|
+
|
4
|
+
#
|
5
|
+
# Example for how to switch to raw mode to output infos about each keypress
|
6
|
+
#
|
7
|
+
STDIN.raw do |io|
|
8
|
+
|
9
|
+
parser = VTParser.new do |action, ch, intermediate_chars, params|
|
10
|
+
|
11
|
+
puts " New VTParser action: #{action}, ch: #{ch.inspect}, ch0x: #{ch.ord.to_s(16)}, intermediate_chars: #{intermediate_chars}, params: #{params}\r\n"
|
12
|
+
|
13
|
+
parser.to_key(action, ch, intermediate_chars, params) do |event|
|
14
|
+
|
15
|
+
puts " Keyevent: #{event.to_sym.inspect} #{event.inspect}\r\n"
|
16
|
+
exit(1) if event.to_sym == :ctrl_c
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
loop do
|
22
|
+
ch = $stdin.getch
|
23
|
+
|
24
|
+
puts "Getch: #{ch.inspect}\r\n"
|
25
|
+
parser.parse ch
|
26
|
+
end
|
27
|
+
end
|
data/examples/indent_cli.rb
CHANGED
@@ -4,6 +4,19 @@ require 'rainbow/refinement' # for colorizing output
|
|
4
4
|
using Rainbow
|
5
5
|
require_relative '../lib/vtparser'
|
6
6
|
|
7
|
+
#
|
8
|
+
# 'indent_cli.rb' - Example for vtparser
|
9
|
+
#
|
10
|
+
# This example demonstrates how to use the VTParser to indent the output of simple (!) tty programs
|
11
|
+
# with colorized or animated output.
|
12
|
+
#
|
13
|
+
# Run with `ruby indent_cli.rb <command>`` where <command> is the command you want to run.
|
14
|
+
#
|
15
|
+
# Two simple examples are included:
|
16
|
+
# - A simple spinner animation is included in `examples/spinner.rb`: `ruby indent_cli.rb 'ruby spinner.rb'`
|
17
|
+
# - A simple progress bar animation is included in `examples/progress.rb`: `ruby indent_cli.rb 'ruby progress.rb'`
|
18
|
+
#
|
19
|
+
|
7
20
|
# Get the command from ARGV
|
8
21
|
command = ARGV.join(' ')
|
9
22
|
if command.empty?
|
@@ -12,56 +25,103 @@ if command.empty?
|
|
12
25
|
end
|
13
26
|
|
14
27
|
line_indent = ' ▐ '.yellow
|
28
|
+
line_indent_length = 6
|
29
|
+
|
30
|
+
#
|
31
|
+
# Use VTParser to process the VT100 escape sequences outputted by nested program and prepend the line_indent text.
|
32
|
+
#
|
15
33
|
first_line = true
|
16
|
-
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
|
+
|
17
42
|
print line_indent if first_line
|
18
43
|
first_line = false
|
19
44
|
|
20
|
-
|
45
|
+
if $DEBUG && (action_type != :print || !(ch =~ /\P{Cc}/))
|
46
|
+
puts action.inspect
|
47
|
+
end
|
21
48
|
|
22
|
-
|
49
|
+
# Handle newlines, carriage returns, and cursor movement
|
50
|
+
case action_type
|
23
51
|
when :print, :execute, :put, :osc_put
|
24
|
-
if ch == "\
|
52
|
+
if ch == "\r" # || ch == "\n"
|
25
53
|
print ch
|
26
54
|
print line_indent
|
27
55
|
next
|
28
56
|
end
|
29
57
|
when :csi_dispatch
|
30
|
-
if to_output == "\e[2K"
|
58
|
+
if to_output == "\e[2K" # Clear line
|
31
59
|
print "\e[2K"
|
32
60
|
print line_indent
|
33
61
|
next
|
34
62
|
else
|
35
|
-
if ch == 'G'
|
36
|
-
|
37
|
-
# && parser.params.size == 1
|
38
|
-
print "\e[#{parser.params[0] + 6}G"
|
39
|
-
|
63
|
+
if ch == 'G' # Cursor movement to column
|
64
|
+
print "\e[#{parser.params[0] + line_indent_length}G"
|
40
65
|
next
|
41
66
|
end
|
42
67
|
end
|
43
68
|
end
|
44
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
|
+
|
45
77
|
print to_output
|
46
78
|
end
|
47
79
|
|
48
80
|
begin
|
49
81
|
PTY.spawn(command) do |stdout_and_stderr, stdin, pid|
|
50
82
|
|
51
|
-
Thread
|
52
|
-
|
53
|
-
|
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
|
54
102
|
end
|
55
|
-
rescue => e
|
56
|
-
puts "Error: #{e}"
|
57
|
-
exit(0)
|
58
103
|
end
|
59
104
|
|
105
|
+
# Pipe stdout and stderr to the parser
|
60
106
|
begin
|
61
|
-
stdout_and_stderr.winsize = $stdout.winsize
|
62
107
|
|
108
|
+
begin
|
109
|
+
winsize = $stdout.winsize
|
110
|
+
rescue Errno::ENOTTY
|
111
|
+
winsize = [0, 120] # Default to 120 columns
|
112
|
+
end
|
113
|
+
# Ensure the child process has the proper window size, because
|
114
|
+
# - tools such as yarn use it to identify tty mode
|
115
|
+
# - some tools use it to determine the width of the terminal for formatting
|
116
|
+
stdout_and_stderr.winsize = [winsize.first, winsize.last - line_indent_length]
|
117
|
+
|
63
118
|
stdout_and_stderr.each_char do |char|
|
64
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
|
65
125
|
parser.parse(char)
|
66
126
|
|
67
127
|
end
|
@@ -72,13 +132,12 @@ begin
|
|
72
132
|
# Wait for the child process to exit
|
73
133
|
Process.wait(pid)
|
74
134
|
pid = nil
|
75
|
-
|
76
|
-
result = exit_status == 0
|
77
|
-
|
78
|
-
# Clear the line, reset the cursor to the start of the line
|
79
|
-
print "\e[2K\e[1G"
|
135
|
+
input_thread.join
|
80
136
|
end
|
81
137
|
|
82
138
|
rescue PTY::ChildExited => e
|
83
139
|
puts "The child process exited: #{e}"
|
84
|
-
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Clear and reset the cursor to the start of the line
|
143
|
+
puts "\e[2K\e[1G"
|
data/examples/minimal.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative '../lib/vtparser'
|
2
|
+
|
3
|
+
# Instantiate the parser with a block to handle actions
|
4
|
+
parser = VTParser.new do |action, ch, intermediate_chars, params|
|
5
|
+
|
6
|
+
# For this minimal example, we'll just turn everything back strings to print
|
7
|
+
print VTParser::to_ansi(action, ch, intermediate_chars, params)
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
# Sample input containing ANSI escape sequences (red text, bold text)
|
12
|
+
input = "\e[31mHello, \e[1mWorld!\e[0m\n"
|
13
|
+
|
14
|
+
# Parse the input
|
15
|
+
parser.parse(input)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Helper script to be used to demonstrate `indent_cli.rb`. It displays a full-width animated progress bar.
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'ruby-progressbar'
|
8
|
+
|
9
|
+
# Hide the cursor
|
10
|
+
print "\e[?25l"
|
11
|
+
|
12
|
+
loop do
|
13
|
+
progressbar = ProgressBar.create
|
14
|
+
99.times {
|
15
|
+
progressbar.increment
|
16
|
+
sleep 0.02
|
17
|
+
}
|
18
|
+
print "\r"
|
19
|
+
end
|
@@ -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/examples/spinner.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Helper script to be used to demonstrate `indent_cli.rb`
|
5
|
+
#
|
6
|
+
|
7
|
+
# See: https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json
|
8
|
+
frames = [
|
9
|
+
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"
|
10
|
+
]
|
11
|
+
interval = 0.08 # 80 milliseconds
|
12
|
+
|
13
|
+
# Infinite loop to display the animation until interrupted
|
14
|
+
loop do
|
15
|
+
frames.each do |frame|
|
16
|
+
print "\r #{frame} " # "\r" moves the cursor back to the start of the line
|
17
|
+
sleep(interval)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Logic taken from vidarh/keyboard_map gem
|
5
|
+
#
|
6
|
+
# https://github.com/vidarh/keyboard_map
|
7
|
+
#
|
8
|
+
# See examples/keymap.rb for usage
|
9
|
+
#
|
10
|
+
|
11
|
+
class KeyEvent
|
12
|
+
attr_reader :modifiers, :key, :args
|
13
|
+
|
14
|
+
def initialize(key, *modifiers, args: nil)
|
15
|
+
@key = key
|
16
|
+
@args = args
|
17
|
+
@modifiers = modifiers.map(&:to_sym).to_set
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
(modifiers.to_a.sort << key).join('_')
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_sym
|
25
|
+
to_s.to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
case other
|
30
|
+
when KeyEvent
|
31
|
+
self.modifiers == other.modifiers && self.key == other.key
|
32
|
+
when Symbol
|
33
|
+
self.to_sym == other
|
34
|
+
else
|
35
|
+
self.to_s == other
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Keymap
|
41
|
+
|
42
|
+
SINGLE_KEY_EVENT = {
|
43
|
+
"\t" => :tab,
|
44
|
+
"\r" => :enter,
|
45
|
+
"\n" => :enter,
|
46
|
+
"\u007F" => :backspace
|
47
|
+
}.freeze
|
48
|
+
|
49
|
+
CSI_BASIC_MAP = {
|
50
|
+
"A" => :up,
|
51
|
+
"B" => :down,
|
52
|
+
"C" => :right,
|
53
|
+
"D" => :left,
|
54
|
+
"E" => :keypad_5,
|
55
|
+
"F" => :end,
|
56
|
+
"H" => :home,
|
57
|
+
}.freeze
|
58
|
+
|
59
|
+
CSI_TILDE_MAP = {
|
60
|
+
"1" => :home,
|
61
|
+
"2" => :insert,
|
62
|
+
"3" => :delete,
|
63
|
+
"4" => :end,
|
64
|
+
"5" => :page_up,
|
65
|
+
"6" => :page_down,
|
66
|
+
"15" => :f5,
|
67
|
+
"17" => :f6,
|
68
|
+
"18" => :f7,
|
69
|
+
"19" => :f8,
|
70
|
+
"20" => :f9,
|
71
|
+
"21" => :f10,
|
72
|
+
"23" => :f11,
|
73
|
+
"24" => :f12,
|
74
|
+
}.freeze
|
75
|
+
|
76
|
+
SS3_KEY_MAP = {
|
77
|
+
"P" => :f1,
|
78
|
+
"Q" => :f2,
|
79
|
+
"R" => :f3,
|
80
|
+
"S" => :f4,
|
81
|
+
}.freeze
|
82
|
+
|
83
|
+
def map_modifiers(mod)
|
84
|
+
return [] if mod.nil? || mod < 2
|
85
|
+
modifiers = []
|
86
|
+
mod = mod - 1 # Subtract 1 to align with modifier bits
|
87
|
+
modifiers << :shift if mod & 1 != 0
|
88
|
+
modifiers << :alt if mod & 2 != 0
|
89
|
+
modifiers << :ctrl if mod & 4 != 0
|
90
|
+
modifiers
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_key(action, ch, intermediate_chars, params, &block)
|
94
|
+
|
95
|
+
case action
|
96
|
+
when :execute, :print, :ignore
|
97
|
+
# Control characters (e.g., Ctrl+C)
|
98
|
+
key = map_control_character(ch)
|
99
|
+
yield key if key
|
100
|
+
#when :print, :ignore
|
101
|
+
# Regular printable characters
|
102
|
+
#yield KeyEvent.new(ch)
|
103
|
+
when :esc_dispatch
|
104
|
+
# ESC sequences without intermediates
|
105
|
+
if intermediate_chars == ''
|
106
|
+
key = process_esc_sequence(ch)
|
107
|
+
yield key if key
|
108
|
+
else
|
109
|
+
# Handle other ESC sequences if necessary
|
110
|
+
end
|
111
|
+
when :csi_dispatch
|
112
|
+
key = process_csi_sequence(params, intermediate_chars, ch)
|
113
|
+
yield key if key
|
114
|
+
when :collect, :param, :clear
|
115
|
+
# Handled internally; no action needed here
|
116
|
+
else
|
117
|
+
# Handle other actions if necessary
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def map_control_character(ch)
|
122
|
+
if SINGLE_KEY_EVENT.key?(ch)
|
123
|
+
return KeyEvent.new(SINGLE_KEY_EVENT[ch])
|
124
|
+
elsif ch.ord.between?(0x01, 0x1A)
|
125
|
+
# Ctrl+A to Ctrl+Z
|
126
|
+
key = (ch.ord + 96).chr
|
127
|
+
return KeyEvent.new(key, :ctrl)
|
128
|
+
else
|
129
|
+
return KeyEvent.new(ch)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def process_esc_sequence(final_char)
|
134
|
+
case final_char
|
135
|
+
when 'Z'
|
136
|
+
# Shift+Tab
|
137
|
+
return KeyEvent.new(:tab, :shift)
|
138
|
+
when "\e"
|
139
|
+
# Double ESC
|
140
|
+
return KeyEvent.new(:esc)
|
141
|
+
else
|
142
|
+
# Meta key (Alt) combinations
|
143
|
+
if final_char.ord.between?(0x20, 0x7E)
|
144
|
+
return KeyEvent.new(final_char, :meta)
|
145
|
+
else
|
146
|
+
# Handle other ESC sequences if necessary
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def process_csi_sequence(params, intermediate_chars, final_char)
|
152
|
+
key = nil
|
153
|
+
modifiers = []
|
154
|
+
params = params.map(&:to_i)
|
155
|
+
|
156
|
+
if intermediate_chars == ''
|
157
|
+
if final_char == '~'
|
158
|
+
# Sequences like ESC [ 1 ~
|
159
|
+
key = CSI_TILDE_MAP[params[0].to_s]
|
160
|
+
modifiers = map_modifiers(params[1]) if params.size > 1
|
161
|
+
else
|
162
|
+
# Sequences like ESC [ A
|
163
|
+
key = CSI_BASIC_MAP[final_char]
|
164
|
+
modifiers = map_modifiers(params[0]) if params.size > 0
|
165
|
+
end
|
166
|
+
else
|
167
|
+
# Handle intermediates if necessary
|
168
|
+
end
|
169
|
+
|
170
|
+
if key
|
171
|
+
return KeyEvent.new(key, *modifiers)
|
172
|
+
else
|
173
|
+
# Handle unrecognized sequences
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
data/lib/vtparser/parser.rb
CHANGED
@@ -1,10 +1,76 @@
|
|
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
|
+
|
62
|
+
|
1
63
|
class VTParser
|
2
|
-
attr_reader :intermediate_chars, :params
|
64
|
+
attr_reader :private_mode_intermediate_char, :intermediate_chars, :params
|
65
|
+
|
66
|
+
include Keymap
|
67
|
+
include PtySupport
|
3
68
|
|
4
69
|
def initialize(&block)
|
5
70
|
@callback = block
|
6
71
|
@state = :GROUND
|
7
72
|
@intermediate_chars = ''
|
73
|
+
@private_mode_intermediate_char = ''
|
8
74
|
@params = []
|
9
75
|
@ignore_flagged = false
|
10
76
|
initialize_states
|
@@ -64,7 +130,7 @@ class VTParser
|
|
64
130
|
0x3a => :CSI_IGNORE,
|
65
131
|
(0x30..0x39) => [:param, :CSI_PARAM],
|
66
132
|
0x3b => [:param, :CSI_PARAM],
|
67
|
-
(0x3c..0x3f) => [:
|
133
|
+
(0x3c..0x3f) => [:private_mode_collect, :CSI_PARAM],
|
68
134
|
(0x40..0x7e) => [:csi_dispatch, :GROUND],
|
69
135
|
},
|
70
136
|
:CSI_PARAM => {
|
@@ -145,7 +211,9 @@ class VTParser
|
|
145
211
|
},
|
146
212
|
:OSC_STRING => {
|
147
213
|
:on_entry => :osc_start,
|
148
|
-
(0x00..
|
214
|
+
(0x00..0x06) => :ignore,
|
215
|
+
(0x07) => [:osc_put, :GROUND], # BEL character for xterm compatibility
|
216
|
+
(0x08..0x17) => :ignore,
|
149
217
|
0x19 => :ignore,
|
150
218
|
(0x1c..0x1f) => :ignore,
|
151
219
|
(0x20..0x7f) => :osc_put,
|
@@ -249,15 +317,15 @@ class VTParser
|
|
249
317
|
|
250
318
|
def handle_action(action, ch)
|
251
319
|
case action
|
252
|
-
when :
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
@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
|
257
324
|
when :collect
|
258
325
|
unless @ignore_flagged
|
259
326
|
@intermediate_chars << ch
|
260
327
|
end
|
328
|
+
return
|
261
329
|
when :param
|
262
330
|
if ch == ';'
|
263
331
|
@params << 0
|
@@ -267,39 +335,25 @@ class VTParser
|
|
267
335
|
end
|
268
336
|
@params[-1] = @params[-1] * 10 + (ch.ord - '0'.ord)
|
269
337
|
end
|
338
|
+
return
|
270
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
|
+
|
271
343
|
@intermediate_chars = ''
|
344
|
+
@private_mode_intermediate_char = ''
|
272
345
|
@params = []
|
273
346
|
@ignore_flagged = false
|
274
|
-
else
|
275
|
-
@callback.call(:error, ch, intermediate_chars, params) if @callback
|
276
|
-
end
|
277
|
-
end
|
278
347
|
|
279
|
-
def self.to_ansi(action, ch, intermediate_chars, params)
|
280
|
-
|
281
|
-
case action
|
282
|
-
when :print, :execute, :put, :osc_put, :ignore
|
283
|
-
# Output the character
|
284
|
-
return ch if ch
|
285
|
-
when :hook
|
286
|
-
return "\eP#{intermediate_chars}"
|
287
|
-
when :esc_dispatch
|
288
|
-
return "\e#{intermediate_chars}#{ch}"
|
289
|
-
when :csi_dispatch
|
290
|
-
# Output ESC [ followed by parameters, intermediates, and final character
|
291
|
-
return "\e[#{params.join(';')}#{intermediate_chars}#{ch}"
|
292
|
-
when :osc_start
|
293
|
-
return "\e]"
|
294
|
-
when :osc_end
|
295
|
-
return "\x07" # BEL character to end OSC
|
296
|
-
when :unhook
|
297
|
-
return "" # \e must come from ESCAPE state
|
298
348
|
else
|
299
|
-
|
300
|
-
|
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
|
301
351
|
|
302
|
-
|
352
|
+
@intermediate_chars = ''
|
353
|
+
@private_mode_intermediate_char = ''
|
354
|
+
@params = []
|
355
|
+
@ignore_flagged = false
|
356
|
+
end
|
303
357
|
end
|
304
358
|
|
305
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
|
@@ -58,14 +58,28 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.40
|
61
|
+
version: '0.40'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.40
|
68
|
+
version: '0.40'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: ruby-progressbar
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
description: A pure Ruby VT100 parser that can be used to parse ANSI escape sequences.
|
70
84
|
email:
|
71
85
|
- c.oezbek@gmail.com
|
@@ -73,13 +87,22 @@ executables: []
|
|
73
87
|
extensions: []
|
74
88
|
extra_rdoc_files: []
|
75
89
|
files:
|
76
|
-
- ".bash_history"
|
77
90
|
- CHANGELOG.md
|
78
91
|
- README.md
|
79
92
|
- Rakefile
|
93
|
+
- examples/analyze.rb
|
94
|
+
- examples/colors.rb
|
95
|
+
- examples/colorswap.rb
|
96
|
+
- examples/echo_keys.rb
|
80
97
|
- examples/indent_cli.rb
|
98
|
+
- examples/minimal.rb
|
99
|
+
- examples/progress.rb
|
100
|
+
- examples/roundtrip.rb
|
101
|
+
- examples/spinner.rb
|
81
102
|
- lib/vtparser.rb
|
103
|
+
- lib/vtparser/keymap.rb
|
82
104
|
- lib/vtparser/parser.rb
|
105
|
+
- lib/vtparser/pty_support.rb
|
83
106
|
- lib/vtparser/version.rb
|
84
107
|
homepage: https://github.com/coezbek/vtparser
|
85
108
|
licenses: []
|
@@ -103,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
126
|
- !ruby/object:Gem::Version
|
104
127
|
version: '0'
|
105
128
|
requirements: []
|
106
|
-
rubygems_version: 3.
|
129
|
+
rubygems_version: 3.5.20
|
107
130
|
signing_key:
|
108
131
|
specification_version: 4
|
109
132
|
summary: Pure Ruby VT100 parser
|
data/.bash_history
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
rspec
|
2
|
-
rspec
|
3
|
-
rspec
|
4
|
-
rspec
|
5
|
-
rspec
|
6
|
-
rspec
|
7
|
-
rspec
|
8
|
-
rspec
|
9
|
-
rspec
|
10
|
-
rspec
|
11
|
-
rspec ./spec/vtparser_spec.rb:153
|
12
|
-
rspec ./spec/vtparser_spec.rb:153
|
13
|
-
rspec ./spec/vtparser_spec.rb:153
|
14
|
-
rspec ./spec/vtparser_spec.rb:153
|
15
|
-
rspec ./spec/vtparser_spec.rb:153
|
16
|
-
rspec ./spec/vtparser_spec.rb:153
|
17
|
-
rspec
|
18
|
-
rspec
|
19
|
-
rspec
|
20
|
-
rspec ./spec/vtparser_spec.rb:37
|
21
|
-
rspec ./spec/vtparser_spec.rb:37
|
22
|
-
rspec
|
23
|
-
rspec
|
24
|
-
rspec expect(output).to eq(input)
|
25
|
-
rspec ./spec/vtparser_spec.rb:156
|
26
|
-
rspec ./spec/vtparser_spec.rb:156
|
27
|
-
rspec ./spec/vtparser_spec.rb:156
|
28
|
-
rspec ./spec/vtparser_spec.rb:156
|
29
|
-
rspec
|
30
|
-
rspec
|
31
|
-
D
|
32
|
-
rspec ./spec/vtparser_spec.rb
|
33
|
-
rspec ./spec/vtparser_spec.rb:137
|
34
|
-
rspec ./spec/vtparser_spec.rb:137
|
35
|
-
rspec ./spec/vtparser_spec.rb:137
|
36
|
-
rspec ./spec/vtparser_spec.rb:137
|
37
|
-
rspec ./spec/vtparser_spec.rb:137
|
38
|
-
rspec ./spec/vtparser_spec.rb:137
|
39
|
-
rspec ./spec/vtparser_spec.rb:137
|
40
|
-
rspec ./spec/vtparser_spec.rb:137
|
41
|
-
rspec ./spec/vtparser_spec.rb
|
42
|
-
rspec ./spec/vtparser_spec.rb
|
43
|
-
rspec ./spec/vtparser_spec.rb
|
44
|
-
bundle
|
45
|
-
rspec ./spec/vtparser_spec.rb
|
46
|
-
rspec ./spec/vtparser_spec.rb
|
47
|
-
ruby lib/vtparser.rb 'yarn add --dev esbuild from "."'
|
48
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
49
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
50
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
51
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
52
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
53
|
-
bundle
|
54
|
-
bundle
|
55
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
56
|
-
irb
|
57
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
58
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
59
|
-
ruby lib/vtparser/parser.rb 'yarn add --dev esbuild from "."'
|
60
|
-
ruby lib/vtparser/parser.rb 'joe'
|
61
|
-
joe
|
62
|
-
ruby lib/vtparser/parser.rb 'vim'
|
63
|
-
vim
|
64
|
-
ruby example/indent_cli.rb 'vim'
|
65
|
-
ruby examples/indent_cli.rb 'vim'
|
66
|
-
ruby examples/indent_cli.rb 'vim'
|
67
|
-
ruby examples/indent_cli.rb 'vim'
|
68
|
-
ruby examples/indent_cli.rb 'joe'
|
69
|
-
ruby examples/indent_cli.rb 'joe'
|
70
|
-
ruby examples/indent_cli.rb 'joe'
|
71
|
-
lsss
|
72
|
-
less
|
73
|
-
less README.md
|
74
|
-
ls
|
75
|
-
exit
|
76
|
-
ls
|
77
|
-
ruby examples/indent_cli.rb 'less'
|
78
|
-
ruby examples/indent_cli.rb 'less README.md'
|
79
|
-
ruby examples/indent_cli.rb 'less README.md'
|
80
|
-
ruby examples/indent_cli.rb 'less README.md'
|
81
|
-
ruby examples/indent_cli.rb 'less README.md'
|
82
|
-
ruby examples/indent_cli.rb 'less README.md'
|
83
|
-
ruby examples/indent_cli.rb 'less README.md'
|
84
|
-
ruby examples/indent_cli.rb 'less README.md'
|
85
|
-
ruby examples/indent_cli.rb 'less README.md'
|
86
|
-
ruby examples/indent_cli.rb 'less README.md'
|
87
|
-
ruby examples/indent_cli.rb 'less README.md'
|
88
|
-
ruby examples/indent_cli.rb 'less README.md'
|
89
|
-
ruby examples/indent_cli.rb 'less README.md'
|
90
|
-
ruby examples/indent_cli.rb 'less README.md'
|
91
|
-
ruby examples/indent_cli.rb 'less README.md'
|
92
|
-
less README.md
|
93
|
-
ruby examples/indent_cli.rb 'less README.md'
|
94
|
-
ruby examples/indent_cli.rb 'less README.md'
|
95
|
-
ruby examples/indent_cli.rb 'less README.md'
|
96
|
-
ruby examples/indent_cli.rb 'less README.md'
|
97
|
-
ruby examples/indent_cli.rb 'less README.md'
|
98
|
-
ruby examples/indent_cli.rb 'less README.md'
|
99
|
-
ruby examples/indent_cli.rb 'less README.md'
|
100
|
-
ruby examples/indent_cli.rb 'less README.md'
|
101
|
-
ruby examples/indent_cli.rb 'less README.md'
|
102
|
-
ruby examples/indent_cli.rb 'less README.md'
|
103
|
-
ruby examples/test.rb
|
104
|
-
kkllllijjaaa
|
105
|
-
ruby examples/test.rb
|
106
|
-
ruby examples/test.rb
|
107
|
-
ruby examples/test.rb
|
108
|
-
ruby examples/test.rb
|
109
|
-
ruby examples/test.rb
|
110
|
-
ruby examples/test.rb
|
111
|
-
ruby examples/test.rb
|
112
|
-
ruby examples/test.rb
|
113
|
-
ruby examples/test.rb
|
114
|
-
ruby examples/test.rb
|
115
|
-
ruby examples/indent_cli.rb
|
116
|
-
ruby examples/indent_cli.rb 'bundle gem test17'
|
117
|
-
ruby examples/indent_cli.rb 'bundle gem test18'
|