1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
|
// Package terminfo implements reading terminfo files in pure go.
package terminfo
//go:generate go run gen.go
import (
"io"
"io/ioutil"
"path"
"strconv"
"strings"
)
// Error is a terminfo error.
type Error string
// Error satisfies the error interface.
func (err Error) Error() string {
return string(err)
}
const (
// ErrInvalidFileSize is the invalid file size error.
ErrInvalidFileSize Error = "invalid file size"
// ErrUnexpectedFileEnd is the unexpected file end error.
ErrUnexpectedFileEnd Error = "unexpected file end"
// ErrInvalidStringTable is the invalid string table error.
ErrInvalidStringTable Error = "invalid string table"
// ErrInvalidMagic is the invalid magic error.
ErrInvalidMagic Error = "invalid magic"
// ErrInvalidHeader is the invalid header error.
ErrInvalidHeader Error = "invalid header"
// ErrInvalidNames is the invalid names error.
ErrInvalidNames Error = "invalid names"
// ErrInvalidExtendedHeader is the invalid extended header error.
ErrInvalidExtendedHeader Error = "invalid extended header"
// ErrEmptyTermName is the empty term name error.
ErrEmptyTermName Error = "empty term name"
// ErrDatabaseDirectoryNotFound is the database directory not found error.
ErrDatabaseDirectoryNotFound Error = "database directory not found"
// ErrFileNotFound is the file not found error.
ErrFileNotFound Error = "file not found"
// ErrInvalidTermProgramVersion is the invalid TERM_PROGRAM_VERSION error.
ErrInvalidTermProgramVersion Error = "invalid TERM_PROGRAM_VERSION"
)
// Terminfo describes a terminal's capabilities.
type Terminfo struct {
// File is the original source file.
File string
// Names are the provided cap names.
Names []string
// Bools are the bool capabilities.
Bools map[int]bool
// BoolsM are the missing bool capabilities.
BoolsM map[int]bool
// Nums are the num capabilities.
Nums map[int]int
// NumsM are the missing num capabilities.
NumsM map[int]bool
// Strings are the string capabilities.
Strings map[int][]byte
// StringsM are the missing string capabilities.
StringsM map[int]bool
// ExtBools are the extended bool capabilities.
ExtBools map[int]bool
// ExtBoolsNames is the map of extended bool capabilities to their index.
ExtBoolNames map[int][]byte
// ExtNums are the extended num capabilities.
ExtNums map[int]int
// ExtNumsNames is the map of extended num capabilities to their index.
ExtNumNames map[int][]byte
// ExtStrings are the extended string capabilities.
ExtStrings map[int][]byte
// ExtStringsNames is the map of extended string capabilities to their index.
ExtStringNames map[int][]byte
}
// Decode decodes the terminfo data contained in buf.
func Decode(buf []byte) (*Terminfo, error) {
var err error
// check max file length
if len(buf) >= maxFileLength {
return nil, ErrInvalidFileSize
}
d := &decoder{
buf: buf,
n: len(buf),
}
// read header
h, err := d.readInts(6, 16)
if err != nil {
return nil, err
}
var numWidth int
// check magic
switch {
case h[fieldMagic] == magic:
numWidth = 16
case h[fieldMagic] == magicExtended:
numWidth = 32
default:
return nil, ErrInvalidMagic
}
// check header
if hasInvalidCaps(h) {
return nil, ErrInvalidHeader
}
// check remaining length
if d.n-d.pos < capLength(h) {
return nil, ErrUnexpectedFileEnd
}
// read names
names, err := d.readBytes(h[fieldNameSize])
if err != nil {
return nil, err
}
// check name is terminated properly
i := findNull(names, 0)
if i == -1 {
return nil, ErrInvalidNames
}
names = names[:i]
// read bool caps
bools, boolsM, err := d.readBools(h[fieldBoolCount])
if err != nil {
return nil, err
}
// read num caps
nums, numsM, err := d.readNums(h[fieldNumCount], numWidth)
if err != nil {
return nil, err
}
// read string caps
strs, strsM, err := d.readStrings(h[fieldStringCount], h[fieldTableSize])
if err != nil {
return nil, err
}
ti := &Terminfo{
Names: strings.Split(string(names), "|"),
Bools: bools,
BoolsM: boolsM,
Nums: nums,
NumsM: numsM,
Strings: strs,
StringsM: strsM,
}
// at the end of file, so no extended caps
if d.pos >= d.n {
return ti, nil
}
// decode extended header
eh, err := d.readInts(5, 16)
if err != nil {
return nil, err
}
// check extended offset field
if hasInvalidExtOffset(eh) {
return nil, ErrInvalidExtendedHeader
}
// check extended cap lengths
if d.n-d.pos != extCapLength(eh, numWidth) {
return nil, ErrInvalidExtendedHeader
}
// read extended bool caps
ti.ExtBools, _, err = d.readBools(eh[fieldExtBoolCount])
if err != nil {
return nil, err
}
// read extended num caps
ti.ExtNums, _, err = d.readNums(eh[fieldExtNumCount], numWidth)
if err != nil {
return nil, err
}
// read extended string data table indexes
extIndexes, err := d.readInts(eh[fieldExtOffsetCount], 16)
if err != nil {
return nil, err
}
// read string data table
extData, err := d.readBytes(eh[fieldExtTableSize])
if err != nil {
return nil, err
}
// precautionary check that exactly at end of file
if d.pos != d.n {
return nil, ErrUnexpectedFileEnd
}
var last int
// read extended string caps
ti.ExtStrings, last, err = readStrings(extIndexes, extData, eh[fieldExtStringCount])
if err != nil {
return nil, err
}
extIndexes, extData = extIndexes[eh[fieldExtStringCount]:], extData[last:]
// read extended bool names
ti.ExtBoolNames, _, err = readStrings(extIndexes, extData, eh[fieldExtBoolCount])
if err != nil {
return nil, err
}
extIndexes = extIndexes[eh[fieldExtBoolCount]:]
// read extended num names
ti.ExtNumNames, _, err = readStrings(extIndexes, extData, eh[fieldExtNumCount])
if err != nil {
return nil, err
}
extIndexes = extIndexes[eh[fieldExtNumCount]:]
// read extended string names
ti.ExtStringNames, _, err = readStrings(extIndexes, extData, eh[fieldExtStringCount])
if err != nil {
return nil, err
}
// extIndexes = extIndexes[eh[fieldExtStringCount]:]
return ti, nil
}
// Open reads the terminfo file name from the specified directory dir.
func Open(dir, name string) (*Terminfo, error) {
var err error
var buf []byte
var filename string
for _, f := range []string{
path.Join(dir, name[0:1], name),
path.Join(dir, strconv.FormatUint(uint64(name[0]), 16), name),
} {
buf, err = ioutil.ReadFile(f)
if err == nil {
filename = f
break
}
}
if buf == nil {
return nil, ErrFileNotFound
}
// decode
ti, err := Decode(buf)
if err != nil {
return nil, err
}
// save original file name
ti.File = filename
// add to cache
termCache.Lock()
for _, n := range ti.Names {
termCache.db[n] = ti
}
termCache.Unlock()
return ti, nil
}
// boolCaps returns all bool and extended capabilities using f to format the
// index key.
func (ti *Terminfo) boolCaps(f func(int) string, extended bool) map[string]bool {
m := make(map[string]bool, len(ti.Bools)+len(ti.ExtBools))
if !extended {
for k, v := range ti.Bools {
m[f(k)] = v
}
} else {
for k, v := range ti.ExtBools {
m[string(ti.ExtBoolNames[k])] = v
}
}
return m
}
// BoolCaps returns all bool capabilities.
func (ti *Terminfo) BoolCaps() map[string]bool {
return ti.boolCaps(BoolCapName, false)
}
// BoolCapsShort returns all bool capabilities, using the short name as the
// index.
func (ti *Terminfo) BoolCapsShort() map[string]bool {
return ti.boolCaps(BoolCapNameShort, false)
}
// ExtBoolCaps returns all extended bool capabilities.
func (ti *Terminfo) ExtBoolCaps() map[string]bool {
return ti.boolCaps(BoolCapName, true)
}
// ExtBoolCapsShort returns all extended bool capabilities, using the short
// name as the index.
func (ti *Terminfo) ExtBoolCapsShort() map[string]bool {
return ti.boolCaps(BoolCapNameShort, true)
}
// numCaps returns all num and extended capabilities using f to format the
// index key.
func (ti *Terminfo) numCaps(f func(int) string, extended bool) map[string]int {
m := make(map[string]int, len(ti.Nums)+len(ti.ExtNums))
if !extended {
for k, v := range ti.Nums {
m[f(k)] = v
}
} else {
for k, v := range ti.ExtNums {
m[string(ti.ExtNumNames[k])] = v
}
}
return m
}
// NumCaps returns all num capabilities.
func (ti *Terminfo) NumCaps() map[string]int {
return ti.numCaps(NumCapName, false)
}
// NumCapsShort returns all num capabilities, using the short name as the
// index.
func (ti *Terminfo) NumCapsShort() map[string]int {
return ti.numCaps(NumCapNameShort, false)
}
// ExtNumCaps returns all extended num capabilities.
func (ti *Terminfo) ExtNumCaps() map[string]int {
return ti.numCaps(NumCapName, true)
}
// ExtNumCapsShort returns all extended num capabilities, using the short
// name as the index.
func (ti *Terminfo) ExtNumCapsShort() map[string]int {
return ti.numCaps(NumCapNameShort, true)
}
// stringCaps returns all string and extended capabilities using f to format the
// index key.
func (ti *Terminfo) stringCaps(f func(int) string, extended bool) map[string][]byte {
m := make(map[string][]byte, len(ti.Strings)+len(ti.ExtStrings))
if !extended {
for k, v := range ti.Strings {
m[f(k)] = v
}
} else {
for k, v := range ti.ExtStrings {
m[string(ti.ExtStringNames[k])] = v
}
}
return m
}
// StringCaps returns all string capabilities.
func (ti *Terminfo) StringCaps() map[string][]byte {
return ti.stringCaps(StringCapName, false)
}
// StringCapsShort returns all string capabilities, using the short name as the
// index.
func (ti *Terminfo) StringCapsShort() map[string][]byte {
return ti.stringCaps(StringCapNameShort, false)
}
// ExtStringCaps returns all extended string capabilities.
func (ti *Terminfo) ExtStringCaps() map[string][]byte {
return ti.stringCaps(StringCapName, true)
}
// ExtStringCapsShort returns all extended string capabilities, using the short
// name as the index.
func (ti *Terminfo) ExtStringCapsShort() map[string][]byte {
return ti.stringCaps(StringCapNameShort, true)
}
// Has determines if the bool cap i is present.
func (ti *Terminfo) Has(i int) bool {
return ti.Bools[i]
}
// Num returns the num cap i, or -1 if not present.
func (ti *Terminfo) Num(i int) int {
n, ok := ti.Nums[i]
if !ok {
return -1
}
return n
}
// Printf formats the string cap i, interpolating parameters v.
func (ti *Terminfo) Printf(i int, v ...interface{}) string {
return Printf(ti.Strings[i], v...)
}
// Fprintf prints the string cap i to writer w, interpolating parameters v.
func (ti *Terminfo) Fprintf(w io.Writer, i int, v ...interface{}) {
Fprintf(w, ti.Strings[i], v...)
}
// Color takes a foreground and background color and returns string that sets
// them for this terminal.
func (ti *Terminfo) Colorf(fg, bg int, str string) string {
maxColors := int(ti.Nums[MaxColors])
// map bright colors to lower versions if the color table only holds 8.
if maxColors == 8 {
if fg > 7 && fg < 16 {
fg -= 8
}
if bg > 7 && bg < 16 {
bg -= 8
}
}
var s string
if maxColors > fg && fg >= 0 {
s += ti.Printf(SetAForeground, fg)
}
if maxColors > bg && bg >= 0 {
s += ti.Printf(SetABackground, bg)
}
return s + str + ti.Printf(ExitAttributeMode)
}
// Goto returns a string suitable for addressing the cursor at the given
// row and column. The origin 0, 0 is in the upper left corner of the screen.
func (ti *Terminfo) Goto(row, col int) string {
return Printf(ti.Strings[CursorAddress], row, col)
}
// Puts emits the string to the writer, but expands inline padding indications
// (of the form $<[delay]> where [delay] is msec) to a suitable number of
// padding characters (usually null bytes) based upon the supplied baud. At
// high baud rates, more padding characters will be inserted.
/*func (ti *Terminfo) Puts(w io.Writer, s string, lines, baud int) (int, error) {
var err error
for {
start := strings.Index(s, "$<")
if start == -1 {
// most strings don't need padding, which is good news!
return io.WriteString(w, s)
}
end := strings.Index(s, ">")
if end == -1 {
// unterminated... just emit bytes unadulterated.
return io.WriteString(w, "$<"+s)
}
var c int
c, err = io.WriteString(w, s[:start])
if err != nil {
return n + c, err
}
n += c
s = s[start+2:]
val := s[:end]
s = s[end+1:]
var ms int
var dot, mandatory, asterisk bool
unit := 1000
for _, ch := range val {
switch {
case ch >= '0' && ch <= '9':
ms = (ms * 10) + int(ch-'0')
if dot {
unit *= 10
}
case ch == '.' && !dot:
dot = true
case ch == '*' && !asterisk:
ms *= lines
asterisk = true
case ch == '/':
mandatory = true
default:
break
}
}
z, pad := ((baud/8)/unit)*ms, ti.Strings[PadChar]
b := make([]byte, len(pad)*z)
for bp := copy(b, pad); bp < len(b); bp *= 2 {
copy(b[bp:], b[:bp])
}
if (!ti.Bools[XonXoff] && baud > int(ti.Nums[PaddingBaudRate])) || mandatory {
c, err = w.Write(b)
if err != nil {
return n + c, err
}
n += c
}
}
return n, nil
}*/
|