summaryrefslogtreecommitdiff
path: root/vendor/github.com/ijc/Gotty/gotty.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/ijc/Gotty/gotty.go')
-rw-r--r--vendor/github.com/ijc/Gotty/gotty.go244
1 files changed, 244 insertions, 0 deletions
diff --git a/vendor/github.com/ijc/Gotty/gotty.go b/vendor/github.com/ijc/Gotty/gotty.go
new file mode 100644
index 000000000..c329778a1
--- /dev/null
+++ b/vendor/github.com/ijc/Gotty/gotty.go
@@ -0,0 +1,244 @@
+// 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 (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "os"
+ "path"
+ "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) {
+ if len(termName) == 0 {
+ return nil, errors.New("No termname given")
+ }
+ // Find the environment variables
+ if termloc := os.Getenv("TERMINFO"); len(termloc) > 0 {
+ return readTermInfo(path.Join(termloc, string(termName[0]), termName))
+ } else {
+ // Search like ncurses
+ locations := []string{}
+ if h := os.Getenv("HOME"); len(h) > 0 {
+ locations = append(locations, path.Join(h, ".terminfo"))
+ }
+ locations = append(locations,
+ "/etc/terminfo/",
+ "/lib/terminfo/",
+ "/usr/share/terminfo/")
+ for _, str := range locations {
+ term, err := readTermInfo(path.Join(str, string(termName[0]), termName))
+ if err == nil {
+ return term, nil
+ }
+ }
+ return nil, errors.New("No terminfo file(-location) found")
+ }
+}
+
+// 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
+ // We know the header is an even number of bytes so only need to check the
+ // total of the names and booleans.
+ if (header[1]+header[2])%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 {
+ if int(offset) >= len(byteArray) {
+ return nil, errors.New("array out of bounds reading string section")
+ }
+ r := bytes.IndexByte(byteArray[offset:], 0)
+ if r == -1 {
+ return nil, errors.New("missing nul byte reading string section")
+ }
+ r += int(offset)
+ term.strAttributes[StrAttr[i*2+1]] = string(byteArray[offset:r])
+ }
+ }
+ return &term, nil
+}