terminfo.go

  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}*/