terminfo/parser/
source.rs

1//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2//                    Version 2, December 2004
3//
4// Copyleft (ↄ) meh. <meh@schizofreni.co> | http://meh.schizofreni.co
5//
6// Everyone is permitted to copy and distribute verbatim or modified
7// copies of this license document, and changing it is allowed as long
8// as the name is changed.
9//
10//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11//   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12//
13//  0. You just DO WHAT THE FUCK YOU WANT TO.
14
15use crate::parser::util::unescape;
16use crate::parser::util::{end, is_eol, is_ws, ws};
17use crate::parser::util::{is_printable_no_comma, is_printable_no_control, is_printable_no_pipe};
18use nom::branch::alt;
19use nom::bytes::streaming::{tag, take, take_until, take_while};
20use nom::character::{is_digit, streaming::line_ending as eol};
21use nom::combinator::{complete, map, map_res, opt};
22use nom::error::{make_error, ErrorKind};
23use nom::sequence::terminated;
24use nom::IResult;
25use std::borrow::Cow;
26use std::str;
27
28#[derive(Eq, PartialEq, Clone, Debug)]
29pub enum Item<'a> {
30	Comment(&'a str),
31
32	Definition { name: &'a str, aliases: Vec<&'a str>, description: &'a str },
33
34	True(&'a str),
35	Number(&'a str, i16),
36	String(&'a str, Cow<'a, [u8]>),
37	Disable(&'a str),
38}
39
40pub fn parse(input: &[u8]) -> IResult<&[u8], Item> {
41	alt((comment, definition, disable, entry))(input)
42}
43
44fn comment(input: &[u8]) -> IResult<&[u8], Item> {
45	let (input, _) = tag("#")(input)?;
46	let (input, content) = map_res(terminated(take_until("\n"), tag("\n")), str::from_utf8)(input)?;
47	let (input, _) = opt(complete(take_while(is_eol)))(input)?;
48
49	Ok((input, Item::Comment(content.trim())))
50}
51
52fn definition(input: &[u8]) -> IResult<&[u8], Item> {
53	let (input, name) =
54		map(take_while(is_printable_no_pipe), |n| unsafe { str::from_utf8_unchecked(n) })(input)?;
55
56	let (input, _) = tag("|")(input)?;
57
58	let (input, content) =
59		map(take_while(is_printable_no_comma), |n| unsafe { str::from_utf8_unchecked(n) })(input)?;
60
61	let (input, _) = tag(",")(input)?;
62
63	let (input, _) = take_while(is_ws)(input)?;
64
65	let (input, _) = eol(input)?;
66	let (input, _) = opt(complete(take_while(is_eol)))(input)?;
67
68	Ok((input, {
69		let mut aliases = content.split(|c| c == '|').map(|n| n.trim()).collect::<Vec<_>>();
70
71		Item::Definition { name, description: aliases.pop().unwrap(), aliases }
72	}))
73}
74
75fn disable(input: &[u8]) -> IResult<&[u8], Item> {
76	let (input, _) = ws(input)?;
77	let (input, _) = take_while(is_ws)(input)?;
78	let (input, _) = tag("@")(input)?;
79
80	let (input, name) =
81		map(take_while(is_printable_no_control), |n| unsafe { str::from_utf8_unchecked(n) })(
82			input,
83		)?;
84
85	let (input, _) = tag(",")(input)?;
86	let (input, _) = take_while(is_ws)(input)?;
87	let (input, _) = end(input)?;
88	let (input, _) = opt(complete(take_while(is_eol)))(input)?;
89
90	Ok((input, Item::Disable(name)))
91}
92
93fn entry(input: &[u8]) -> IResult<&[u8], Item> {
94	let (input, _) = ws(input)?;
95	let (input, _) = take_while(is_ws)(input)?;
96
97	let (input, name) =
98		map(take_while(is_printable_no_control), |n| unsafe { str::from_utf8_unchecked(n) })(
99			input,
100		)?;
101
102	let (input, c) = take(1_usize)(input)?;
103	let (input, value) = match c {
104		b"," => (input, Item::True(name)),
105
106		b"#" => {
107			let (input, value) =
108				map(take_while(is_digit), |n| unsafe { str::from_utf8_unchecked(n) })(input)?;
109
110			let (input, _) = tag(",")(input)?;
111
112			(input, Item::Number(name, value.parse().unwrap()))
113		}
114
115		b"=" => {
116			let (input, value) = take_while(is_printable_no_comma)(input)?;
117
118			let (input, _) = tag(",")(input)?;
119
120			(input, Item::String(name, unescape(value)))
121		}
122
123		_ => Err(nom::Err::Error(make_error(input, ErrorKind::Switch)))?,
124	};
125
126	let (input, _) = take_while(is_ws)(input)?;
127	let (input, _) = end(input)?;
128	let (input, _) = opt(complete(take_while(is_eol)))(input)?;
129
130	Ok((input, value))
131}
132
133#[cfg(test)]
134mod test {
135	use super::*;
136
137	use std::fs::File;
138	use std::io::Read;
139
140	#[test]
141	fn parsing() {
142		let mut file = File::open("tests/xterm.terminfo").unwrap();
143		let mut buffer = Vec::new();
144		file.read_to_end(&mut buffer).unwrap();
145
146		let mut input = &buffer[..];
147
148		while !input.is_empty() {
149			match parse(input) {
150				Ok((rest, _)) => input = rest,
151
152				Err(::nom::Err::Incomplete(_)) => panic!("incomplete"),
153
154				Err(err) => panic!("parsing: {:?}", err),
155			}
156		}
157	}
158}