clash_brush_parser/
prompt.rs1use crate::error;
4
5#[derive(Clone, Debug)]
7#[cfg_attr(
8 any(test, feature = "serde"),
9 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
10)]
11pub enum PromptPiece {
12 AsciiCharacter(u32),
14 Backslash,
16 BellCharacter,
18 CarriageReturn,
20 CurrentCommandNumber,
22 CurrentHistoryNumber,
24 CurrentUser,
26 CurrentWorkingDirectory {
28 tilde_replaced: bool,
30 basename: bool,
32 },
33 Date(PromptDateFormat),
35 DollarOrPound,
37 EndNonPrintingSequence,
39 EscapeCharacter,
41 EscapedSequence(String),
43 Hostname {
45 only_up_to_first_dot: bool,
47 },
48 Literal(String),
50 Newline,
52 NumberOfManagedJobs,
54 ShellBaseName,
56 ShellRelease,
58 ShellVersion,
60 StartNonPrintingSequence,
62 TerminalDeviceBaseName,
64 Time(PromptTimeFormat),
66}
67
68#[derive(Clone, Debug)]
70#[cfg_attr(
71 any(test, feature = "serde"),
72 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
73)]
74pub enum PromptDateFormat {
75 WeekdayMonthDate,
77 Custom(String),
79}
80
81#[derive(Clone, Debug)]
83#[cfg_attr(
84 any(test, feature = "serde"),
85 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
86)]
87pub enum PromptTimeFormat {
88 TwelveHourAM,
90 TwelveHourHHMMSS,
92 TwentyFourHourHHMM,
94 TwentyFourHourHHMMSS,
96}
97
98peg::parser! {
99 grammar prompt_parser() for str {
100 pub(crate) rule prompt() -> Vec<PromptPiece> =
101 pieces:prompt_piece()*
102
103 rule prompt_piece() -> PromptPiece =
104 special_sequence() /
105 literal_sequence()
106
107 rule special_sequence() -> PromptPiece =
111 "\\a" { PromptPiece::BellCharacter } /
112 "\\A" { PromptPiece::Time(PromptTimeFormat::TwentyFourHourHHMM) } /
113 "\\d" { PromptPiece::Date(PromptDateFormat::WeekdayMonthDate) } /
114 "\\D{" f:date_format() "}" { PromptPiece::Date(PromptDateFormat::Custom(f)) } /
115 "\\e" { PromptPiece::EscapeCharacter } /
116 "\\h" { PromptPiece::Hostname { only_up_to_first_dot: true } } /
117 "\\H" { PromptPiece::Hostname { only_up_to_first_dot: false } } /
118 "\\j" { PromptPiece::NumberOfManagedJobs } /
119 "\\l" { PromptPiece::TerminalDeviceBaseName } /
120 "\\n" { PromptPiece::Newline } /
121 "\\r" { PromptPiece::CarriageReturn } /
122 "\\s" { PromptPiece::ShellBaseName } /
123 "\\t" { PromptPiece::Time(PromptTimeFormat::TwentyFourHourHHMMSS ) } /
124 "\\T" { PromptPiece::Time(PromptTimeFormat::TwelveHourHHMMSS ) } /
125 "\\@" { PromptPiece::Time(PromptTimeFormat::TwelveHourAM ) } /
126 "\\u" { PromptPiece::CurrentUser } /
127 "\\v" { PromptPiece::ShellVersion } /
128 "\\V" { PromptPiece::ShellRelease } /
129 "\\w" { PromptPiece::CurrentWorkingDirectory { tilde_replaced: true, basename: false, } } /
130 "\\W" { PromptPiece::CurrentWorkingDirectory { tilde_replaced: true, basename: true, } } /
131 "\\!" { PromptPiece::CurrentHistoryNumber } /
132 "\\#" { PromptPiece::CurrentCommandNumber } /
133 "\\$" { PromptPiece::DollarOrPound } /
134 "\\" n:octal_number() { PromptPiece::AsciiCharacter(n) } /
135 "\\\\" { PromptPiece::Backslash } /
136 "\\[" { PromptPiece::StartNonPrintingSequence } /
137 "\\]" { PromptPiece::EndNonPrintingSequence } /
138 s:$("\\" [_]) { PromptPiece::EscapedSequence(s.to_owned()) }
139
140 rule literal_sequence() -> PromptPiece =
141 s:$((!special_sequence() [c])+) { PromptPiece::Literal(s.to_owned()) }
142
143 rule date_format() -> String =
144 s:$([c if c != '}']*) { s.to_owned() }
145
146 rule octal_number() -> u32 =
147 s:$(['0'..='7']*<1,3>) {? u32::from_str_radix(s, 8).or(Err("invalid octal number")) }
148 }
149}
150
151pub fn parse(s: &str) -> Result<Vec<PromptPiece>, error::WordParseError> {
157 let result = prompt_parser::prompt(s).map_err(|e| error::WordParseError::Prompt(e.into()))?;
158 Ok(result)
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use anyhow::Result;
165 use pretty_assertions::assert_eq;
166
167 #[test]
168 fn basic_prompt() -> Result<()> {
169 assert_eq!(
170 parse(r"\u@\h:\w$ ")?,
171 &[
172 PromptPiece::CurrentUser,
173 PromptPiece::Literal("@".to_owned()),
174 PromptPiece::Hostname {
175 only_up_to_first_dot: true
176 },
177 PromptPiece::Literal(":".to_owned()),
178 PromptPiece::CurrentWorkingDirectory {
179 tilde_replaced: true,
180 basename: false
181 },
182 PromptPiece::Literal("$ ".to_owned()),
183 ]
184 );
185
186 Ok(())
187 }
188
189 #[test]
190 fn brackets_and_vars() -> Result<()> {
191 assert_eq!(
192 parse(r"\[$foo\]\u > ")?,
193 &[
194 PromptPiece::StartNonPrintingSequence,
195 PromptPiece::Literal("$foo".to_owned()),
196 PromptPiece::EndNonPrintingSequence,
197 PromptPiece::CurrentUser,
198 PromptPiece::Literal(" > ".to_owned()),
199 ]
200 );
201
202 Ok(())
203 }
204}