1// Package terminfo implements reading terminfo files in pure go.
2package terminfo
3
4//go:generate go run gen.go
5
6import (
7 "io"
8 "io/ioutil"
9 "path"
10 "strconv"
11 "strings"
12)
13
14// Error is a terminfo error.
15type Error string
16
17// Error satisfies the error interface.
18func (err Error) Error() string {
19 return string(err)
20}
21
22const (
23 // ErrInvalidFileSize is the invalid file size error.
24 ErrInvalidFileSize Error = "invalid file size"
25 // ErrUnexpectedFileEnd is the unexpected file end error.
26 ErrUnexpectedFileEnd Error = "unexpected file end"
27 // ErrInvalidStringTable is the invalid string table error.
28 ErrInvalidStringTable Error = "invalid string table"
29 // ErrInvalidMagic is the invalid magic error.
30 ErrInvalidMagic Error = "invalid magic"
31 // ErrInvalidHeader is the invalid header error.
32 ErrInvalidHeader Error = "invalid header"
33 // ErrInvalidNames is the invalid names error.
34 ErrInvalidNames Error = "invalid names"
35 // ErrInvalidExtendedHeader is the invalid extended header error.
36 ErrInvalidExtendedHeader Error = "invalid extended header"
37 // ErrEmptyTermName is the empty term name error.
38 ErrEmptyTermName Error = "empty term name"
39 // ErrDatabaseDirectoryNotFound is the database directory not found error.
40 ErrDatabaseDirectoryNotFound Error = "database directory not found"
41 // ErrFileNotFound is the file not found error.
42 ErrFileNotFound Error = "file not found"
43 // ErrInvalidTermProgramVersion is the invalid TERM_PROGRAM_VERSION error.
44 ErrInvalidTermProgramVersion Error = "invalid TERM_PROGRAM_VERSION"
45)
46
47// Terminfo describes a terminal's capabilities.
48type Terminfo struct {
49 // File is the original source file.
50 File string
51 // Names are the provided cap names.
52 Names []string
53 // Bools are the bool capabilities.
54 Bools map[int]bool
55 // BoolsM are the missing bool capabilities.
56 BoolsM map[int]bool
57 // Nums are the num capabilities.
58 Nums map[int]int
59 // NumsM are the missing num capabilities.
60 NumsM map[int]bool
61 // Strings are the string capabilities.
62 Strings map[int][]byte
63 // StringsM are the missing string capabilities.
64 StringsM map[int]bool
65 // ExtBools are the extended bool capabilities.
66 ExtBools map[int]bool
67 // ExtBoolsNames is the map of extended bool capabilities to their index.
68 ExtBoolNames map[int][]byte
69 // ExtNums are the extended num capabilities.
70 ExtNums map[int]int
71 // ExtNumsNames is the map of extended num capabilities to their index.
72 ExtNumNames map[int][]byte
73 // ExtStrings are the extended string capabilities.
74 ExtStrings map[int][]byte
75 // ExtStringsNames is the map of extended string capabilities to their index.
76 ExtStringNames map[int][]byte
77}
78
79// Decode decodes the terminfo data contained in buf.
80func Decode(buf []byte) (*Terminfo, error) {
81 var err error
82 // check max file length
83 if len(buf) >= maxFileLength {
84 return nil, ErrInvalidFileSize
85 }
86 d := &decoder{
87 buf: buf,
88 n: len(buf),
89 }
90 // read header
91 h, err := d.readInts(6, 16)
92 if err != nil {
93 return nil, err
94 }
95 var numWidth int
96 // check magic
97 switch {
98 case h[fieldMagic] == magic:
99 numWidth = 16
100 case h[fieldMagic] == magicExtended:
101 numWidth = 32
102 default:
103 return nil, ErrInvalidMagic
104 }
105 // check header
106 if hasInvalidCaps(h) {
107 return nil, ErrInvalidHeader
108 }
109 // check remaining length
110 if d.n-d.pos < capLength(h) {
111 return nil, ErrUnexpectedFileEnd
112 }
113 // read names
114 names, err := d.readBytes(h[fieldNameSize])
115 if err != nil {
116 return nil, err
117 }
118 // check name is terminated properly
119 i := findNull(names, 0)
120 if i == -1 {
121 return nil, ErrInvalidNames
122 }
123 names = names[:i]
124 // read bool caps
125 bools, boolsM, err := d.readBools(h[fieldBoolCount])
126 if err != nil {
127 return nil, err
128 }
129 // read num caps
130 nums, numsM, err := d.readNums(h[fieldNumCount], numWidth)
131 if err != nil {
132 return nil, err
133 }
134 // read string caps
135 strs, strsM, err := d.readStrings(h[fieldStringCount], h[fieldTableSize])
136 if err != nil {
137 return nil, err
138 }
139 ti := &Terminfo{
140 Names: strings.Split(string(names), "|"),
141 Bools: bools,
142 BoolsM: boolsM,
143 Nums: nums,
144 NumsM: numsM,
145 Strings: strs,
146 StringsM: strsM,
147 }
148 // at the end of file, so no extended caps
149 if d.pos >= d.n {
150 return ti, nil
151 }
152 // decode extended header
153 eh, err := d.readInts(5, 16)
154 if err != nil {
155 return nil, err
156 }
157 // check extended offset field
158 if hasInvalidExtOffset(eh) {
159 return nil, ErrInvalidExtendedHeader
160 }
161 // check extended cap lengths
162 if d.n-d.pos != extCapLength(eh, numWidth) {
163 return nil, ErrInvalidExtendedHeader
164 }
165 // read extended bool caps
166 ti.ExtBools, _, err = d.readBools(eh[fieldExtBoolCount])
167 if err != nil {
168 return nil, err
169 }
170 // read extended num caps
171 ti.ExtNums, _, err = d.readNums(eh[fieldExtNumCount], numWidth)
172 if err != nil {
173 return nil, err
174 }
175 // read extended string data table indexes
176 extIndexes, err := d.readInts(eh[fieldExtOffsetCount], 16)
177 if err != nil {
178 return nil, err
179 }
180 // read string data table
181 extData, err := d.readBytes(eh[fieldExtTableSize])
182 if err != nil {
183 return nil, err
184 }
185 // precautionary check that exactly at end of file
186 if d.pos != d.n {
187 return nil, ErrUnexpectedFileEnd
188 }
189 var last int
190 // read extended string caps
191 ti.ExtStrings, last, err = readStrings(extIndexes, extData, eh[fieldExtStringCount])
192 if err != nil {
193 return nil, err
194 }
195 extIndexes, extData = extIndexes[eh[fieldExtStringCount]:], extData[last:]
196 // read extended bool names
197 ti.ExtBoolNames, _, err = readStrings(extIndexes, extData, eh[fieldExtBoolCount])
198 if err != nil {
199 return nil, err
200 }
201 extIndexes = extIndexes[eh[fieldExtBoolCount]:]
202 // read extended num names
203 ti.ExtNumNames, _, err = readStrings(extIndexes, extData, eh[fieldExtNumCount])
204 if err != nil {
205 return nil, err
206 }
207 extIndexes = extIndexes[eh[fieldExtNumCount]:]
208 // read extended string names
209 ti.ExtStringNames, _, err = readStrings(extIndexes, extData, eh[fieldExtStringCount])
210 if err != nil {
211 return nil, err
212 }
213 // extIndexes = extIndexes[eh[fieldExtStringCount]:]
214 return ti, nil
215}
216
217// Open reads the terminfo file name from the specified directory dir.
218func Open(dir, name string) (*Terminfo, error) {
219 var err error
220 var buf []byte
221 var filename string
222 for _, f := range []string{
223 path.Join(dir, name[0:1], name),
224 path.Join(dir, strconv.FormatUint(uint64(name[0]), 16), name),
225 } {
226 buf, err = ioutil.ReadFile(f)
227 if err == nil {
228 filename = f
229 break
230 }
231 }
232 if buf == nil {
233 return nil, ErrFileNotFound
234 }
235 // decode
236 ti, err := Decode(buf)
237 if err != nil {
238 return nil, err
239 }
240 // save original file name
241 ti.File = filename
242 // add to cache
243 termCache.Lock()
244 for _, n := range ti.Names {
245 termCache.db[n] = ti
246 }
247 termCache.Unlock()
248 return ti, nil
249}
250
251// boolCaps returns all bool and extended capabilities using f to format the
252// index key.
253func (ti *Terminfo) boolCaps(f func(int) string, extended bool) map[string]bool {
254 m := make(map[string]bool, len(ti.Bools)+len(ti.ExtBools))
255 if !extended {
256 for k, v := range ti.Bools {
257 m[f(k)] = v
258 }
259 } else {
260 for k, v := range ti.ExtBools {
261 m[string(ti.ExtBoolNames[k])] = v
262 }
263 }
264 return m
265}
266
267// BoolCaps returns all bool capabilities.
268func (ti *Terminfo) BoolCaps() map[string]bool {
269 return ti.boolCaps(BoolCapName, false)
270}
271
272// BoolCapsShort returns all bool capabilities, using the short name as the
273// index.
274func (ti *Terminfo) BoolCapsShort() map[string]bool {
275 return ti.boolCaps(BoolCapNameShort, false)
276}
277
278// ExtBoolCaps returns all extended bool capabilities.
279func (ti *Terminfo) ExtBoolCaps() map[string]bool {
280 return ti.boolCaps(BoolCapName, true)
281}
282
283// ExtBoolCapsShort returns all extended bool capabilities, using the short
284// name as the index.
285func (ti *Terminfo) ExtBoolCapsShort() map[string]bool {
286 return ti.boolCaps(BoolCapNameShort, true)
287}
288
289// numCaps returns all num and extended capabilities using f to format the
290// index key.
291func (ti *Terminfo) numCaps(f func(int) string, extended bool) map[string]int {
292 m := make(map[string]int, len(ti.Nums)+len(ti.ExtNums))
293 if !extended {
294 for k, v := range ti.Nums {
295 m[f(k)] = v
296 }
297 } else {
298 for k, v := range ti.ExtNums {
299 m[string(ti.ExtNumNames[k])] = v
300 }
301 }
302 return m
303}
304
305// NumCaps returns all num capabilities.
306func (ti *Terminfo) NumCaps() map[string]int {
307 return ti.numCaps(NumCapName, false)
308}
309
310// NumCapsShort returns all num capabilities, using the short name as the
311// index.
312func (ti *Terminfo) NumCapsShort() map[string]int {
313 return ti.numCaps(NumCapNameShort, false)
314}
315
316// ExtNumCaps returns all extended num capabilities.
317func (ti *Terminfo) ExtNumCaps() map[string]int {
318 return ti.numCaps(NumCapName, true)
319}
320
321// ExtNumCapsShort returns all extended num capabilities, using the short
322// name as the index.
323func (ti *Terminfo) ExtNumCapsShort() map[string]int {
324 return ti.numCaps(NumCapNameShort, true)
325}
326
327// stringCaps returns all string and extended capabilities using f to format the
328// index key.
329func (ti *Terminfo) stringCaps(f func(int) string, extended bool) map[string][]byte {
330 m := make(map[string][]byte, len(ti.Strings)+len(ti.ExtStrings))
331 if !extended {
332 for k, v := range ti.Strings {
333 m[f(k)] = v
334 }
335 } else {
336 for k, v := range ti.ExtStrings {
337 m[string(ti.ExtStringNames[k])] = v
338 }
339 }
340 return m
341}
342
343// StringCaps returns all string capabilities.
344func (ti *Terminfo) StringCaps() map[string][]byte {
345 return ti.stringCaps(StringCapName, false)
346}
347
348// StringCapsShort returns all string capabilities, using the short name as the
349// index.
350func (ti *Terminfo) StringCapsShort() map[string][]byte {
351 return ti.stringCaps(StringCapNameShort, false)
352}
353
354// ExtStringCaps returns all extended string capabilities.
355func (ti *Terminfo) ExtStringCaps() map[string][]byte {
356 return ti.stringCaps(StringCapName, true)
357}
358
359// ExtStringCapsShort returns all extended string capabilities, using the short
360// name as the index.
361func (ti *Terminfo) ExtStringCapsShort() map[string][]byte {
362 return ti.stringCaps(StringCapNameShort, true)
363}
364
365// Has determines if the bool cap i is present.
366func (ti *Terminfo) Has(i int) bool {
367 return ti.Bools[i]
368}
369
370// Num returns the num cap i, or -1 if not present.
371func (ti *Terminfo) Num(i int) int {
372 n, ok := ti.Nums[i]
373 if !ok {
374 return -1
375 }
376 return n
377}
378
379// Printf formats the string cap i, interpolating parameters v.
380func (ti *Terminfo) Printf(i int, v ...interface{}) string {
381 return Printf(ti.Strings[i], v...)
382}
383
384// Fprintf prints the string cap i to writer w, interpolating parameters v.
385func (ti *Terminfo) Fprintf(w io.Writer, i int, v ...interface{}) {
386 Fprintf(w, ti.Strings[i], v...)
387}
388
389// Color takes a foreground and background color and returns string that sets
390// them for this terminal.
391func (ti *Terminfo) Colorf(fg, bg int, str string) string {
392 maxColors := int(ti.Nums[MaxColors])
393 // map bright colors to lower versions if the color table only holds 8.
394 if maxColors == 8 {
395 if fg > 7 && fg < 16 {
396 fg -= 8
397 }
398 if bg > 7 && bg < 16 {
399 bg -= 8
400 }
401 }
402 var s string
403 if maxColors > fg && fg >= 0 {
404 s += ti.Printf(SetAForeground, fg)
405 }
406 if maxColors > bg && bg >= 0 {
407 s += ti.Printf(SetABackground, bg)
408 }
409 return s + str + ti.Printf(ExitAttributeMode)
410}
411
412// Goto returns a string suitable for addressing the cursor at the given
413// row and column. The origin 0, 0 is in the upper left corner of the screen.
414func (ti *Terminfo) Goto(row, col int) string {
415 return Printf(ti.Strings[CursorAddress], row, col)
416}
417
418// Puts emits the string to the writer, but expands inline padding indications
419// (of the form $<[delay]> where [delay] is msec) to a suitable number of
420// padding characters (usually null bytes) based upon the supplied baud. At
421// high baud rates, more padding characters will be inserted.
422/*func (ti *Terminfo) Puts(w io.Writer, s string, lines, baud int) (int, error) {
423 var err error
424 for {
425 start := strings.Index(s, "$<")
426 if start == -1 {
427 // most strings don't need padding, which is good news!
428 return io.WriteString(w, s)
429 }
430 end := strings.Index(s, ">")
431 if end == -1 {
432 // unterminated... just emit bytes unadulterated.
433 return io.WriteString(w, "$<"+s)
434 }
435 var c int
436 c, err = io.WriteString(w, s[:start])
437 if err != nil {
438 return n + c, err
439 }
440 n += c
441 s = s[start+2:]
442 val := s[:end]
443 s = s[end+1:]
444 var ms int
445 var dot, mandatory, asterisk bool
446 unit := 1000
447 for _, ch := range val {
448 switch {
449 case ch >= '0' && ch <= '9':
450 ms = (ms * 10) + int(ch-'0')
451 if dot {
452 unit *= 10
453 }
454 case ch == '.' && !dot:
455 dot = true
456 case ch == '*' && !asterisk:
457 ms *= lines
458 asterisk = true
459 case ch == '/':
460 mandatory = true
461 default:
462 break
463 }
464 }
465 z, pad := ((baud/8)/unit)*ms, ti.Strings[PadChar]
466 b := make([]byte, len(pad)*z)
467 for bp := copy(b, pad); bp < len(b); bp *= 2 {
468 copy(b[bp:], b[:bp])
469 }
470 if (!ti.Bools[XonXoff] && baud > int(ti.Nums[PaddingBaudRate])) || mandatory {
471 c, err = w.Write(b)
472 if err != nil {
473 return n + c, err
474 }
475 n += c
476 }
477 }
478 return n, nil
479}*/