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.
5 // Gotty is a Go-package for reading and parsing the terminfo database
8 // TODO add more concurrency to name lookup, look for more opportunities.
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
25 func OpenTermInfo(termName string) (*TermInfo, error) {
26 if len(termName) == 0 {
27 return nil, errors.New("No termname given")
29 // Find the environment variables
30 if termloc := os.Getenv("TERMINFO"); len(termloc) > 0 {
31 return readTermInfo(path.Join(termloc, string(termName[0]), termName))
33 // Search like ncurses
34 locations := []string{}
35 if h := os.Getenv("HOME"); len(h) > 0 {
36 locations = append(locations, path.Join(h, ".terminfo"))
38 locations = append(locations,
41 "/usr/share/terminfo/")
42 for _, str := range locations {
43 term, err := readTermInfo(path.Join(str, string(termName[0]), termName))
48 return nil, errors.New("No terminfo file(-location) found")
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)
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.
65 // Add a blocking WaitGroup
66 var block sync.WaitGroup
67 // Keep track of variable being written.
69 // Function to put into goroutine.
70 f := func(ats interface{}) {
73 // Switch on type of map to use and assign value to it.
74 switch reflect.TypeOf(ats).Elem().Kind() {
76 v, ok = ats.(map[string]bool)[attr]
78 v, ok = ats.(map[string]int16)[attr]
80 v, ok = ats.(map[string]string)[attr]
82 // If ok, a value is found, so we can write.
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.
97 // If a value has been written, return it.
102 return nil, fmt.Errorf("Erorr finding attribute")
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)
112 // A utility function that finds and returns the termcap equivalent of a
114 func GetTermcapName(name string) string {
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 {
127 // Goroutine is finished
131 // Go for all 3 attribute lists
135 // Wait until every goroutine is done
137 // Return the termcap name
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)
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.
156 // Byte array is used to read in byte values
158 // Short array is used to read in short values
160 // TermInfo object to store values
163 // Read in the header
164 err = binary.Read(file, binary.LittleEndian, &header)
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"))
174 byteArray = make([]byte, header[1])
175 err = binary.Read(file, binary.LittleEndian, &byteArray)
179 term.Names = strings.Split(string(byteArray), "|")
181 // Read in the booleans
182 byteArray = make([]byte, header[2])
183 err = binary.Read(file, binary.LittleEndian, &byteArray)
187 term.boolAttributes = make(map[string]bool)
188 for i, b := range byteArray {
190 term.boolAttributes[BoolAttr[i*2+1]] = true
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))
204 shArray = make([]int16, header[3])
205 err = binary.Read(file, binary.LittleEndian, &shArray)
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
216 // Read the offsets into the short array
217 shArray = make([]int16, header[4])
218 err = binary.Read(file, binary.LittleEndian, &shArray)
222 // Read the actual strings in the byte array
223 byteArray = make([]byte, header[5])
224 err = binary.Read(file, binary.LittleEndian, &byteArray)
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 {
232 if int(offset) >= len(byteArray) {
233 return nil, errors.New("array out of bounds reading string section")
235 r := bytes.IndexByte(byteArray[offset:], 0)
237 return nil, errors.New("missing nul byte reading string section")
240 term.strAttributes[StrAttr[i*2+1]] = string(byteArray[offset:r])