forked from toolshed/abra
		
	We were running behind and there were quite some deprecations to update. This was mostly in the upstream copy/pasta package but seems quite minimal.
		
			
				
	
	
		
			480 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			480 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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
 | |
| }*/
 |