feat: Implement the prompt module for time (#138)

Add a module which displays the current time in a format requested by
the user. Disabled by default.
This commit is contained in:
John Letey 2019-09-10 18:54:40 +01:00 committed by Kevin Song
parent 7d02f718c8
commit f9a4514045
9 changed files with 207 additions and 1 deletions

1
Cargo.lock generated
View File

@ -764,6 +764,7 @@ version = "0.16.0"
dependencies = [
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"battery 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"gethostname 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -41,6 +41,7 @@ path-slash = "0.1.1"
unicode-segmentation = "1.3.0"
gethostname = "0.2.0"
once_cell = "1.1.0"
chrono = "0.4"
[dev-dependencies]
tempfile = "3.1.0"

View File

@ -96,6 +96,7 @@ default_prompt_order = [
"cmd_duration",
"line_break",
"jobs",
"time",
"battery",
"character",
]
@ -603,6 +604,38 @@ The module will be shown if any of the following conditions are met:
symbol = "⚙️ "
```
## Time
The `time` module shows the current **local** time.
The `format` configuration value is used by the [`chrono`](https://crates.io/crates/chrono) crate to control how the time is displayed. Take a look [at the chrono strftime docs](https://docs.rs/chrono/0.4.7/chrono/format/strftime/index.html) to see what options are available.
::: tip
This module is disabled by default.
To enable it, set `disabled` to `false` in your configuration file.
:::
### Options
| Variable | Default | Description |
| ---------- | ------------- | ------------------------------------------------------------------------------------------------------------------- |
| `12hr` | `false` | Enables 12 hour formatting |
| `format` | see below | The [chrono format string](https://docs.rs/chrono/0.4.7/chrono/format/strftime/index.html) used to format the time. |
| `style` | `bold yellow` | The style for the module time |
| `disabled` | `true` | Disables the `time` module. |
If `12hr` is `true`, then `format` defaults to `"%r"`. Otherwise, it defaults to `"%T"`.
Manually setting `format` will override the `12hr` setting.
### Example
```toml
# ~/.config/starship.toml
[time]
disabled = false
format = "🕙[ %T ]"
```
## Username
The `username` module shows active user's username.

View File

@ -24,6 +24,7 @@ pub const ALL_MODULES: &[&str] = &[
"python",
"ruby",
"rust",
"time",
"username",
];

View File

@ -15,6 +15,7 @@ mod package;
mod python;
mod ruby;
mod rust;
mod time;
mod username;
#[cfg(feature = "battery")]
@ -44,6 +45,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
"jobs" => jobs::module(context),
"nix_shell" => nix_shell::module(context),
"hostname" => hostname::module(context),
"time" => time::module(context),
_ => {
eprintln!("Error: Unknown module {}. Use starship module --list to list out all supported modules.", module);

105
src/modules/time.rs Normal file
View File

@ -0,0 +1,105 @@
use ansi_term::Color;
use chrono::offset::TimeZone;
use chrono::{DateTime, Local};
use super::{Context, Module};
/// Outputs the current time
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("time");
// Remove when logic for disabled by default exists
if module.config_value_bool("disabled").unwrap_or(true) {
return None;
}
let module_style = module
.config_value_style("style")
.unwrap_or_else(|| Color::Yellow.bold());
module.set_style(module_style);
// Load module settings
let is_12hr = module.config_value_bool("12hr").unwrap_or(false);
let default_format = if is_12hr { "%r" } else { "%T" };
let time_format = module
.config_value_str("format")
.unwrap_or(default_format)
.to_owned();
log::trace!(
"Timer module is enabled with format string: {}",
time_format
);
let local: DateTime<Local> = Local::now();
let formatted_time_string = format_time(&time_format, local);
module.new_segment("time", &formatted_time_string);
module.get_prefix().set_value("at ");
Some(module)
}
/// Format a given time into the given string. This function should be referentially
/// transparent, which makes it easy to test (unlike anything involving the actual time)
fn format_time(time_format: &str, localtime: DateTime<Local>) -> String {
localtime.format(time_format).to_string()
}
/* Because we cannot do integration tests on the time module, these unit
tests become extra important */
#[cfg(test)]
mod tests {
use super::*;
const FMT_12: &str = "%r";
const FMT_24: &str = "%T";
#[test]
fn test_midnight_12hr() {
let time = Local.ymd(2014, 7, 8).and_hms(0, 0, 0);
let formatted = format_time(FMT_12, time);
assert_eq!(formatted, "12:00:00 AM");
}
#[test]
fn test_midnight_24hr() {
let time = Local.ymd(2014, 7, 8).and_hms(0, 0, 0);
let formatted = format_time(FMT_24, time);
assert_eq!(formatted, "00:00:00");
}
#[test]
fn test_noon_12hr() {
let time = Local.ymd(2014, 7, 8).and_hms(12, 0, 0);
let formatted = format_time(FMT_12, time);
assert_eq!(formatted, "12:00:00 PM");
}
#[test]
fn test_noon_24hr() {
let time = Local.ymd(2014, 7, 8).and_hms(12, 0, 0);
let formatted = format_time(FMT_24, time);
assert_eq!(formatted, "12:00:00");
}
#[test]
fn test_arbtime_12hr() {
let time = Local.ymd(2014, 7, 8).and_hms(15, 36, 47);
let formatted = format_time(FMT_12, time);
assert_eq!(formatted, "03:36:47 PM");
}
#[test]
fn test_arbtime_24hr() {
let time = Local.ymd(2014, 7, 8).and_hms(15, 36, 47);
let formatted = format_time(FMT_24, time);
assert_eq!(formatted, "15:36:47");
}
fn test_format_with_paren() {
let time = Local.ymd(2014, 7, 8).and_hms(15, 36, 47);
let formatted = format_time("[%T]", time);
assert_eq!(formatted, "[15:36:47]");
}
}

View File

@ -29,7 +29,7 @@ const DEFAULT_PROMPT_ORDER: &[&str] = &[
"line_break",
"jobs",
#[cfg(feature = "battery")]
"battery",
"time",
"character",
];

View File

@ -15,4 +15,5 @@ mod nix_shell;
mod nodejs;
mod python;
mod ruby;
mod time;
mod username;

62
tests/testsuite/time.rs Normal file
View File

@ -0,0 +1,62 @@
use ansi_term::Color;
use std::fs;
use std::io;
use std::path::Path;
use tempfile::TempDir;
use crate::common::{self, TestCommand};
/* Note: tests in this crate cannot rely on the actual time displayed by
the module, since that is dependent on the time inside the test environment,
which we cannot control.
However, we *can* test certain things here, such as the fact that the module
should not display when disabled, should display *something* when enabled,
and should have the correct prefixes and suffixes in a given config */
#[test]
fn config_enabled() -> io::Result<()> {
let output = common::render_module("time")
.use_config(toml::toml! {
[time]
disabled = false
})
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
// We can't test what it actually is...but we can assert it's not blank
assert!(!actual.is_empty());
Ok(())
}
#[test]
fn config_blank() -> io::Result<()> {
let output = common::render_module("time").output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = "";
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn config_check_prefix_and_suffix() -> io::Result<()> {
let output = common::render_module("time")
.use_config(toml::toml! {
[time]
disabled = false
format = "[%T]"
})
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
// This is the prefix with "at ", the color code, then the prefix char [
let col_prefix = format!("at {}{}[", '\u{1b}', "[1;33m");
// This is the suffix with suffix char ']', then color codes, then a space
let col_suffix = format!("]{}{} ", '\u{1b}', "[0m");
assert!(actual.starts_with(&col_prefix));
assert!(actual.ends_with(&col_suffix));
Ok(())
}