mirror of
https://github.com/starship/starship.git
synced 2025-02-18 11:20:58 +01:00
Added disk used module
Added docs for the disk used module Works on windows now Minor fixes on disk_used error generation and handling Signed-off-by: xxchan <xxchan22f@gmail.com>
This commit is contained in:
parent
2021334206
commit
063016c177
@ -348,6 +348,7 @@ $sudo\
|
||||
$cmd_duration\
|
||||
$line_break\
|
||||
$jobs\
|
||||
$disk_used\
|
||||
$battery\
|
||||
$time\
|
||||
$status\
|
||||
@ -1263,6 +1264,76 @@ The `direnv` module shows the status of the current rc file if one is present. T
|
||||
disabled = false
|
||||
```
|
||||
|
||||
## Disk Used
|
||||
|
||||
The `disk_used` module shows disk used in current directory or any disk specified.
|
||||
|
||||
By default the moduel only shows the current directory's disk used when it is more than 30%.
|
||||
|
||||
::: tip
|
||||
|
||||
This module is disabled by default.
|
||||
To enable it, set `disabled` to `false` in your configuration file.
|
||||
|
||||
:::
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `prefix` | ` ` | A prefix for the module output. |
|
||||
| `format` | `"[($prefix )]($style)$symbol$current_storage(\\[$other_storage\\]) "` | The format for the module. |
|
||||
| `symbol` | `"💾 "` | The symbol used before displaying the disk used usage. |
|
||||
| `default_style` | `"white bold"` | The style for the module. |
|
||||
| `disabled` | `true` | Disables the `disk_used` module. |
|
||||
| `separator` | `|` | Used to seprate disk used texts. |
|
||||
| `show_percentage` | `true` | Switches between `63.05%` and `147GB/233GB`. |
|
||||
| `current_threshold` | `30` | Hides the current directory disk used if it is less than this value. |
|
||||
| `all_threshold` | ` ` | Hides all disk used if it is less than this value. |
|
||||
| `show_current_name` | `false` | Toggles between `💾 sda1: 63.05%` and `💾 63.05%`. |
|
||||
| `threshold_styles` | [link](#disk-used-style-threshold) | Thresholds and style pairs for disk used |
|
||||
|
||||
### Disk Used Style Threshold
|
||||
|
||||
The `threshold_styles` are used to change the style of the disk used display based on the percentage of disk used.
|
||||
|
||||
```toml
|
||||
[[disk_used.threshold_styles]]
|
||||
threshold = 50
|
||||
style = "yellow bold"
|
||||
[[disk_used.threshold_styles]]
|
||||
threshold = 80
|
||||
style = "red bold"
|
||||
```
|
||||
|
||||
### Variables
|
||||
|
||||
| Variable | Example | Description |
|
||||
| ----------------- | ------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| prefix | `used` | A perfix for the prompt. |
|
||||
| style\* | `white bold` | Mirrors the value of option `default_style`. |
|
||||
| symbol | `💾 ` | Mirrors the value of option `symbol`. |
|
||||
| current_storage | `63.05%` | The amount of used space on the current disk. |
|
||||
| other_storage\*\* | `sda2:63.05%|sdb1:63.05%` | The amount of used space on all disks. Only displays the disks that pass the `all_threshold` |
|
||||
|
||||
\*: This variable can only be used as a part of a style string
|
||||
\*\*: Disabled if `all_threshold` is not defined
|
||||
|
||||
### Example
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
[disk_used]
|
||||
disabled = false
|
||||
show_percentage = true
|
||||
current_threshold = 0
|
||||
symbol = "💾 "
|
||||
separator = "|"
|
||||
style = "white bold"
|
||||
show_current_name = false
|
||||
```
|
||||
|
||||
## Docker Context
|
||||
|
||||
The `docker_context` module shows the currently active
|
||||
|
51
src/configs/disk_used.rs
Normal file
51
src/configs/disk_used.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::config::{ModuleConfig, RootModuleConfig};
|
||||
|
||||
use starship_module_config_derive::ModuleConfig;
|
||||
|
||||
#[derive(Clone, ModuleConfig)]
|
||||
pub struct ThresholdStyle<'a> {
|
||||
pub threshold: i64,
|
||||
pub style: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Clone, ModuleConfig)]
|
||||
pub struct DiskUsedConfig<'a> {
|
||||
pub format: &'a str,
|
||||
pub symbol: &'a str,
|
||||
pub prefix: &'a str,
|
||||
pub separator: &'a str,
|
||||
pub disabled: bool,
|
||||
pub show_percentage: bool,
|
||||
pub current_threshold: Option<i64>,
|
||||
pub all_threshold: Option<i64>,
|
||||
pub show_current_name: bool,
|
||||
pub default_style: &'a str,
|
||||
pub threshold_styles: Vec<ThresholdStyle<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> RootModuleConfig<'a> for DiskUsedConfig<'a> {
|
||||
fn new() -> Self {
|
||||
DiskUsedConfig {
|
||||
format: "[($prefix )]($style)$symbol$current_storage(\\[$other_storage\\]) ",
|
||||
symbol: "💾 ",
|
||||
prefix: "",
|
||||
separator: "|",
|
||||
disabled: true,
|
||||
show_percentage: true,
|
||||
current_threshold: Some(30),
|
||||
all_threshold: None,
|
||||
show_current_name: false,
|
||||
default_style: "white bold",
|
||||
threshold_styles: vec![
|
||||
ThresholdStyle {
|
||||
threshold: 50,
|
||||
style: "yellow bold",
|
||||
},
|
||||
ThresholdStyle {
|
||||
threshold: 80,
|
||||
style: "red bold",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ pub mod dart;
|
||||
pub mod deno;
|
||||
pub mod directory;
|
||||
pub mod direnv;
|
||||
pub mod disk_used;
|
||||
pub mod docker_context;
|
||||
pub mod dotnet;
|
||||
pub mod elixir;
|
||||
|
@ -108,6 +108,7 @@ pub const PROMPT_ORDER: &[&str] = &[
|
||||
"meson",
|
||||
"spack",
|
||||
"memory_usage",
|
||||
"disk_used",
|
||||
"aws",
|
||||
"gcloud",
|
||||
"openstack",
|
||||
|
@ -93,6 +93,7 @@ pub const ALL_MODULES: &[&str] = &[
|
||||
"sudo",
|
||||
"swift",
|
||||
"terraform",
|
||||
"disk_used",
|
||||
"time",
|
||||
"typst",
|
||||
"username",
|
||||
|
307
src/modules/disk_used.rs
Normal file
307
src/modules/disk_used.rs
Normal file
@ -0,0 +1,307 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use byte_unit::{Byte, ByteError, ByteUnit};
|
||||
use sysinfo::{Disk, DiskExt, RefreshKind, System, SystemExt};
|
||||
|
||||
use super::{Context, Module, RootModuleConfig, Shell};
|
||||
|
||||
use crate::configs::disk_used::DiskUsedConfig;
|
||||
use crate::formatter::StringFormatter;
|
||||
use crate::segment::Segment;
|
||||
|
||||
fn format_byte(n_byte: f64) -> Result<String, ByteError> {
|
||||
let byte = Byte::from_unit(n_byte, ByteUnit::B)?;
|
||||
let mut display_bytes = byte.get_appropriate_unit(false).format(0);
|
||||
display_bytes.retain(|c| c != ' ');
|
||||
Ok(display_bytes)
|
||||
}
|
||||
|
||||
fn get_disk_name(disk: &'_ Disk) -> Option<&'_ str> {
|
||||
let full_disk_name = disk.get_name();
|
||||
let disk_name = Path::new(full_disk_name).file_name();
|
||||
match disk_name {
|
||||
Some(disk_name) => disk_name.to_str(),
|
||||
None => full_disk_name.to_str(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_disk_used(
|
||||
disk: &Disk,
|
||||
config: &DiskUsedConfig,
|
||||
show_disk_name: bool,
|
||||
add_separator: bool,
|
||||
percentage_char: &'_ str,
|
||||
) -> Result<Vec<Segment>, Box<dyn Error>> {
|
||||
let used_space = (disk.get_total_space() - disk.get_available_space()) as f64;
|
||||
let total_space = disk.get_total_space() as f64;
|
||||
|
||||
let formatted_usage = if config.show_percentage {
|
||||
format!(
|
||||
"{:.2}{}",
|
||||
(used_space / total_space) * 100f64,
|
||||
percentage_char
|
||||
)
|
||||
} else {
|
||||
format!("{}/{}", format_byte(used_space)?, format_byte(total_space)?)
|
||||
};
|
||||
|
||||
let threshold_config = config.threshold_styles.iter().find(|threshold_style| {
|
||||
(used_space / total_space) >= (threshold_style.threshold as f64 * 0.01f64)
|
||||
});
|
||||
|
||||
let style = match threshold_config {
|
||||
Some(threshold_config) => threshold_config.style,
|
||||
None => config.default_style,
|
||||
};
|
||||
|
||||
let parsed = StringFormatter::new(
|
||||
"([$name:]($default_style))[$usage]($style)([$separator]($default_style))",
|
||||
)
|
||||
.and_then(|formatter| {
|
||||
formatter
|
||||
.map_style(|variable| match variable {
|
||||
"style" => Some(Ok(style)),
|
||||
"default_style" => Some(Ok(config.default_style)),
|
||||
_ => None,
|
||||
})
|
||||
.map(|var| match var {
|
||||
"name" if show_disk_name => match get_disk_name(disk) {
|
||||
Some(name) => Some(Ok(name)),
|
||||
_ => None,
|
||||
},
|
||||
"usage" => Some(Ok(formatted_usage.as_str())),
|
||||
"separator" if add_separator => Some(Ok(config.separator)),
|
||||
_ => None,
|
||||
})
|
||||
.parse(None)
|
||||
});
|
||||
|
||||
match parsed {
|
||||
Ok(parsed) => Ok(parsed),
|
||||
Err(e) => Err(Box::new(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn should_display_disk(disk: &Disk, threshold: i64) -> bool {
|
||||
let percentage = (disk.get_total_space() - disk.get_available_space()) as f64
|
||||
/ disk.get_total_space() as f64;
|
||||
|
||||
percentage >= (threshold as f64 * 0.01f64)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DiskNotFoundError {
|
||||
path: PathBuf,
|
||||
}
|
||||
impl DiskNotFoundError {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
DiskNotFoundError { path }
|
||||
}
|
||||
}
|
||||
impl fmt::Display for DiskNotFoundError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "No disk found for path: {:?}!", self.path)
|
||||
}
|
||||
}
|
||||
impl Error for DiskNotFoundError {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_drive_from_path<'a>(path: &PathBuf, disks: &'a [Disk]) -> Result<&'a Disk, Box<dyn Error>> {
|
||||
use std::{ffi::OsString, io::Error, iter::once, os::windows::prelude::*};
|
||||
use winapi::um::fileapi::GetVolumePathNameW;
|
||||
|
||||
let path: Vec<u16> = path.as_os_str().encode_wide().chain(once(0)).collect();
|
||||
let mut volume_path_name: Vec<u16> = vec![0; 260];
|
||||
|
||||
let ret = unsafe {
|
||||
GetVolumePathNameW(
|
||||
path.as_ptr(),
|
||||
volume_path_name.as_mut_ptr(),
|
||||
volume_path_name.len() as u32,
|
||||
)
|
||||
};
|
||||
|
||||
if ret != 0 {
|
||||
// Strip out the nulls from the end
|
||||
while let Some(last) = volume_path_name.last() {
|
||||
if *last == 0 {
|
||||
volume_path_name.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let volume_path_name = OsString::from_wide(&volume_path_name);
|
||||
log::info!("Got path name: {:?}", volume_path_name);
|
||||
for disk in disks {
|
||||
if volume_path_name == disk.get_mount_point() {
|
||||
return Ok(disk);
|
||||
}
|
||||
}
|
||||
Err(Box::new(DiskNotFoundError::new(PathBuf::from(
|
||||
volume_path_name,
|
||||
))))
|
||||
} else {
|
||||
Err(Box::new(Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "macos"))]
|
||||
fn get_drive_from_path<'a>(path: &PathBuf, disks: &'a [Disk]) -> Result<&'a Disk, Box<dyn Error>> {
|
||||
use std::{fs, os::unix::fs::MetadataExt};
|
||||
|
||||
let meta = fs::metadata(path)?;
|
||||
let dev_id = meta.dev();
|
||||
|
||||
for disk in disks {
|
||||
let disk_meta = fs::metadata(disk.get_name())?;
|
||||
if disk_meta.rdev() == dev_id {
|
||||
return Ok(disk);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Box::new(DiskNotFoundError::new(path.to_owned())))
|
||||
}
|
||||
|
||||
/// Uses the device id to get the drive
|
||||
#[cfg(target_os = "linux")]
|
||||
fn get_drive_from_path<'a>(path: &PathBuf, disks: &'a [Disk]) -> Result<&'a Disk, Box<dyn Error>> {
|
||||
use std::{fs, os::linux::fs::MetadataExt};
|
||||
|
||||
let meta = fs::metadata(path)?;
|
||||
let dev_id = meta.st_dev();
|
||||
|
||||
for disk in disks {
|
||||
let disk_meta = fs::metadata(disk.get_name())?;
|
||||
if disk_meta.st_rdev() == dev_id {
|
||||
return Ok(disk);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Box::new(DiskNotFoundError::new(path.to_owned())))
|
||||
}
|
||||
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
let mut module = context.new_module("disk_used");
|
||||
let config = DiskUsedConfig::try_load(module.config);
|
||||
|
||||
if config.disabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
let percentage_char = match context.shell {
|
||||
Shell::Zsh => "%%", // % is an escape in zsh, see PROMPT in `man zshmisc`
|
||||
_ => "%",
|
||||
};
|
||||
|
||||
let mut current_disk = None;
|
||||
let mut current_storage = None;
|
||||
let mut other_storage: Option<Vec<Segment>> = None;
|
||||
let system = System::new_with_specifics(RefreshKind::new().with_disks().with_disks_list());
|
||||
let all_disks = system.get_disks();
|
||||
|
||||
if let Some(threshold) = config.current_threshold {
|
||||
match get_drive_from_path(&context.current_dir, all_disks) {
|
||||
Ok(disk) => {
|
||||
current_disk = Some(disk);
|
||||
if should_display_disk(disk, threshold) {
|
||||
match format_disk_used(
|
||||
&disk,
|
||||
&config,
|
||||
config.show_current_name,
|
||||
false,
|
||||
percentage_char,
|
||||
) {
|
||||
Ok(segments) => {
|
||||
if !segments.is_empty() {
|
||||
current_storage = Some(segments);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Couldn't format disk from current path: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Couldn't get disk from current path: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(threshold) = config.all_threshold {
|
||||
let display_disks: Vec<_> = all_disks
|
||||
.iter()
|
||||
.filter(|disk| match current_disk {
|
||||
Some(current) => disk.get_name() != current.get_name(),
|
||||
None => true,
|
||||
})
|
||||
.filter(|disk| should_display_disk(disk, threshold))
|
||||
.collect();
|
||||
let mut all_segments = Vec::new();
|
||||
|
||||
for (i, disk) in display_disks.iter().enumerate() {
|
||||
match format_disk_used(
|
||||
&disk,
|
||||
&config,
|
||||
true,
|
||||
display_disks.len() != i + 1,
|
||||
percentage_char,
|
||||
) {
|
||||
Ok(ref mut segments) => {
|
||||
all_segments.append(segments);
|
||||
}
|
||||
Err(e) => match get_disk_name(&disk) {
|
||||
Some(name) => log::warn!("Couldn't format disk {}: {}", name, e),
|
||||
None => log::warn!("Couldn't get disk name or do formatting: {}", e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if !all_segments.is_empty() {
|
||||
other_storage = Some(all_segments);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no storage data return None
|
||||
if current_storage.is_none() && other_storage.is_none() {
|
||||
return None;
|
||||
}
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
formatter
|
||||
.map_meta(|mvar, _| match mvar {
|
||||
"symbol" => Some(config.symbol),
|
||||
"prefix" => Some(config.prefix),
|
||||
_ => None,
|
||||
})
|
||||
.map_style(|variable| match variable {
|
||||
"style" => Some(Ok(config.default_style)),
|
||||
_ => None,
|
||||
})
|
||||
.map_variables_to_segments(|var| match var {
|
||||
"current_storage" => match ¤t_storage {
|
||||
Some(current_storage) => Some(Ok(current_storage.to_vec())),
|
||||
None => None,
|
||||
},
|
||||
"other_storage" => match &other_storage {
|
||||
Some(other_storage) => Some(Ok(other_storage.to_vec())),
|
||||
None => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.parse(None)
|
||||
});
|
||||
|
||||
module.set_segments(match parsed {
|
||||
Ok(segments) => segments,
|
||||
Err(error) => {
|
||||
log::warn!("Error in module `storage`:\n{}", error);
|
||||
return None;
|
||||
}
|
||||
});
|
||||
|
||||
Some(module)
|
||||
}
|
@ -17,6 +17,7 @@ mod dart;
|
||||
mod deno;
|
||||
mod directory;
|
||||
mod direnv;
|
||||
mod disk_used;
|
||||
mod docker_context;
|
||||
mod dotnet;
|
||||
mod elixir;
|
||||
@ -129,6 +130,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
||||
"deno" => deno::module(context),
|
||||
"directory" => directory::module(context),
|
||||
"direnv" => direnv::module(context),
|
||||
"disk_used" => disk_used::module(context),
|
||||
"docker_context" => docker_context::module(context),
|
||||
"dotnet" => dotnet::module(context),
|
||||
"elixir" => elixir::module(context),
|
||||
@ -253,6 +255,7 @@ pub fn description(module: &str) -> &'static str {
|
||||
"deno" => "The currently installed version of Deno",
|
||||
"directory" => "The current working directory",
|
||||
"direnv" => "The currently applied direnv file",
|
||||
"disk_used" => "Current disk used",
|
||||
"docker_context" => "The current docker context",
|
||||
"dotnet" => "The relevant version of the .NET Core SDK for the current directory",
|
||||
"elixir" => "The currently installed versions of Elixir and OTP",
|
||||
|
Loading…
Reference in New Issue
Block a user