Tizen_4.0 base
[platform/upstream/docker-engine.git] / vendor / github.com / Nvveen / Gotty / gotty.go
1 // Copyright 2012 Neal van Veen. All rights reserved.
2 // Usage of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Gotty is a Go-package for reading and parsing the terminfo database
6 package gotty
7
8 // TODO add more concurrency to name lookup, look for more opportunities.
9
10 import (
11         "bytes"
12         "encoding/binary"
13         "errors"
14         "fmt"
15         "os"
16         "path"
17         "reflect"
18         "strings"
19         "sync"
20 )
21
22 // Open a terminfo file by the name given and construct a TermInfo object.
23 // If something went wrong reading the terminfo database file, an error is
24 // returned.
25 func OpenTermInfo(termName string) (*TermInfo, error) {
26         if len(termName) == 0 {
27                 return nil, errors.New("No termname given")
28         }
29         // Find the environment variables
30         if termloc := os.Getenv("TERMINFO"); len(termloc) > 0 {
31                 return readTermInfo(path.Join(termloc, string(termName[0]), termName))
32         } else {
33                 // Search like ncurses
34                 locations := []string{}
35                 if h := os.Getenv("HOME"); len(h) > 0 {
36                         locations = append(locations, path.Join(h, ".terminfo"))
37                 }
38                 locations = append(locations,
39                         "/etc/terminfo/",
40                         "/lib/terminfo/",
41                         "/usr/share/terminfo/")
42                 for _, str := range locations {
43                         term, err := readTermInfo(path.Join(str, string(termName[0]), termName))
44                         if err == nil {
45                                 return term, nil
46                         }
47                 }
48                 return nil, errors.New("No terminfo file(-location) found")
49         }
50 }
51
52 // Open a terminfo file from the environment variable containing the current
53 // terminal name and construct a TermInfo object. If something went wrong
54 // reading the terminfo database file, an error is returned.
55 func OpenTermInfoEnv() (*TermInfo, error) {
56         termenv := os.Getenv("TERM")
57         return OpenTermInfo(termenv)
58 }
59
60 // Return an attribute by the name attr provided. If none can be found,
61 // an error is returned.
62 func (term *TermInfo) GetAttribute(attr string) (stacker, error) {
63         // Channel to store the main value in.
64         var value stacker
65         // Add a blocking WaitGroup
66         var block sync.WaitGroup
67         // Keep track of variable being written.
68         written := false
69         // Function to put into goroutine.
70         f := func(ats interface{}) {
71                 var ok bool
72                 var v stacker
73                 // Switch on type of map to use and assign value to it.
74                 switch reflect.TypeOf(ats).Elem().Kind() {
75                 case reflect.Bool:
76                         v, ok = ats.(map[string]bool)[attr]
77                 case reflect.Int16:
78                         v, ok = ats.(map[string]int16)[attr]
79                 case reflect.String:
80                         v, ok = ats.(map[string]string)[attr]
81                 }
82                 // If ok, a value is found, so we can write.
83                 if ok {
84                         value = v
85                         written = true
86                 }
87                 // Goroutine is done
88                 block.Done()
89         }
90         block.Add(3)
91         // Go for all 3 attribute lists.
92         go f(term.boolAttributes)
93         go f(term.numAttributes)
94         go f(term.strAttributes)
95         // Wait until every goroutine is done.
96         block.Wait()
97         // If a value has been written, return it.
98         if written {
99                 return value, nil
100         }
101         // Otherwise, error.
102         return nil, fmt.Errorf("Erorr finding attribute")
103 }
104
105 // Return an attribute by the name attr provided. If none can be found,
106 // an error is returned. A name is first converted to its termcap value.
107 func (term *TermInfo) GetAttributeName(name string) (stacker, error) {
108         tc := GetTermcapName(name)
109         return term.GetAttribute(tc)
110 }
111
112 // A utility function that finds and returns the termcap equivalent of a
113 // variable name.
114 func GetTermcapName(name string) string {
115         // Termcap name
116         var tc string
117         // Blocking group
118         var wait sync.WaitGroup
119         // Function to put into a goroutine
120         f := func(attrs []string) {
121                 // Find the string corresponding to the name
122                 for i, s := range attrs {
123                         if s == name {
124                                 tc = attrs[i+1]
125                         }
126                 }
127                 // Goroutine is finished
128                 wait.Done()
129         }
130         wait.Add(3)
131         // Go for all 3 attribute lists
132         go f(BoolAttr[:])
133         go f(NumAttr[:])
134         go f(StrAttr[:])
135         // Wait until every goroutine is done
136         wait.Wait()
137         // Return the termcap name
138         return tc
139 }
140
141 // This function takes a path to a terminfo file and reads it in binary
142 // form to construct the actual TermInfo file.
143 func readTermInfo(path string) (*TermInfo, error) {
144         // Open the terminfo file
145         file, err := os.Open(path)
146         defer file.Close()
147         if err != nil {
148                 return nil, err
149         }
150
151         // magic, nameSize, boolSize, nrSNum, nrOffsetsStr, strSize
152         // Header is composed of the magic 0432 octal number, size of the name
153         // section, size of the boolean section, the amount of number values,
154         // the number of offsets of strings, and the size of the string section.
155         var header [6]int16
156         // Byte array is used to read in byte values
157         var byteArray []byte
158         // Short array is used to read in short values
159         var shArray []int16
160         // TermInfo object to store values
161         var term TermInfo
162
163         // Read in the header
164         err = binary.Read(file, binary.LittleEndian, &header)
165         if err != nil {
166                 return nil, err
167         }
168         // If magic number isn't there or isn't correct, we have the wrong filetype
169         if header[0] != 0432 {
170                 return nil, errors.New(fmt.Sprintf("Wrong filetype"))
171         }
172
173         // Read in the names
174         byteArray = make([]byte, header[1])
175         err = binary.Read(file, binary.LittleEndian, &byteArray)
176         if err != nil {
177                 return nil, err
178         }
179         term.Names = strings.Split(string(byteArray), "|")
180
181         // Read in the booleans
182         byteArray = make([]byte, header[2])
183         err = binary.Read(file, binary.LittleEndian, &byteArray)
184         if err != nil {
185                 return nil, err
186         }
187         term.boolAttributes = make(map[string]bool)
188         for i, b := range byteArray {
189                 if b == 1 {
190                         term.boolAttributes[BoolAttr[i*2+1]] = true
191                 }
192         }
193         // If the number of bytes read is not even, a byte for alignment is added
194         // We know the header is an even number of bytes so only need to check the
195         // total of the names and booleans.
196         if (header[1]+header[2])%2 != 0 {
197                 err = binary.Read(file, binary.LittleEndian, make([]byte, 1))
198                 if err != nil {
199                         return nil, err
200                 }
201         }
202
203         // Read in shorts
204         shArray = make([]int16, header[3])
205         err = binary.Read(file, binary.LittleEndian, &shArray)
206         if err != nil {
207                 return nil, err
208         }
209         term.numAttributes = make(map[string]int16)
210         for i, n := range shArray {
211                 if n != 0377 && n > -1 {
212                         term.numAttributes[NumAttr[i*2+1]] = n
213                 }
214         }
215
216         // Read the offsets into the short array
217         shArray = make([]int16, header[4])
218         err = binary.Read(file, binary.LittleEndian, &shArray)
219         if err != nil {
220                 return nil, err
221         }
222         // Read the actual strings in the byte array
223         byteArray = make([]byte, header[5])
224         err = binary.Read(file, binary.LittleEndian, &byteArray)
225         if err != nil {
226                 return nil, err
227         }
228         term.strAttributes = make(map[string]string)
229         // We get an offset, and then iterate until the string is null-terminated
230         for i, offset := range shArray {
231                 if offset > -1 {
232                         if int(offset) >= len(byteArray) {
233                                 return nil, errors.New("array out of bounds reading string section")
234                         }
235                         r := bytes.IndexByte(byteArray[offset:], 0)
236                         if r == -1 {
237                                 return nil, errors.New("missing nul byte reading string section")
238                         }
239                         r += int(offset)
240                         term.strAttributes[StrAttr[i*2+1]] = string(byteArray[offset:r])
241                 }
242         }
243         return &term, nil
244 }