Skip to content

Commit

Permalink
fix: implement date and time in prompts (#298)
Browse files Browse the repository at this point in the history
  • Loading branch information
reubeno authored Dec 26, 2024
1 parent 0b59daf commit eacb489
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 8 deletions.
7 changes: 5 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions brush-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ async-trait = "0.1.83"
brush-parser = { version = "^0.2.11", path = "../brush-parser" }
cached = "0.54.0"
cfg-if = "1.0.0"
chrono = "0.4.39"
clap = { version = "4.5.21", features = ["derive", "wrap_help"] }
fancy-regex = "0.14.0"
futures = "0.3.31"
Expand Down
7 changes: 7 additions & 0 deletions brush-core/benches/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ mod unix {
c.bench_function("expand_one_string", |b| {
b.iter(|| black_box(expand_one_string()));
});
c.bench_function("for_loop", |b| {
b.to_async(tokio()).iter(|| {
black_box(run_one_command(
"for ((i = 0; i < 100000; i++)); do :; done",
))
});
});
}
}

Expand Down
110 changes: 105 additions & 5 deletions brush-core/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ pub(crate) fn format_prompt_piece(
tilde_replaced,
basename,
} => format_current_working_directory(shell, tilde_replaced, basename),
brush_parser::prompt::PromptPiece::Date(_) => return error::unimp("prompt: date"),
brush_parser::prompt::PromptPiece::Date(format) => {
format_date(&chrono::Local::now(), &format)
}
brush_parser::prompt::PromptPiece::DollarOrPound => {
if users::is_root() {
"#".to_owned()
Expand All @@ -77,9 +79,7 @@ pub(crate) fn format_prompt_piece(
hn
}
brush_parser::prompt::PromptPiece::Newline => "\n".to_owned(),
brush_parser::prompt::PromptPiece::NumberOfManagedJobs => {
return error::unimp("prompt: number of managed jobs")
}
brush_parser::prompt::PromptPiece::NumberOfManagedJobs => shell.jobs.jobs.len().to_string(),
brush_parser::prompt::PromptPiece::ShellBaseName => {
if let Some(shell_name) = &shell.shell_name {
Path::new(shell_name)
Expand All @@ -100,7 +100,9 @@ pub(crate) fn format_prompt_piece(
brush_parser::prompt::PromptPiece::TerminalDeviceBaseName => {
return error::unimp("prompt: terminal device base name")
}
brush_parser::prompt::PromptPiece::Time(_) => return error::unimp("prompt: time"),
brush_parser::prompt::PromptPiece::Time(time_fmt) => {
format_time(&chrono::Local::now(), &time_fmt)
}
};

Ok(formatted)
Expand All @@ -125,3 +127,101 @@ fn format_current_working_directory(shell: &Shell, tilde_replaced: bool, basenam

working_dir_str
}

fn format_time<Tz: chrono::TimeZone>(
datetime: &chrono::DateTime<Tz>,
format: &brush_parser::prompt::PromptTimeFormat,
) -> String
where
Tz::Offset: std::fmt::Display,
{
let formatted = match format {
brush_parser::prompt::PromptTimeFormat::TwelveHourAM => datetime.format("%I:%M %p"),
brush_parser::prompt::PromptTimeFormat::TwelveHourHHMMSS => datetime.format("%I:%M:%S"),
brush_parser::prompt::PromptTimeFormat::TwentyFourHourHHMMSS => datetime.format("%H:%M:%S"),
};

formatted.to_string()
}

fn format_date<Tz: chrono::TimeZone>(
datetime: &chrono::DateTime<Tz>,
format: &brush_parser::prompt::PromptDateFormat,
) -> String
where
Tz::Offset: std::fmt::Display,
{
match format {
brush_parser::prompt::PromptDateFormat::WeekdayMonthDate => {
datetime.format("%a %b %d").to_string()
}
brush_parser::prompt::PromptDateFormat::Custom(fmt) => {
let fmt_items = chrono::format::StrftimeItems::new(fmt);
datetime.format_with_items(fmt_items).to_string()
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_format_time() {
// Create a well-known test date/time.
let dt = chrono::DateTime::parse_from_rfc3339("2024-12-25T13:34:56.789Z").unwrap();

assert_eq!(
format_time(&dt, &brush_parser::prompt::PromptTimeFormat::TwelveHourAM),
"01:34 PM"
);

assert_eq!(
format_time(
&dt,
&brush_parser::prompt::PromptTimeFormat::TwentyFourHourHHMMSS
),
"13:34:56"
);

assert_eq!(
format_time(
&dt,
&brush_parser::prompt::PromptTimeFormat::TwelveHourHHMMSS
),
"01:34:56"
);
}

#[test]
fn test_format_date() {
// Create a well-known test date/time.
let dt = chrono::DateTime::parse_from_rfc3339("2024-12-25T12:34:56.789Z").unwrap();

assert_eq!(
format_date(
&dt,
&brush_parser::prompt::PromptDateFormat::WeekdayMonthDate
),
"Wed Dec 25"
);

assert_eq!(
format_date(
&dt,
&brush_parser::prompt::PromptDateFormat::Custom(String::from("%Y-%m-%d"))
),
"2024-12-25"
);

assert_eq!(
format_date(
&dt,
&brush_parser::prompt::PromptDateFormat::Custom(String::from(
"%Y-%m-%d %H:%M:%S.%f"
))
),
"2024-12-25 12:34:56.789000000"
);
}
}
2 changes: 1 addition & 1 deletion brush-parser/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ peg::parser! {
s:$((!special_sequence() [c])+) { PromptPiece::Literal(s.to_owned()) }

rule date_format() -> String =
s:$(!"}" [c]+) { s.to_owned() }
s:$([c if c != '}']*) { s.to_owned() }

rule octal_number() -> u32 =
s:$(['0'..='9']*<3,3>) {? u32::from_str_radix(s, 8).or(Err("invalid octal number")) }
Expand Down
36 changes: 36 additions & 0 deletions brush-shell/tests/cases/prompt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,39 @@ cases:
prompt='\V'
[[ "${prompt@P}" == ^\d+\.\d+\.\d+$ ]] && echo "Release is correct"
- name: "Simple date"
stdin: |
prompt='\d'
[[ "${prompt@P}" == $(date +'%a %b %d') ]] && echo '\d date matches'
- name: "Date format with plain string"
stdin: |
prompt='\D{something}'
echo "Date format with plain string: '${prompt@P}'"
- name: "Date format with year"
stdin: |
prompt='\d{%Y}'
echo "Date format with year: '${prompt@P}'"
- name: "Time format: @"
stdin: |
prompt='\@'
expanded=${prompt@P}
roundtripped=$(date --date="${expanded}" +'%I:%M %p')
[[ ${expanded} == ${roundtripped} ]] && echo "Time matches"
- name: "Time format: T"
stdin: |
prompt='\T'
expanded=${prompt@P}
roundtripped=$(date --date="${expanded}" +'%I:%M:%S')
[[ ${expanded} == ${roundtripped} ]] && echo "Time matches"
- name: "Time format: t"
stdin: |
prompt='\t'
expanded=${prompt@P}
roundtripped=$(date --date="${expanded}" +'%H:%M:%S')
[[ ${expanded} == ${roundtripped} ]] && echo "Time matches"

0 comments on commit eacb489

Please sign in to comment.