brush_parser/
prompt.rs

1//! Parser for shell prompt syntax (e.g., `PS1`).
2
3use crate::error;
4
5/// A piece of a prompt string.
6#[derive(Clone)]
7pub enum PromptPiece {
8    /// An ASCII character.
9    AsciiCharacter(u32),
10    /// A backslash character.
11    Backslash,
12    /// The bell character.
13    BellCharacter,
14    /// A carriage return character.
15    CarriageReturn,
16    /// The current command number.
17    CurrentCommandNumber,
18    /// The current history number.
19    CurrentHistoryNumber,
20    /// The name of the current user.
21    CurrentUser,
22    /// Path to the current working directory.
23    CurrentWorkingDirectory {
24        /// Whether or not to apply tilde-replacement before expanding.
25        tilde_replaced: bool,
26        /// Whether or not to only expand to the basename of the directory.
27        basename: bool,
28    },
29    /// The current date, using the given format.
30    Date(PromptDateFormat),
31    /// The dollar or pound character.
32    DollarOrPound,
33    /// Special marker indicating the end of a non-printing sequence of characters.
34    EndNonPrintingSequence,
35    /// The escape character.
36    EscapeCharacter,
37    /// The hostname of the system.
38    Hostname {
39        /// Whether or not to include only up to the first dot of the name.
40        only_up_to_first_dot: bool,
41    },
42    /// A literal string.
43    Literal(String),
44    /// A newline character.
45    Newline,
46    /// The number of actively managed jobs.
47    NumberOfManagedJobs,
48    /// The base name of the shell.
49    ShellBaseName,
50    /// The release of the shell.
51    ShellRelease,
52    /// The version of the shell.
53    ShellVersion,
54    /// Special marker indicating the start of a non-printing sequence of characters.
55    StartNonPrintingSequence,
56    /// The base name of the terminal device.
57    TerminalDeviceBaseName,
58    /// The current time, using the given format.
59    Time(PromptTimeFormat),
60}
61
62/// Format for a date in a prompt.
63#[derive(Clone)]
64pub enum PromptDateFormat {
65    /// A format including weekday, month, and date.
66    WeekdayMonthDate,
67    /// A customer string format.
68    Custom(String),
69}
70
71/// Format for a time in a prompt.
72#[derive(Clone)]
73pub enum PromptTimeFormat {
74    /// A twelve-hour time format with AM/PM.
75    TwelveHourAM,
76    /// A twelve-hour time format (HHMMSS).
77    TwelveHourHHMMSS,
78    /// A twenty-four-hour time format (HHMMSS).
79    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        //
92        // Reference: https://www.gnu.org/software/bash/manual/bash.html#Controlling-the-Prompt
93        //
94        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
133/// Parses a shell prompt string.
134///
135/// # Arguments
136///
137/// * `s` - The prompt string to parse.
138pub 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}