termtime 1.0.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.
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "termtime/db"
4
+
5
+ module TermTime
6
+ class TermInfo
7
+ class Number
8
+ attr_reader :value, :db
9
+
10
+ def initialize v, db
11
+ @value = v
12
+ @db = db
13
+ freeze
14
+ end
15
+
16
+ def tiname; @db.tiname; end
17
+ end
18
+
19
+ class String
20
+ attr_reader :value, :db
21
+
22
+ def initialize v, db
23
+ @value = v
24
+ @db = db
25
+ freeze
26
+ end
27
+
28
+ def tiname; @db.tiname; end
29
+
30
+ def tparm(...)
31
+ TermTime.tparm(value, ...)
32
+ end
33
+ end
34
+
35
+ def initialize fname = ENV["TERM"]
36
+ unless File.file? fname
37
+ fname = "/usr/share/terminfo/#{fname.getbyte(0).to_s(16)}/#{fname}"
38
+ end
39
+
40
+ File.open fname, "rb" do |f|
41
+ magic,
42
+ names_size,
43
+ booleans_size,
44
+ short_int_count,
45
+ offset_count,
46
+ string_table_size = f.read((2 * 6)).unpack("ssssss")
47
+
48
+ raise unless magic == 0432
49
+ @names = f.read(names_size - 1).force_encoding("UTF-8").freeze
50
+ raise unless f.readbyte == 0
51
+ @flags = f.read(booleans_size - 1)
52
+
53
+ f.readbyte
54
+ f.readbyte if f.pos % 2 != 0 # alignment
55
+
56
+ @numbers = f.read(short_int_count * 2)
57
+ strings = f.read(offset_count * 2)
58
+ string_table = f.read(string_table_size)
59
+ @strings = decode_strings(strings, string_table)
60
+ end
61
+ end
62
+
63
+ def flags
64
+ flags = []
65
+ @flags.bytesize.times do |i|
66
+ if @flags.getbyte(i) > 0
67
+ flags << DB::BOOLEAN_LIST.fetch(i)
68
+ end
69
+ end
70
+ flags
71
+ end
72
+
73
+ def numbers
74
+ numbers = []
75
+ @numbers.unpack("s*").each_with_index do |v, i|
76
+ next if v == -1
77
+ numbers << Number.new(v, DB::NUMBER_LIST.fetch(i))
78
+ end
79
+ numbers
80
+ end
81
+
82
+ def strings
83
+ @strings.values
84
+ end
85
+
86
+ def getnum name
87
+ offset = DB::NUMBERS.fetch(name).i * 2
88
+ @numbers.getbyte(offset) | (@numbers.getbyte(offset + 1) << 8)
89
+ end
90
+
91
+ def getflag name
92
+ @flags.getbyte(DB::BOOLEANS.fetch(name).i) == 1
93
+ end
94
+
95
+ def getstr name
96
+ str(name).value
97
+ end
98
+
99
+ def str name
100
+ @strings.fetch(name)
101
+ end
102
+
103
+ def str? name
104
+ @strings.key?(name)
105
+ end
106
+
107
+ def names
108
+ @names.split("|")
109
+ end
110
+
111
+ private
112
+
113
+ def decode_strings str_offsets, string_table
114
+ strings = {}
115
+ str_offsets.unpack("s*").each_with_index do |v, i|
116
+ next if v == -1
117
+ next if i >= DB::STRING_LIST.length
118
+
119
+ len = string_table.byteindex("\0", v) - v
120
+ str = string_table.byteslice(v, len).freeze
121
+ x = DB::STRING_LIST.fetch(i)
122
+ strings[x.tiname] = String.new(str, x)
123
+ end
124
+ strings
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,3 @@
1
+ module TermTime
2
+ VERSION = "1.0.0"
3
+ end
data/lib/termtime.rb ADDED
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module TermTime
6
+ autoload :TermInfo, "termtime/terminfo"
7
+ autoload :DB, "termtime/db"
8
+
9
+ def self.tparm fmt, p1 = 0, p2 = 0, p3 = 0, p4 = 0, p5 = 0, p6 = 0, p7 = 0, p8 = 0, p9 = 0
10
+ return fmt unless fmt =~ /%/
11
+
12
+ regs = [p1, p2, p3, p4, p5, p6, p7, p8, p9]
13
+ stack = []
14
+
15
+ scan = StringScanner.new fmt
16
+ buff = ''.b
17
+
18
+ while !scan.eos?
19
+ start = scan.pos
20
+ case
21
+ when scan.skip(/%i/)
22
+ regs[0] += 1
23
+ regs[1] += 1
24
+ when scan.skip(/%t/)
25
+ if stack.pop == 0
26
+ scan.skip_until(/%e/)
27
+ else
28
+ end
29
+ when scan.skip(/%c/)
30
+ buff << stack.pop
31
+ when scan.skip(/%</)
32
+ y = stack.pop
33
+ x = stack.pop
34
+ stack << (x < y ? 1 : 0)
35
+ when scan.skip(/%>/)
36
+ y = stack.pop
37
+ x = stack.pop
38
+ stack << (x > y ? 1 : 0)
39
+ when scan.skip(/%\+/) then stack << (stack.pop + stack.pop)
40
+ when scan.skip(/%\*/) then stack << (stack.pop * stack.pop)
41
+ when scan.skip(/%-/)
42
+ y = stack.pop
43
+ x = stack.pop
44
+ stack << (x - y)
45
+ when scan.skip(/%\//)
46
+ y = stack.pop
47
+ x = stack.pop
48
+ stack << (x / y)
49
+ when scan.skip(/%m/)
50
+ y = stack.pop
51
+ x = stack.pop
52
+ stack << (x % y)
53
+ when scan.skip(/%!/) then stack << (stack.pop == 0 ? 1 : 0)
54
+ when scan.skip(/%=/) then stack << (stack.pop == stack.pop ? 1 : 0)
55
+ when scan.skip(/%%/) then buff << "%"
56
+ when scan.skip(/%e/)
57
+ # if we made it to %e, we must have been in the true branch,
58
+ # so skip until after %;
59
+ if !scan.skip_until(/%;/)
60
+ break;
61
+ end
62
+ when scan.skip(/%l/) then raise NotImplementedError
63
+ when scan.skip(/%&/) then stack << (stack.pop & stack.pop)
64
+ when scan.skip(/%\|/) then stack << (stack.pop | stack.pop)
65
+ when scan.skip(/%\^/) then stack << (stack.pop ^ stack.pop)
66
+ when scan.skip(/%~/) then stack << (~stack.pop)
67
+ when scan.skip(/%'.'/) then stack << fmt.getbyte(start + 2)
68
+ when scan.skip(/%A/)
69
+ stack << (stack.pop != 0 && stack.pop != 0 ? 1 : 0)
70
+ when scan.skip(/%O/)
71
+ stack << (stack.pop != 0 || stack.pop != 0 ? 1 : 0)
72
+ when scan.skip(/%[;?]/)
73
+ when scan.skip(/%{\d+}/)
74
+ stack << fmt.byteslice(start + 2, scan.pos - start - 3).to_i
75
+ when scan.skip(/%p[0-9]/)
76
+ stack.push regs[(fmt.getbyte(scan.pos - 1) - 0x30) - 1]
77
+ when str = scan.scan(/%([0-9].[0-9])?[dsoxX]/)
78
+ buff << sprintf(str, stack.pop)
79
+ when str = scan.scan(/[^%]+/)
80
+ buff << str
81
+ else
82
+ p scan
83
+ raise
84
+ end
85
+ end
86
+ buff
87
+ end
88
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # GENERATED FILE, DO NOT EDIT
4
+ # :stopdoc:
5
+ module TermTime
6
+ module DB
7
+ class Capability
8
+ attr_reader :name, :tiname, :titype, :tcname, :key_name, :value, :flags, :desc, :i
9
+
10
+ def initialize(name, tiname, titype, tcname, key_name, value, flags, desc, i)
11
+ @name = name
12
+ @tiname = tiname
13
+ @titype = titype
14
+ @tcname = tcname
15
+ @key_name = key_name
16
+ @value = value
17
+ @flags = flags
18
+ @desc = desc
19
+ @i = i
20
+ end
21
+ end
22
+ class Boolean < Capability; end
23
+ class Number < Capability; end
24
+ class String < Capability; end
25
+
26
+ BOOLEANS = {
27
+ <% info["bool"].each_with_index do |bool, i| %>
28
+ <%= bool[1].dump %> => Boolean.new(
29
+ <% bool.map(&:dump).each do |f| %>
30
+ <%= f %>,
31
+ <% end %>
32
+ <%= i %>,
33
+ ).freeze,
34
+ <% end %>
35
+ }.freeze
36
+ BOOLEAN_LIST = BOOLEANS.values
37
+
38
+ NUMBERS = {
39
+ <% info["num"].each_with_index do |bool, i| %>
40
+ <%= bool[1].dump %> => Number.new(
41
+ <% bool.map(&:dump).each do |f| %>
42
+ <%= f %>,
43
+ <% end %>
44
+ <%= i %>,
45
+ ).freeze,
46
+ <% end %>
47
+ }.freeze
48
+ NUMBER_LIST = NUMBERS.values
49
+
50
+ STRINGS = {
51
+ <% info["str"].each_with_index do |bool, i| %>
52
+ <%= bool[1].dump %> => String.new(
53
+ <% bool.map(&:dump).each do |f| %>
54
+ <%= f %>,
55
+ <% end %>
56
+ <%= i %>,
57
+ ).freeze,
58
+ <% end %>
59
+ }.freeze
60
+ STRING_LIST = STRINGS.values
61
+ end
62
+ end
63
+ # :startdoc:
data/termtime.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ require_relative "lib/termtime/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "termtime"
5
+ s.version = TermTime::VERSION
6
+ s.summary = "Pure Ruby terminfo library"
7
+ s.description = "This is a pure Ruby terminfo library"
8
+ s.authors = [ "Aaron Patterson" ]
9
+ s.email = "tenderlove@ruby-lang.org"
10
+ s.files = `git ls-files -z`.split("\x0")
11
+ s.test_files = s.files.grep(%r{^test/})
12
+ s.homepage = "https://github.com/tenderlove/termtime"
13
+ s.license = "Apache-2.0"
14
+
15
+ s.add_development_dependency("rake", "~> 13.0")
16
+ s.add_development_dependency("minitest", "~> 5.20")
17
+ end
Binary file
Binary file
Binary file
data/test/helper.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "minitest/autorun"
2
+
3
+ module TermTime
4
+ class Test < Minitest::Test
5
+ FIXTURES = File.expand_path(File.join(File.dirname(__FILE__), "fixtures"))
6
+ end
7
+ end
@@ -0,0 +1,105 @@
1
+ require "helper"
2
+ require "termtime"
3
+
4
+ module TermTime
5
+ class TermInfoTest < Test
6
+ def test_info
7
+ ti = TermInfo.new File.join(FIXTURES, "xterm-256color")
8
+ assert_equal ["xterm-256color", "xterm with 256 colors"], ti.names
9
+ assert_equal ["am", "bce", "ccc", "xenl", "km", "mir", "msgr", "npc", "mc5i"].sort, ti.flags.map(&:tiname).sort
10
+ assert ti.getflag("am")
11
+ assert ti.getflag("npc")
12
+ assert_equal ["colors", "cols", "it", "lines", "pairs"].sort, ti.numbers.map(&:tiname).sort
13
+ assert_equal [256, 80, 8, 24, 32767], ti.numbers.sort_by(&:tiname).map(&:value)
14
+
15
+ assert_equal 256, ti.getnum("colors")
16
+ assert_equal 24, ti.getnum("lines")
17
+
18
+ assert_equal "\e[H\e[2J", ti.getstr("clear")
19
+ end
20
+
21
+ def test_adm3a
22
+ ti = TermInfo.new File.join(FIXTURES, "adm3a")
23
+ assert_equal ["adm3a", "lsi adm3a"], ti.names
24
+ assert_equal ["am"], ti.flags.map(&:tiname)
25
+ assert ti.getflag("am")
26
+ refute ti.getflag("npc")
27
+
28
+ assert_equal ["cols", "lines"].sort, ti.numbers.map(&:tiname).sort
29
+ assert_equal [80, 24], ti.numbers.sort_by(&:tiname).map(&:value)
30
+
31
+ assert_equal ["bel", "clear", "cr", "cub1", "cud1", "cuf1", "cup", "cuu1", "home", "ind", "kcub1", "kcud1", "kcuf1", "kcuu1", "rs2"], ti.strings.map(&:tiname).sort
32
+
33
+ assert_equal ["\a", "\x1A$<1/>", "\r", "\b", "\n", "\f", "\e=%p1%' '%+%c%p2%' '%+%c", "\v", "\x1E", "\n", "\b", "\n", "\f", "\v", "\x0E"], ti.strings.sort_by(&:tiname).map(&:value)
34
+ end
35
+
36
+ def test_getstr_tparm
37
+ ti = TermInfo.new File.join(FIXTURES, "xterm-256color")
38
+ assert_equal "\e[2D", ti.str("cub").tparm(2)
39
+ end
40
+
41
+ def test_tparm
42
+ assert_equal "1;1", TermTime.tparm("%i%p1%d;%p2%d")
43
+ assert_equal "0;0", TermTime.tparm("%p1%d;%p2%d")
44
+ assert_equal "1;2", TermTime.tparm("%i%p1%d;%p2%d", 0, 1)
45
+ assert_equal "true", TermTime.tparm("%p1%ttrue%efalse%;", 1)
46
+ assert_equal "false", TermTime.tparm("%p1%ttrue%efalse%;", 0)
47
+ assert_equal "8", TermTime.tparm("%{8}%d")
48
+ assert_equal "%", TermTime.tparm("%%")
49
+ assert_equal [40].pack("C"), TermTime.tparm("%{40}%c")
50
+ end
51
+
52
+ def test_tparm_lt
53
+ assert_equal "less", TermTime.tparm("%?%p1%{8}%<%tless%emore%?", 0)
54
+ assert_equal "less", TermTime.tparm("%?%p1%{8}%<%tless%emore%;", 0)
55
+ assert_equal "more", TermTime.tparm("%?%p1%{8}%<%tless%emore%;", 8)
56
+ end
57
+
58
+ def test_tparm_gt
59
+ assert_equal "less", TermTime.tparm("%?%p1%{8}%>%tmore%eless%;", 0)
60
+ assert_equal "more", TermTime.tparm("%?%p1%{8}%>%tmore%eless%;", 9)
61
+ end
62
+
63
+ def test_tparm_eq
64
+ assert_equal "no", TermTime.tparm("%?%p1%{8}%=%tyes%eno%;", 0)
65
+ assert_equal "yes", TermTime.tparm("%?%p1%{8}%=%tyes%eno%;", 8)
66
+ end
67
+
68
+ def test_tparm_math
69
+ assert_equal "9", TermTime.tparm("%{8}%p1%+%d", 1)
70
+ assert_equal "7", TermTime.tparm("%{8}%p1%-%d", 1)
71
+ assert_equal "16", TermTime.tparm("%{8}%p1%*%d", 2)
72
+ assert_equal "4", TermTime.tparm("%{8}%p1%/%d", 2)
73
+ assert_equal "1", TermTime.tparm("%{9}%p1%m%d", 2)
74
+ end
75
+
76
+ def test_tparm_bits
77
+ assert_equal "1", TermTime.tparm("%{9}%p1%&%d", 1)
78
+ assert_equal "9", TermTime.tparm("%{8}%p1%|%d", 1)
79
+ assert_equal "6", TermTime.tparm("%{5}%p1%^%d", 3)
80
+ assert_equal "-6", TermTime.tparm("%{5}%~%d")
81
+ end
82
+
83
+ def test_tparm_bool
84
+ assert_equal "1", TermTime.tparm("%{0}%!%d")
85
+ assert_equal "0", TermTime.tparm("%{5}%!%d")
86
+ end
87
+
88
+ def test_tparm_logic
89
+ assert_equal "1", TermTime.tparm("%p1%p2%A%d", 1, 5)
90
+ assert_equal "0", TermTime.tparm("%p1%p2%A%d", 1, 0)
91
+ assert_equal "1", TermTime.tparm("%p1%p2%O%d", 1, 5)
92
+ assert_equal "1", TermTime.tparm("%p1%p2%O%d", 1, 0)
93
+ end
94
+
95
+ def test_tparm_char_constant
96
+ assert_equal "65", TermTime.tparm("%'A'%d")
97
+ end
98
+
99
+ def test_odk
100
+ x = "\e]4;1;rgb:00/00/01\e\\"
101
+ y = TermTime.tparm("\e]4;%p1%d;rgb:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\e\\", 1, 2, 3, 4)
102
+ assert_equal x, y
103
+ end
104
+ end
105
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: termtime
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ original_platform: ''
7
+ authors:
8
+ - Aaron Patterson
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.20'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.20'
41
+ description: This is a pure Ruby terminfo library
42
+ email: tenderlove@ruby-lang.org
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - ".github/workflows/ci.yml"
48
+ - CODE_OF_CONDUCT.md
49
+ - Gemfile
50
+ - LICENSE
51
+ - README.md
52
+ - Rakefile
53
+ - assets/termtime.png
54
+ - lib/termtime.rb
55
+ - lib/termtime/db.rb
56
+ - lib/termtime/terminfo.rb
57
+ - lib/termtime/version.rb
58
+ - templates/lib/termtime/db.rb.erb
59
+ - termtime.gemspec
60
+ - test/fixtures/adm3
61
+ - test/fixtures/adm3a
62
+ - test/fixtures/xterm-256color
63
+ - test/helper.rb
64
+ - test/terminfo_test.rb
65
+ homepage: https://github.com/tenderlove/termtime
66
+ licenses:
67
+ - Apache-2.0
68
+ metadata: {}
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 3.6.0.dev
84
+ specification_version: 4
85
+ summary: Pure Ruby terminfo library
86
+ test_files:
87
+ - test/fixtures/adm3
88
+ - test/fixtures/adm3a
89
+ - test/fixtures/xterm-256color
90
+ - test/helper.rb
91
+ - test/terminfo_test.rb