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