1 // Copyright 2019 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! ncurses-compatible compiled terminfo format parsing (term(5))
13 use std
::collections
::HashMap
;
15 use std
::io
::prelude
::*;
17 use crate::terminfo
::Error
::*;
18 use crate::terminfo
::TermInfo
;
21 pub use crate::terminfo
::parser
::names
::*;
23 // These are the orders ncurses uses in its compiled format (as of 5.9). Not
26 fn read_le_u16(r
: &mut dyn io
::Read
) -> io
::Result
<u32> {
28 r
.read_exact(&mut buf
)
29 .map(|()| u32::from(u16::from_le_bytes(buf
)))
32 fn read_le_u32(r
: &mut dyn io
::Read
) -> io
::Result
<u32> {
34 r
.read_exact(&mut buf
).map(|()| u32::from_le_bytes(buf
))
37 fn read_byte(r
: &mut dyn io
::Read
) -> io
::Result
<u8> {
38 match r
.bytes().next() {
40 None
=> Err(io
::Error
::new(io
::ErrorKind
::Other
, "end of file")),
44 /// Parse a compiled terminfo entry, using long capability names if `longnames`
46 pub fn parse(file
: &mut dyn io
::Read
, longnames
: bool
) -> Result
<TermInfo
> {
47 let (bnames
, snames
, nnames
) = if longnames
{
48 (boolfnames
, stringfnames
, numfnames
)
50 (boolnames
, stringnames
, numnames
)
55 file
.read_exact(&mut buf
)?
;
56 let magic
= u16::from_le_bytes(buf
);
58 let read_number
= match magic
{
59 0x011A => read_le_u16
,
60 0x021e => read_le_u32
,
61 _
=> return Err(BadMagic(magic
).into()),
64 // According to the spec, these fields must be >= -1 where -1 means that the
66 // supported. Using 0 instead of -1 works because we skip sections with length
68 macro_rules
! read_nonneg
{
70 match read_le_u16(file
)?
as i16 {
71 n
if n
>= 0 => n
as usize,
73 _
=> return Err(InvalidLength
.into()),
78 let names_bytes
= read_nonneg
!();
79 let bools_bytes
= read_nonneg
!();
80 let numbers_count
= read_nonneg
!();
81 let string_offsets_count
= read_nonneg
!();
82 let string_table_bytes
= read_nonneg
!();
85 return Err(ShortNames
.into());
88 if bools_bytes
> boolnames
.len() {
89 return Err(TooManyBools
.into());
92 if numbers_count
> numnames
.len() {
93 return Err(TooManyNumbers
.into());
96 if string_offsets_count
> stringnames
.len() {
97 return Err(TooManyStrings
.into());
101 let mut bytes
= Vec
::new();
102 file
.take((names_bytes
- 1) as u64)
103 .read_to_end(&mut bytes
)?
;
104 let names_str
= match String
::from_utf8(bytes
) {
106 Err(e
) => return Err(NotUtf8(e
.utf8_error()).into()),
109 let term_names
: Vec
<String
> = names_str
.split('
|'
).map(|s
| s
.to_owned()).collect();
111 if read_byte(file
)?
!= b'
\0'
{
112 return Err(NamesMissingNull
.into());
115 let bools_map
= (0..bools_bytes
)
116 .filter_map(|i
| match read_byte(file
) {
117 Err(e
) => Some(Err(e
)),
118 Ok(1) => Some(Ok((bnames
[i
], true))),
121 .collect
::<io
::Result
<HashMap
<_
, _
>>>()?
;
123 if (bools_bytes
+ names_bytes
) % 2 == 1 {
124 read_byte(file
)?
; // compensate for padding
127 let numbers_map
= (0..numbers_count
)
128 .filter_map(|i
| match read_number(file
) {
130 Ok(n
) => Some(Ok((nnames
[i
], n
))),
131 Err(e
) => Some(Err(e
)),
133 .collect
::<io
::Result
<HashMap
<_
, _
>>>()?
;
135 let string_map
: HashMap
<&str, Vec
<u8>> = if string_offsets_count
> 0 {
136 let string_offsets
= (0..string_offsets_count
)
138 let mut buf
= [0; 2];
139 file
.read_exact(&mut buf
).map(|()| u16::from_le_bytes(buf
))
141 .collect
::<io
::Result
<Vec
<_
>>>()?
;
143 let mut string_table
= Vec
::new();
144 file
.take(string_table_bytes
as u64)
145 .read_to_end(&mut string_table
)?
;
150 .filter(|&(_
, offset
)| {
155 let offset
= offset
as usize;
157 let name
= if snames
[i
] == "_" {
163 if offset
== 0xFFFE {
164 // undocumented: FFFE indicates cap@, which means the capability
166 // unsure if the handling for this is correct
167 return Ok((name
, Vec
::new()));
170 // Find the offset of the NUL we want to go to
171 let nulpos
= string_table
[offset
..string_table_bytes
]
173 .position(|&b
| b
== 0);
175 Some(len
) => Ok((name
, string_table
[offset
..offset
+ len
].to_vec())),
176 None
=> Err(crate::Error
::TerminfoParsing(StringsMissingNull
)),
179 .collect
::<Result
<HashMap
<_
, _
>>>()?
184 // And that's all there is to it
188 numbers
: numbers_map
,
196 use super::{boolfnames, boolnames, numfnames, numnames, stringfnames, stringnames}
;
200 assert_eq
!(boolfnames
.len(), boolnames
.len());
201 assert_eq
!(numfnames
.len(), numnames
.len());
202 assert_eq
!(stringfnames
.len(), stringnames
.len());