brush_parser/prompt.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
//! Parser for shell prompt syntax (e.g., `PS1`).
use crate::error;
/// A piece of a prompt string.
#[derive(Clone)]
pub enum PromptPiece {
/// An ASCII character.
AsciiCharacter(u32),
/// A backslash character.
Backslash,
/// The bell character.
BellCharacter,
/// A carriage return character.
CarriageReturn,
/// The current command number.
CurrentCommandNumber,
/// The current history number.
CurrentHistoryNumber,
/// The name of the current user.
CurrentUser,
/// Path to the current working directory.
CurrentWorkingDirectory {
/// Whether or not to apply tilde-replacement before expanding.
tilde_replaced: bool,
/// Whether or not to only expand to the basename of the directory.
basename: bool,
},
/// The current date, using the given format.
Date(PromptDateFormat),
/// The dollar or pound character.
DollarOrPound,
/// Special marker indicating the end of a non-printing sequence of characters.
EndNonPrintingSequence,
/// The escape character.
EscapeCharacter,
/// The hostname of the system.
Hostname {
/// Whether or not to include only up to the first dot of the name.
only_up_to_first_dot: bool,
},
/// A literal string.
Literal(String),
/// A newline character.
Newline,
/// The number of actively managed jobs.
NumberOfManagedJobs,
/// The base name of the shell.
ShellBaseName,
/// The release of the shell.
ShellRelease,
/// The version of the shell.
ShellVersion,
/// Special marker indicating the start of a non-printing sequence of characters.
StartNonPrintingSequence,
/// The base name of the terminal device.
TerminalDeviceBaseName,
/// The current time, using the given format.
Time(PromptTimeFormat),
}
/// Format for a date in a prompt.
#[derive(Clone)]
pub enum PromptDateFormat {
/// A format including weekday, month, and date.
WeekdayMonthDate,
/// A customer string format.
Custom(String),
}
/// Format for a time in a prompt.
#[derive(Clone)]
pub enum PromptTimeFormat {
/// A twelve-hour time format with AM/PM.
TwelveHourAM,
/// A twelve-hour time format (HHMMSS).
TwelveHourHHMMSS,
/// A twenty-four-hour time format (HHMMSS).
TwentyFourHourHHMMSS,
}
peg::parser! {
grammar prompt_parser() for str {
pub(crate) rule prompt() -> Vec<PromptPiece> =
pieces:prompt_piece()*
rule prompt_piece() -> PromptPiece =
special_sequence() /
literal_sequence()
//
// Reference: https://www.gnu.org/software/bash/manual/bash.html#Controlling-the-Prompt
//
rule special_sequence() -> PromptPiece =
"\\a" { PromptPiece::BellCharacter } /
"\\d" { PromptPiece::Date(PromptDateFormat::WeekdayMonthDate) } /
"\\D{" f:date_format() "}" { PromptPiece::Date(PromptDateFormat::Custom(f)) } /
"\\e" { PromptPiece::EscapeCharacter } /
"\\h" { PromptPiece::Hostname { only_up_to_first_dot: true } } /
"\\H" { PromptPiece::Hostname { only_up_to_first_dot: false } } /
"\\j" { PromptPiece::NumberOfManagedJobs } /
"\\l" { PromptPiece::TerminalDeviceBaseName } /
"\\n" { PromptPiece::Newline } /
"\\r" { PromptPiece::CarriageReturn } /
"\\s" { PromptPiece::ShellBaseName } /
"\\t" { PromptPiece::Time(PromptTimeFormat::TwentyFourHourHHMMSS ) } /
"\\T" { PromptPiece::Time(PromptTimeFormat::TwelveHourHHMMSS ) } /
"\\@" { PromptPiece::Time(PromptTimeFormat::TwelveHourAM ) } /
"\\u" { PromptPiece::CurrentUser } /
"\\v" { PromptPiece::ShellVersion } /
"\\V" { PromptPiece::ShellRelease } /
"\\w" { PromptPiece::CurrentWorkingDirectory { tilde_replaced: true, basename: false, } } /
"\\W" { PromptPiece::CurrentWorkingDirectory { tilde_replaced: true, basename: true, } } /
"\\!" { PromptPiece::CurrentHistoryNumber } /
"\\#" { PromptPiece::CurrentCommandNumber } /
"\\$" { PromptPiece::DollarOrPound } /
"\\" n:octal_number() { PromptPiece::AsciiCharacter(n) } /
"\\\\" { PromptPiece::Backslash } /
"\\[" { PromptPiece::StartNonPrintingSequence } /
"\\]" { PromptPiece::EndNonPrintingSequence }
rule literal_sequence() -> PromptPiece =
s:$((!special_sequence() [c])+) { PromptPiece::Literal(s.to_owned()) }
rule date_format() -> String =
s:$(!"}" [c]+) { s.to_owned() }
rule octal_number() -> u32 =
s:$(['0'..='9']*<3,3>) {? u32::from_str_radix(s, 8).or(Err("invalid octal number")) }
}
}
/// Parses a shell prompt string.
///
/// # Arguments
///
/// * `s` - The prompt string to parse.
pub fn parse(s: &str) -> Result<Vec<PromptPiece>, error::WordParseError> {
let result = prompt_parser::prompt(s).map_err(error::WordParseError::Prompt)?;
Ok(result)
}