| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- // 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
- }*/
|