diff options
Diffstat (limited to 'vendor/github.com/Nvveen/Gotty/gotty.go')
-rw-r--r-- | vendor/github.com/Nvveen/Gotty/gotty.go | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/vendor/github.com/Nvveen/Gotty/gotty.go b/vendor/github.com/Nvveen/Gotty/gotty.go new file mode 100644 index 000000000..093cbf37e --- /dev/null +++ b/vendor/github.com/Nvveen/Gotty/gotty.go @@ -0,0 +1,238 @@ +// Copyright 2012 Neal van Veen. All rights reserved. +// Usage of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Gotty is a Go-package for reading and parsing the terminfo database +package gotty + +// TODO add more concurrency to name lookup, look for more opportunities. + +import ( + "encoding/binary" + "errors" + "fmt" + "os" + "reflect" + "strings" + "sync" +) + +// Open a terminfo file by the name given and construct a TermInfo object. +// If something went wrong reading the terminfo database file, an error is +// returned. +func OpenTermInfo(termName string) (*TermInfo, error) { + var term *TermInfo + var err error + // Find the environment variables + termloc := os.Getenv("TERMINFO") + if len(termloc) == 0 { + // Search like ncurses + locations := []string{os.Getenv("HOME") + "/.terminfo/", "/etc/terminfo/", + "/lib/terminfo/", "/usr/share/terminfo/"} + var path string + for _, str := range locations { + // Construct path + path = str + string(termName[0]) + "/" + termName + // Check if path can be opened + file, _ := os.Open(path) + if file != nil { + // Path can open, fall out and use current path + file.Close() + break + } + } + if len(path) > 0 { + term, err = readTermInfo(path) + } else { + err = errors.New(fmt.Sprintf("No terminfo file(-location) found")) + } + } + return term, err +} + +// Open a terminfo file from the environment variable containing the current +// terminal name and construct a TermInfo object. If something went wrong +// reading the terminfo database file, an error is returned. +func OpenTermInfoEnv() (*TermInfo, error) { + termenv := os.Getenv("TERM") + return OpenTermInfo(termenv) +} + +// Return an attribute by the name attr provided. If none can be found, +// an error is returned. +func (term *TermInfo) GetAttribute(attr string) (stacker, error) { + // Channel to store the main value in. + var value stacker + // Add a blocking WaitGroup + var block sync.WaitGroup + // Keep track of variable being written. + written := false + // Function to put into goroutine. + f := func(ats interface{}) { + var ok bool + var v stacker + // Switch on type of map to use and assign value to it. + switch reflect.TypeOf(ats).Elem().Kind() { + case reflect.Bool: + v, ok = ats.(map[string]bool)[attr] + case reflect.Int16: + v, ok = ats.(map[string]int16)[attr] + case reflect.String: + v, ok = ats.(map[string]string)[attr] + } + // If ok, a value is found, so we can write. + if ok { + value = v + written = true + } + // Goroutine is done + block.Done() + } + block.Add(3) + // Go for all 3 attribute lists. + go f(term.boolAttributes) + go f(term.numAttributes) + go f(term.strAttributes) + // Wait until every goroutine is done. + block.Wait() + // If a value has been written, return it. + if written { + return value, nil + } + // Otherwise, error. + return nil, fmt.Errorf("Erorr finding attribute") +} + +// Return an attribute by the name attr provided. If none can be found, +// an error is returned. A name is first converted to its termcap value. +func (term *TermInfo) GetAttributeName(name string) (stacker, error) { + tc := GetTermcapName(name) + return term.GetAttribute(tc) +} + +// A utility function that finds and returns the termcap equivalent of a +// variable name. +func GetTermcapName(name string) string { + // Termcap name + var tc string + // Blocking group + var wait sync.WaitGroup + // Function to put into a goroutine + f := func(attrs []string) { + // Find the string corresponding to the name + for i, s := range attrs { + if s == name { + tc = attrs[i+1] + } + } + // Goroutine is finished + wait.Done() + } + wait.Add(3) + // Go for all 3 attribute lists + go f(BoolAttr[:]) + go f(NumAttr[:]) + go f(StrAttr[:]) + // Wait until every goroutine is done + wait.Wait() + // Return the termcap name + return tc +} + +// This function takes a path to a terminfo file and reads it in binary +// form to construct the actual TermInfo file. +func readTermInfo(path string) (*TermInfo, error) { + // Open the terminfo file + file, err := os.Open(path) + defer file.Close() + if err != nil { + return nil, err + } + + // magic, nameSize, boolSize, nrSNum, nrOffsetsStr, strSize + // Header is composed of the magic 0432 octal number, size of the name + // section, size of the boolean section, the amount of number values, + // the number of offsets of strings, and the size of the string section. + var header [6]int16 + // Byte array is used to read in byte values + var byteArray []byte + // Short array is used to read in short values + var shArray []int16 + // TermInfo object to store values + var term TermInfo + + // Read in the header + err = binary.Read(file, binary.LittleEndian, &header) + if err != nil { + return nil, err + } + // If magic number isn't there or isn't correct, we have the wrong filetype + if header[0] != 0432 { + return nil, errors.New(fmt.Sprintf("Wrong filetype")) + } + + // Read in the names + byteArray = make([]byte, header[1]) + err = binary.Read(file, binary.LittleEndian, &byteArray) + if err != nil { + return nil, err + } + term.Names = strings.Split(string(byteArray), "|") + + // Read in the booleans + byteArray = make([]byte, header[2]) + err = binary.Read(file, binary.LittleEndian, &byteArray) + if err != nil { + return nil, err + } + term.boolAttributes = make(map[string]bool) + for i, b := range byteArray { + if b == 1 { + term.boolAttributes[BoolAttr[i*2+1]] = true + } + } + // If the number of bytes read is not even, a byte for alignment is added + if len(byteArray)%2 != 0 { + err = binary.Read(file, binary.LittleEndian, make([]byte, 1)) + if err != nil { + return nil, err + } + } + + // Read in shorts + shArray = make([]int16, header[3]) + err = binary.Read(file, binary.LittleEndian, &shArray) + if err != nil { + return nil, err + } + term.numAttributes = make(map[string]int16) + for i, n := range shArray { + if n != 0377 && n > -1 { + term.numAttributes[NumAttr[i*2+1]] = n + } + } + + // Read the offsets into the short array + shArray = make([]int16, header[4]) + err = binary.Read(file, binary.LittleEndian, &shArray) + if err != nil { + return nil, err + } + // Read the actual strings in the byte array + byteArray = make([]byte, header[5]) + err = binary.Read(file, binary.LittleEndian, &byteArray) + if err != nil { + return nil, err + } + term.strAttributes = make(map[string]string) + // We get an offset, and then iterate until the string is null-terminated + for i, offset := range shArray { + if offset > -1 { + r := offset + for ; byteArray[r] != 0; r++ { + } + term.strAttributes[StrAttr[i*2+1]] = string(byteArray[offset:r]) + } + } + return &term, nil +} |