escape_code 0.2
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 +7 -0
- data/lib/escape_code.rb +9 -0
- data/lib/escape_code/code.rb +36 -0
- data/lib/escape_code/html_formatter.rb +43 -0
- data/lib/escape_code/html_formatter/color_scheme.rb +49 -0
- data/lib/escape_code/scanner.rb +29 -0
- data/lib/escape_code/sgr_command.rb +44 -0
- data/lib/escape_code/sgr_state.rb +31 -0
- data/spec/escape_code_spec.rb +17 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d91dddd55b34e7ef953ad2a19b94d433e5cb18c1
|
4
|
+
data.tar.gz: 5d85e224ed28b54e21b4104eb4f1cad42c2f925b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9c141acd971f8299d224fba4f24dfc558ec6742a10537c90771145ec0c2a03ed201cb53ed1bd395cc60633c90b02ea5d27a719b78970a4f98983b185d679ef21
|
7
|
+
data.tar.gz: 28c947714e0fb3f6c2544235c249bb41f6b21019e5166843304c1ed54aace1812f094515b48a37f17404fc799d448a658ed6ba18d80e623600f7ec595c56eb64
|
data/lib/escape_code.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
class EscapeCode::Code
|
3
|
+
REGEX = /\e\[([0-9;]*)([a-zA-Z])/
|
4
|
+
|
5
|
+
SGR = 'm'
|
6
|
+
|
7
|
+
# TODO: Support other types of escape codes, like private sequences and non-alphanumeric mode characters
|
8
|
+
attr_reader :type, :args, :sgr_commands
|
9
|
+
|
10
|
+
def initialize(type, args)
|
11
|
+
@type = type
|
12
|
+
@args = args
|
13
|
+
|
14
|
+
if sgr?
|
15
|
+
@sgr_commands ||= begin
|
16
|
+
if args == []
|
17
|
+
# SGR without an argument is equvalent to reset
|
18
|
+
[EscapeCode::SgrCommand.new(SgrCommand::RESET)]
|
19
|
+
else
|
20
|
+
args.map do |arg|
|
21
|
+
EscapeCode::SgrCommand.new(arg)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.parse(code)
|
29
|
+
raise 'not a valid escape sequence' unless code =~ REGEX
|
30
|
+
new($~[2], $~[1].split(';'))
|
31
|
+
end
|
32
|
+
|
33
|
+
def sgr?
|
34
|
+
type == SGR
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
class EscapeCode::HtmlFormatter
|
4
|
+
def initialize(text, prefix = '', color_scheme: EscapeCode::HtmlFormatter::ColorScheme.default)
|
5
|
+
@prefix = prefix
|
6
|
+
@color_scheme = color_scheme
|
7
|
+
@scanner = EscapeCode::Scanner.new(text)
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_stylesheet
|
11
|
+
@color_scheme.generate_stylesheet
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate
|
15
|
+
state = EscapeCode::SgrState.new
|
16
|
+
|
17
|
+
@scanner.scan.map do |thing|
|
18
|
+
case thing
|
19
|
+
when EscapeCode::Code
|
20
|
+
state.ingest(thing)
|
21
|
+
when String
|
22
|
+
classes = compute_classes(state)
|
23
|
+
if classes.empty?
|
24
|
+
CGI.escapeHTML(thing)
|
25
|
+
else
|
26
|
+
"<span class='#{compute_classes(state)}'>#{CGI.escapeHTML(thing)}</span>"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end.compact.join
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_page
|
33
|
+
"<html><head><style>#{generate_stylesheet}</style></head><body><pre>#{generate}</pre></body></html>"
|
34
|
+
end
|
35
|
+
|
36
|
+
private def compute_classes(state)
|
37
|
+
classes = []
|
38
|
+
classes << 'bold' if state.bold?
|
39
|
+
classes << "#{@prefix}#{state.foreground}-foreground" if state.foreground
|
40
|
+
classes << "#{@prefix}#{state.background}-background" if state.background
|
41
|
+
classes.join(' ')
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
class EscapeCode::HtmlFormatter::ColorScheme
|
3
|
+
def self.default
|
4
|
+
@default ||= new(
|
5
|
+
foreground: {
|
6
|
+
black: '#2e3436',
|
7
|
+
red: '#cc0000',
|
8
|
+
green: '#4e9a06',
|
9
|
+
yellow: '#c4a000',
|
10
|
+
blue: '#3465a4',
|
11
|
+
purple: '#75507b',
|
12
|
+
cyan: '#06989a',
|
13
|
+
white: '#d3d7cf'
|
14
|
+
},
|
15
|
+
background: {
|
16
|
+
black: '#555753',
|
17
|
+
red: '#ef2929',
|
18
|
+
green: '#8ae234',
|
19
|
+
yellow: '#fce94f',
|
20
|
+
blue: '#729fcf',
|
21
|
+
purple: '#ad7fa8',
|
22
|
+
cyan: '#34e2e2',
|
23
|
+
white: '#eeeeec'
|
24
|
+
}
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(foreground:, background: nil)
|
29
|
+
@foreground = foreground
|
30
|
+
@background = background || foreground
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_stylesheet(prefix = '')
|
34
|
+
stylesheet = []
|
35
|
+
|
36
|
+
@background.each do |color, value|
|
37
|
+
stylesheet << ".#{prefix}#{color}-background {\n background-color: #{value};\n}"
|
38
|
+
end
|
39
|
+
|
40
|
+
@foreground.each do |color, value|
|
41
|
+
stylesheet << ".#{prefix}#{color}-foreground {\n color: #{value};\n}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# TODO: Do this in a better way
|
45
|
+
stylesheet << ".#{prefix}bold {\n font-weight: bold;\n}"
|
46
|
+
|
47
|
+
stylesheet.join("\n")
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
class EscapeCode::Scanner
|
4
|
+
def initialize(string)
|
5
|
+
@string = string
|
6
|
+
end
|
7
|
+
|
8
|
+
def scan(&block)
|
9
|
+
enumerator = Enumerator.new do |y|
|
10
|
+
scanner = StringScanner.new(@string)
|
11
|
+
|
12
|
+
until scanner.eos?
|
13
|
+
if scanner.scan EscapeCode::Code::REGEX
|
14
|
+
y << EscapeCode::Code.parse(scanner.matched)
|
15
|
+
else
|
16
|
+
# TODO: don't split up strings with \e but not an actual escape sequence
|
17
|
+
y << scanner.scan(/[^\e]*/)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if block
|
23
|
+
enumerator.each(&block)
|
24
|
+
nil
|
25
|
+
else
|
26
|
+
enumerator
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
class EscapeCode::SgrCommand
|
3
|
+
RESET = '0'
|
4
|
+
BOLD = '1'
|
5
|
+
|
6
|
+
COLORS = {
|
7
|
+
'0' => :black,
|
8
|
+
'1' => :red,
|
9
|
+
'2' => :green,
|
10
|
+
'3' => :yellow,
|
11
|
+
'4' => :blue,
|
12
|
+
'5' => :purple,
|
13
|
+
'6' => :cyan,
|
14
|
+
'7' => :white
|
15
|
+
}
|
16
|
+
|
17
|
+
attr_reader :type
|
18
|
+
|
19
|
+
def initialize(type)
|
20
|
+
@type = type
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset?
|
24
|
+
type == RESET
|
25
|
+
end
|
26
|
+
|
27
|
+
def bold?
|
28
|
+
type == BOLD
|
29
|
+
end
|
30
|
+
|
31
|
+
def foreground_color?
|
32
|
+
type =~ /^3[0-7]$/
|
33
|
+
end
|
34
|
+
|
35
|
+
def background_color?
|
36
|
+
type =~ /^4[0-7]/
|
37
|
+
end
|
38
|
+
|
39
|
+
def color
|
40
|
+
return nil unless foreground_color? || background_color?
|
41
|
+
|
42
|
+
EscapeCode::SgrCommand::COLORS[type[1]]
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
class EscapeCode::SgrState
|
3
|
+
attr_reader :bold, :foreground, :background
|
4
|
+
alias_method :bold?, :bold
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@bold = false
|
8
|
+
@foreground = nil
|
9
|
+
@background = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def ingest(command)
|
13
|
+
# convenience thing to allow passing in an entire code instead of individual SGR commands
|
14
|
+
if command.is_a?(EscapeCode::Code)
|
15
|
+
command.sgr_commands.each { |c| ingest(c) } if command.sgr?
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
if command.reset?
|
20
|
+
@bold = false
|
21
|
+
@foreground = nil
|
22
|
+
@background = nil
|
23
|
+
elsif command.bold?
|
24
|
+
@bold = true
|
25
|
+
elsif command.foreground_color?
|
26
|
+
@foreground = command.color
|
27
|
+
elsif command.background_color?
|
28
|
+
@background = command.color
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'escape_code'
|
2
|
+
|
3
|
+
RSpec.describe EscapeCode do
|
4
|
+
it 'handles pathological cases' do
|
5
|
+
result = EscapeCode::HtmlFormatter.new("one \e[1mtwo\e[0m three \e[42;37mfour \e[1mfive \e[41msix\e[0m").generate
|
6
|
+
expected = "one <span class='bold'>two</span> three <span class='white-foreground green-background'>four </span><span class='bold white-foreground green-background'>five </span><span class='bold white-foreground red-background'>six</span>"
|
7
|
+
expect(result).to eq(expected)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'generates stylesheets that have both background and foreground colors' do
|
11
|
+
stylesheet = EscapeCode::HtmlFormatter::ColorScheme.default.generate_stylesheet
|
12
|
+
expect(stylesheet).to match(/ background-color:/)
|
13
|
+
expect(stylesheet).to match(/ color:/)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should probably have more specs'
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: escape_code
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Boyd
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: alex@opengroove.org
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/escape_code.rb
|
20
|
+
- lib/escape_code/code.rb
|
21
|
+
- lib/escape_code/html_formatter.rb
|
22
|
+
- lib/escape_code/html_formatter/color_scheme.rb
|
23
|
+
- lib/escape_code/scanner.rb
|
24
|
+
- lib/escape_code/sgr_command.rb
|
25
|
+
- lib/escape_code/sgr_state.rb
|
26
|
+
- spec/escape_code_spec.rb
|
27
|
+
homepage: https://github.com/javawizard/escape_code
|
28
|
+
licenses: []
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.2.2
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: ANSI escape code parsing library and ANSI -> HTML converter
|
50
|
+
test_files: []
|
51
|
+
has_rdoc:
|