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:
Sarthak Singh 2020-10-06 04:19:12 -07:00 committed by xxchan
parent 2021334206
commit 063016c177
No known key found for this signature in database
7 changed files with 435 additions and 0 deletions

View File

@ -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
View 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",
},
],
}
}
}

View File

@ -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;

View File

@ -108,6 +108,7 @@ pub const PROMPT_ORDER: &[&str] = &[
"meson",
"spack",
"memory_usage",
"disk_used",
"aws",
"gcloud",
"openstack",

View File

@ -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
View 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 &current_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)
}

View File

@ -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",