1use crate::error;
4
5#[derive(Clone)]
7pub enum PromptPiece {
8 AsciiCharacter(u32),
10 Backslash,
12 BellCharacter,
14 CarriageReturn,
16 CurrentCommandNumber,
18 CurrentHistoryNumber,
20 CurrentUser,
22 CurrentWorkingDirectory {
24 tilde_replaced: bool,
26 basename: bool,
28 },
29 Date(PromptDateFormat),
31 DollarOrPound,
33 EndNonPrintingSequence,
35 EscapeCharacter,
37 Hostname {
39 only_up_to_first_dot: bool,
41 },
42 Literal(String),
44 Newline,
46 NumberOfManagedJobs,
48 ShellBaseName,
50 ShellRelease,
52 ShellVersion,
54 StartNonPrintingSequence,
56 TerminalDeviceBaseName,
58 Time(PromptTimeFormat),
60}
61
62#[derive(Clone)]
64pub enum PromptDateFormat {
65 WeekdayMonthDate,
67 Custom(String),
69}
70
71#[derive(Clone)]
73pub enum PromptTimeFormat {
74 TwelveHourAM,
76 TwelveHourHHMMSS,
78 TwentyFourHourHHMMSS,
80}
81
82peg::parser! {
83 grammar prompt_parser() for str {
84 pub(crate) rule prompt() -> Vec<PromptPiece> =
85 pieces:prompt_piece()*
86
87 rule prompt_piece() -> PromptPiece =
88 special_sequence() /
89 literal_sequence()
90
91 rule special_sequence() -> PromptPiece =
95 "\\a" { PromptPiece::BellCharacter } /
96 "\\d" { PromptPiece::Date(PromptDateFormat::WeekdayMonthDate) } /
97 "\\D{" f:date_format() "}" { PromptPiece::Date(PromptDateFormat::Custom(f)) } /
98 "\\e" { PromptPiece::EscapeCharacter } /
99 "\\h" { PromptPiece::Hostname { only_up_to_first_dot: true } } /
100 "\\H" { PromptPiece::Hostname { only_up_to_first_dot: false } } /
101 "\\j" { PromptPiece::NumberOfManagedJobs } /
102 "\\l" { PromptPiece::TerminalDeviceBaseName } /
103 "\\n" { PromptPiece::Newline } /
104 "\\r" { PromptPiece::CarriageReturn } /
105 "\\s" { PromptPiece::ShellBaseName } /
106 "\\t" { PromptPiece::Time(PromptTimeFormat::TwentyFourHourHHMMSS ) } /
107 "\\T" { PromptPiece::Time(PromptTimeFormat::TwelveHourHHMMSS ) } /
108 "\\@" { PromptPiece::Time(PromptTimeFormat::TwelveHourAM ) } /
109 "\\u" { PromptPiece::CurrentUser } /
110 "\\v" { PromptPiece::ShellVersion } /
111 "\\V" { PromptPiece::ShellRelease } /
112 "\\w" { PromptPiece::CurrentWorkingDirectory { tilde_replaced: true, basename: false, } } /
113 "\\W" { PromptPiece::CurrentWorkingDirectory { tilde_replaced: true, basename: true, } } /
114 "\\!" { PromptPiece::CurrentHistoryNumber } /
115 "\\#" { PromptPiece::CurrentCommandNumber } /
116 "\\$" { PromptPiece::DollarOrPound } /
117 "\\" n:octal_number() { PromptPiece::AsciiCharacter(n) } /
118 "\\\\" { PromptPiece::Backslash } /
119 "\\[" { PromptPiece::StartNonPrintingSequence } /
120 "\\]" { PromptPiece::EndNonPrintingSequence }
121
122 rule literal_sequence() -> PromptPiece =
123 s:$((!special_sequence() [c])+) { PromptPiece::Literal(s.to_owned()) }
124
125 rule date_format() -> String =
126 s:$([c if c != '}']*) { s.to_owned() }
127
128 rule octal_number() -> u32 =
129 s:$(['0'..='9']*<3,3>) {? u32::from_str_radix(s, 8).or(Err("invalid octal number")) }
130 }
131}
132
133pub fn parse(s: &str) -> Result<Vec<PromptPiece>, error::WordParseError> {
139 let result = prompt_parser::prompt(s).map_err(error::WordParseError::Prompt)?;
140 Ok(result)
141}