1use crate::error;
4
5#[derive(Clone)]
7#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
8pub enum PromptPiece {
9 AsciiCharacter(u32),
11 Backslash,
13 BellCharacter,
15 CarriageReturn,
17 CurrentCommandNumber,
19 CurrentHistoryNumber,
21 CurrentUser,
23 CurrentWorkingDirectory {
25 tilde_replaced: bool,
27 basename: bool,
29 },
30 Date(PromptDateFormat),
32 DollarOrPound,
34 EndNonPrintingSequence,
36 EscapeCharacter,
38 Hostname {
40 only_up_to_first_dot: bool,
42 },
43 Literal(String),
45 Newline,
47 NumberOfManagedJobs,
49 ShellBaseName,
51 ShellRelease,
53 ShellVersion,
55 StartNonPrintingSequence,
57 TerminalDeviceBaseName,
59 Time(PromptTimeFormat),
61}
62
63#[derive(Clone)]
65#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
66pub enum PromptDateFormat {
67 WeekdayMonthDate,
69 Custom(String),
71}
72
73#[derive(Clone)]
75#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
76pub enum PromptTimeFormat {
77 TwelveHourAM,
79 TwelveHourHHMMSS,
81 TwentyFourHourHHMM,
83 TwentyFourHourHHMMSS,
85}
86
87peg::parser! {
88 grammar prompt_parser() for str {
89 pub(crate) rule prompt() -> Vec<PromptPiece> =
90 pieces:prompt_piece()*
91
92 rule prompt_piece() -> PromptPiece =
93 special_sequence() /
94 literal_sequence()
95
96 rule special_sequence() -> PromptPiece =
100 "\\a" { PromptPiece::BellCharacter } /
101 "\\A" { PromptPiece::Time(PromptTimeFormat::TwentyFourHourHHMM) } /
102 "\\d" { PromptPiece::Date(PromptDateFormat::WeekdayMonthDate) } /
103 "\\D{" f:date_format() "}" { PromptPiece::Date(PromptDateFormat::Custom(f)) } /
104 "\\e" { PromptPiece::EscapeCharacter } /
105 "\\h" { PromptPiece::Hostname { only_up_to_first_dot: true } } /
106 "\\H" { PromptPiece::Hostname { only_up_to_first_dot: false } } /
107 "\\j" { PromptPiece::NumberOfManagedJobs } /
108 "\\l" { PromptPiece::TerminalDeviceBaseName } /
109 "\\n" { PromptPiece::Newline } /
110 "\\r" { PromptPiece::CarriageReturn } /
111 "\\s" { PromptPiece::ShellBaseName } /
112 "\\t" { PromptPiece::Time(PromptTimeFormat::TwentyFourHourHHMMSS ) } /
113 "\\T" { PromptPiece::Time(PromptTimeFormat::TwelveHourHHMMSS ) } /
114 "\\@" { PromptPiece::Time(PromptTimeFormat::TwelveHourAM ) } /
115 "\\u" { PromptPiece::CurrentUser } /
116 "\\v" { PromptPiece::ShellVersion } /
117 "\\V" { PromptPiece::ShellRelease } /
118 "\\w" { PromptPiece::CurrentWorkingDirectory { tilde_replaced: true, basename: false, } } /
119 "\\W" { PromptPiece::CurrentWorkingDirectory { tilde_replaced: true, basename: true, } } /
120 "\\!" { PromptPiece::CurrentHistoryNumber } /
121 "\\#" { PromptPiece::CurrentCommandNumber } /
122 "\\$" { PromptPiece::DollarOrPound } /
123 "\\" n:octal_number() { PromptPiece::AsciiCharacter(n) } /
124 "\\\\" { PromptPiece::Backslash } /
125 "\\[" { PromptPiece::StartNonPrintingSequence } /
126 "\\]" { PromptPiece::EndNonPrintingSequence }
127
128 rule literal_sequence() -> PromptPiece =
129 s:$((!special_sequence() [c])+) { PromptPiece::Literal(s.to_owned()) }
130
131 rule date_format() -> String =
132 s:$([c if c != '}']*) { s.to_owned() }
133
134 rule octal_number() -> u32 =
135 s:$(['0'..='9']*<3,3>) {? u32::from_str_radix(s, 8).or(Err("invalid octal number")) }
136 }
137}
138
139pub fn parse(s: &str) -> Result<Vec<PromptPiece>, error::WordParseError> {
145 let result = prompt_parser::prompt(s).map_err(|e| error::WordParseError::Prompt(e.into()))?;
146 Ok(result)
147}