refactor: use clippy::unwrap_used (#1882)

* refactor: use clippy::unwrap_used

* fix log format use

* fix a bunch of macos ones

* oop

* allow schema script

* driveby schema bump
This commit is contained in:
Clement Tsang 2025-11-22 23:26:02 -05:00 committed by GitHub
parent 85af7ba50d
commit 86b35bcd79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 188 additions and 120 deletions

View File

@ -254,3 +254,4 @@ missing_crate_level_docs = "deny"
todo = "deny"
unimplemented = "deny"
missing_safety_doc = "deny"
unwrap_used = "deny"

View File

@ -1,5 +1,7 @@
//! General build script used by bottom to generate completion files and set binary version.
#![expect(clippy::unwrap_used)]
#[expect(dead_code)]
#[path = "src/options/args.rs"]
mod args;

View File

@ -1,3 +1,5 @@
cognitive-complexity-threshold = 100
type-complexity-threshold = 500
too-many-arguments-threshold = 8
allow-unwrap-in-consts = true
allow-unwrap-in-tests = true

View File

@ -471,6 +471,12 @@
}
]
},
"read_only": {
"type": [
"boolean",
"null"
]
},
"regex": {
"type": [
"boolean",

View File

@ -213,7 +213,7 @@ impl StoredData {
reason = "this is fine since it's done via a static OnceLock. In the future though, separate it out."
)]
if let Some(new_name) = DISK_REGEX
.get_or_init(|| Regex::new(r"disk\d+").unwrap())
.get_or_init(|| Regex::new(r"disk\d+").expect("valid regex"))
.find(checked_name)
{
io.get(new_name.as_str())

View File

@ -192,7 +192,7 @@ impl AppSearchState {
self.grapheme_cursor
.next_boundary(chunk, start_position)
.unwrap();
.expect("another grapheme boundary should exist after the cursor with the provided context");
}
_ => panic!("{err:?}"),
},
@ -213,7 +213,9 @@ impl AppSearchState {
self.grapheme_cursor
.provide_context(&self.current_search_query[0..ctx], 0);
self.grapheme_cursor.prev_boundary(chunk, 0).unwrap();
self.grapheme_cursor
.prev_boundary(chunk, 0)
.expect("another grapheme boundary should exist before the cursor with the provided context");
}
_ => panic!("{err:?}"),
},

View File

@ -1,4 +1,8 @@
#![cfg(feature = "generate_schema")]
#![expect(
clippy::unwrap_used,
reason = "this is just used to generate jsonschema files"
)]
use bottom::{options::config, widgets};
use clap::Parser;

View File

@ -674,17 +674,22 @@ impl<'a> TimeChart<'a> {
&self, buf: &mut Buffer, layout: &ChartLayout, chart_area: Rect, graph_area: Rect,
) {
let Some(y) = layout.label_x else { return };
let labels = self.x_axis.labels.as_ref().unwrap();
let Some(labels) = self.x_axis.labels.as_ref() else {
return;
};
let labels_len = labels.len() as u16;
if labels_len < 2 {
return;
}
let first_label = labels.first().expect("must have at least 2 labels");
let last_label = labels.last().expect("must have at least 2 labels");
let width_between_ticks = graph_area.width / labels_len;
let label_area = self.first_x_label_area(
y,
labels.first().unwrap().width() as u16,
first_label.width() as u16,
width_between_ticks,
chart_area,
graph_area,
@ -696,7 +701,7 @@ impl<'a> TimeChart<'a> {
Alignment::Right => Alignment::Left,
};
Self::render_label(buf, labels.first().unwrap(), label_area, label_alignment);
Self::render_label(buf, first_label, label_area, label_alignment);
for (i, label) in labels[1..labels.len() - 1].iter().enumerate() {
// We add 1 to x (and width-1 below) to leave at least one space before each
@ -710,7 +715,7 @@ impl<'a> TimeChart<'a> {
let x = graph_area.right() - width_between_ticks;
let label_area = Rect::new(x, y, width_between_ticks, 1);
// The last label should be aligned Right to be at the edge of the graph area
Self::render_label(buf, labels.last().unwrap(), label_area, Alignment::Right);
Self::render_label(buf, last_label, label_area, Alignment::Right);
}
fn first_x_label_area(
@ -751,8 +756,11 @@ impl<'a> TimeChart<'a> {
// FIXME: Control how many y-axis labels are rendered based on height.
let Some(x) = layout.label_y else { return };
let labels = self.y_axis.labels.as_ref().unwrap();
let Some(labels) = self.y_axis.labels.as_ref() else {
return;
};
let labels_len = labels.len() as u16;
for (i, label) in labels.iter().enumerate() {
let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1);
if dy < graph_area.bottom() {
@ -836,7 +844,7 @@ impl Widget for TimeChart<'_> {
.render(graph_area, buf);
if let Some((x, y)) = layout.title_x {
let title = self.x_axis.title.as_ref().unwrap();
if let Some(title) = self.x_axis.title.as_ref() {
let width = graph_area
.right()
.saturating_sub(x)
@ -852,9 +860,10 @@ impl Widget for TimeChart<'_> {
);
buf.set_line(x, y, title, width);
}
}
if let Some((x, y)) = layout.title_y {
let title = self.y_axis.title.as_ref().unwrap();
if let Some(title) = self.y_axis.title.as_ref() {
let width = graph_area
.right()
.saturating_sub(x)
@ -870,6 +879,7 @@ impl Widget for TimeChart<'_> {
);
buf.set_line(x, y, title, width);
}
}
if let Some(legend_area) = layout.legend_area {
buf.set_style(legend_area, original_style);

View File

@ -75,7 +75,7 @@ impl Grid for BrailleGrid {
}
fn save(&self) -> Layer {
let string = String::from_utf16(&self.utf16_code_points).unwrap();
let string = String::from_utf16(&self.utf16_code_points).expect("valid UTF-16 data");
// the background color is always reset for braille patterns
let colors = self.colors.iter().map(|c| (*c, Color::Reset)).collect();
Layer { string, colors }

View File

@ -18,8 +18,8 @@ impl Painter {
let current_table = if let BottomWidgetType::ProcSort = current_table.widget_type {
current_table
.right_neighbour
.map(|id| app_state.widget_map.get(&id).unwrap())
.unwrap()
.and_then(|id| app_state.widget_map.get(&id))
.expect("id must exist in widget mapping")
} else {
current_table
};

View File

@ -94,12 +94,12 @@ pub fn get_amd_name(device_path: &Path) -> Option<String> {
rev_data = rev_data.trim_end().to_string();
dev_data = dev_data.trim_end().to_string();
if rev_data.starts_with("0x") {
rev_data = rev_data.strip_prefix("0x").unwrap().to_string();
if let Some(stripped) = rev_data.strip_prefix("0x") {
rev_data = stripped.to_string();
}
if dev_data.starts_with("0x") {
dev_data = dev_data.strip_prefix("0x").unwrap().to_string();
if let Some(stripped) = dev_data.strip_prefix("0x") {
dev_data = stripped.to_string();
}
let revision_id = u32::from_str_radix(&rev_data, 16).unwrap_or(0);
@ -353,9 +353,8 @@ pub fn get_amd_vecs(widgets_to_harvest: &UsedWidgets, prev_time: Instant) -> Opt
if widgets_to_harvest.use_proc {
if let Some(procs) = get_amd_fdinfo(&device_path) {
let mut proc_info = PROC_DATA.lock().unwrap();
let _ = proc_info.try_insert(device_path.clone(), HashMap::new());
let prev_fdinfo = proc_info.get_mut(&device_path).unwrap();
let mut proc_info = PROC_DATA.lock().expect("mutex is poisoned");
let prev_fdinfo = proc_info.entry(device_path).or_insert_with(HashMap::new);
let mut procs_map = HashMap::new();
for (proc_pid, proc_usage) in procs {

View File

@ -117,11 +117,11 @@ pub(crate) trait UnixProcessExt {
.collect();
let cpu_usages = Self::backup_proc_cpu(&cpu_usage_unknown_pids)?;
for process in &mut process_vector {
if cpu_usages.contains_key(&process.pid) {
if let Some(&cpu_usage) = cpu_usages.get(&process.pid) {
process.cpu_usage_percent = if unnormalized_cpu || num_processors == 0 {
*cpu_usages.get(&process.pid).unwrap()
cpu_usage
} else {
*cpu_usages.get(&process.pid).unwrap() / num_processors as f32
cpu_usage / num_processors as f32
};
}
}

View File

@ -99,7 +99,7 @@ fn check_if_terminal() {
"Warning: bottom is not being output to a terminal. Things might not work properly."
);
eprintln!("If you're stuck, press 'q' or 'Ctrl-c' to quit the program.");
stderr().flush().unwrap();
stderr().flush().expect("should succeed in flushing stderr");
thread::sleep(Duration::from_secs(1));
}
}

View File

@ -526,7 +526,10 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL
basic_table_widget_state,
};
let current_widget = widget_map.get(&initial_widget_id).unwrap().clone();
let current_widget = widget_map
.get(&initial_widget_id)
.expect("widget map should have the initial widget ID")
.clone();
let filters = DataFilters {
disk_filter: disk_name_filter,
mount_filter: disk_mount_filter,
@ -568,14 +571,13 @@ fn get_widget_layout(
let rows = match &config.row {
Some(r) => r,
None => {
// This cannot (like it really shouldn't) fail!
ref_row = toml_edit::de::from_str::<Config>(if get_use_battery(args, config) {
DEFAULT_BATTERY_LAYOUT
} else {
DEFAULT_LAYOUT
})?
.row
.unwrap();
.expect("This cannot (like it really shouldn't) fail!");
&ref_row
}
};

View File

@ -131,7 +131,7 @@ pub struct Styles {
impl Default for Styles {
fn default() -> Self {
Self::default_style()
Self::default_palette()
}
}
@ -156,8 +156,8 @@ impl Styles {
fn from_theme(theme: &str) -> anyhow::Result<Self> {
let lower_case = theme.to_lowercase();
match lower_case.as_str() {
"default" => Ok(Self::default_style()),
"default-light" => Ok(Self::default_light_mode()),
"default" => Ok(Self::default_palette()),
"default-light" => Ok(Self::default_light_palette()),
"gruvbox" => Ok(Self::gruvbox_palette()),
"gruvbox-light" => Ok(Self::gruvbox_light_palette()),
"nord" => Ok(Self::nord_palette()),
@ -246,8 +246,9 @@ mod test {
#[test]
fn default_selected_colour_works() {
let mut colours = Styles::default();
let original_selected_text_colour = Styles::default_style().selected_text_style.fg.unwrap();
let original_selected_bg_colour = Styles::default_style().selected_text_style.bg.unwrap();
let original_selected_text_colour =
Styles::default_palette().selected_text_style.fg.unwrap();
let original_selected_bg_colour = Styles::default_palette().selected_text_style.bg.unwrap();
assert_eq!(
colours.selected_text_style,

View File

@ -4,18 +4,30 @@ pub(super) mod default;
pub(super) mod gruvbox;
pub(super) mod nord;
/// Convert a [`tui::style::Color`] into a [`tui::style::Style`] with the color as the foreground.
macro_rules! color {
($value:expr) => {
tui::style::Style::new().fg($value)
};
}
/// Convert a hex string to a [`tui::style::Style`], where the hex string is used as the foreground color.
macro_rules! hex {
($value:literal) => {
tui::style::Style::new()
.fg(crate::options::config::style::utils::convert_hex_to_color($value.into()).unwrap())
tui::style::Style::new().fg(crate::options::config::style::utils::try_hex_to_colour(
$value.into(),
)
.expect("valid hex"))
};
}
/// Convert a hex string to a [`tui::style::Color`].
macro_rules! hex_colour {
($value:literal) => {
crate::options::config::style::utils::try_hex_to_colour($value.into()).expect("valid hex")
};
}
pub(super) use color;
pub(super) use hex;
pub(super) use hex_colour;

View File

@ -7,7 +7,7 @@ use super::color;
use crate::options::config::style::Styles;
impl Styles {
pub(crate) fn default_style() -> Self {
pub(crate) fn default_palette() -> Self {
const FIRST_COLOUR: Color = Color::LightMagenta;
const SECOND_COLOUR: Color = Color::LightYellow;
const THIRD_COLOUR: Color = Color::LightCyan;
@ -72,7 +72,7 @@ impl Styles {
}
}
pub fn default_light_mode() -> Self {
pub fn default_light_palette() -> Self {
Self {
ram_style: color!(Color::Blue),
#[cfg(not(target_os = "windows"))]
@ -111,7 +111,15 @@ impl Styles {
graph_style: color!(Color::Black),
graph_legend_style: color!(Color::Black),
disabled_text_style: color!(Color::Gray),
..Self::default_style()
..Self::default_palette()
}
}
}
mod tests {
#[test]
fn default_palettes_valid() {
let _ = super::Styles::default_palette();
let _ = super::Styles::default_light_palette();
}
}

View File

@ -4,7 +4,7 @@ use tui::{
};
use super::{color, hex};
use crate::options::config::style::{Styles, utils::convert_hex_to_color};
use crate::options::config::style::{Styles, themes::hex_colour};
impl Styles {
pub(crate) fn gruvbox_palette() -> Self {
@ -56,7 +56,7 @@ impl Styles {
border_style: hex!("#ebdbb2"),
highlighted_border_style: hex!("#fe8019"),
text_style: hex!("#ebdbb2"),
selected_text_style: hex!("#1d2021").bg(convert_hex_to_color("#ebdbb2").unwrap()),
selected_text_style: hex!("#1d2021").bg(hex_colour!("#ebdbb2")),
table_header_style: hex!("#83a598").add_modifier(Modifier::BOLD),
widget_title_style: hex!("#ebdbb2"),
graph_style: hex!("#ebdbb2"),
@ -121,7 +121,7 @@ impl Styles {
border_style: hex!("#3c3836"),
highlighted_border_style: hex!("#af3a03"),
text_style: hex!("#3c3836"),
selected_text_style: hex!("#ebdbb2").bg(convert_hex_to_color("#3c3836").unwrap()),
selected_text_style: hex!("#ebdbb2").bg(hex_colour!("#3c3836")),
table_header_style: hex!("#076678").add_modifier(Modifier::BOLD),
widget_title_style: hex!("#3c3836"),
graph_style: hex!("#3c3836"),
@ -137,3 +137,12 @@ impl Styles {
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn gruvbox_palettes_valid() {
let _ = super::Styles::gruvbox_palette();
let _ = super::Styles::gruvbox_light_palette();
}
}

View File

@ -4,7 +4,7 @@ use tui::{
};
use super::{color, hex};
use crate::options::config::style::{Styles, utils::convert_hex_to_color};
use crate::options::config::style::{Styles, themes::hex_colour};
impl Styles {
pub(crate) fn nord_palette() -> Self {
@ -44,7 +44,7 @@ impl Styles {
border_style: hex!("#88c0d0"),
highlighted_border_style: hex!("#5e81ac"),
text_style: hex!("#e5e9f0"),
selected_text_style: hex!("#2e3440").bg(convert_hex_to_color("#88c0d0").unwrap()),
selected_text_style: hex!("#2e3440").bg(hex_colour!("#88c0d0")),
table_header_style: hex!("#81a1c1").add_modifier(Modifier::BOLD),
widget_title_style: hex!("#e5e9f0"),
graph_style: hex!("#e5e9f0"),
@ -97,7 +97,7 @@ impl Styles {
border_style: hex!("#2e3440"),
highlighted_border_style: hex!("#5e81ac"),
text_style: hex!("#2e3440"),
selected_text_style: hex!("#f5f5f5").bg(convert_hex_to_color("#5e81ac").unwrap()),
selected_text_style: hex!("#f5f5f5").bg(hex_colour!("#5e81ac")),
table_header_style: hex!("#5e81ac").add_modifier(Modifier::BOLD),
widget_title_style: hex!("#2e3440"),
graph_style: hex!("#2e3440"),
@ -113,3 +113,12 @@ impl Styles {
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn nord_palettes_valid() {
let _ = super::Styles::nord_palette();
let _ = super::Styles::nord_light_palette();
}
}

View File

@ -3,7 +3,7 @@ use tui::style::Color;
use unicode_segmentation::UnicodeSegmentation;
/// Convert a hex string to a colour.
pub(super) fn convert_hex_to_color(hex: &str) -> Result<Color, String> {
pub(super) fn try_hex_to_colour(hex: &str) -> Result<Color, String> {
fn hex_component_to_int(hex: &str, first: &str, second: &str) -> Result<u8, String> {
u8::from_str_radix(&concat_string!(first, second), 16)
.map_err(|_| format!("'{hex}' is an invalid hex color, could not decode."))
@ -42,7 +42,7 @@ pub(super) fn convert_hex_to_color(hex: &str) -> Result<Color, String> {
pub fn str_to_colour(input_val: &str) -> Result<Color, String> {
if input_val.len() > 1 {
if input_val.starts_with('#') {
convert_hex_to_color(input_val)
try_hex_to_colour(input_val)
} else if input_val.contains(',') {
convert_rgb_to_color(input_val)
} else {
@ -336,72 +336,66 @@ mod test {
fn valid_hex_colours() {
// Check hex with 6 characters.
assert_eq!(
convert_hex_to_color("#ffffff").unwrap(),
try_hex_to_colour("#ffffff").unwrap(),
Color::Rgb(255, 255, 255)
);
assert_eq!(try_hex_to_colour("#000000").unwrap(), Color::Rgb(0, 0, 0));
try_hex_to_colour("#111111").unwrap();
try_hex_to_colour("#11ff11").unwrap();
try_hex_to_colour("#1f1f1f").unwrap();
assert_eq!(
convert_hex_to_color("#000000").unwrap(),
Color::Rgb(0, 0, 0)
);
convert_hex_to_color("#111111").unwrap();
convert_hex_to_color("#11ff11").unwrap();
convert_hex_to_color("#1f1f1f").unwrap();
assert_eq!(
convert_hex_to_color("#123abc").unwrap(),
try_hex_to_colour("#123abc").unwrap(),
Color::Rgb(18, 58, 188)
);
// Check hex with 3 characters.
assert_eq!(
convert_hex_to_color("#fff").unwrap(),
try_hex_to_colour("#fff").unwrap(),
Color::Rgb(255, 255, 255)
);
assert_eq!(convert_hex_to_color("#000").unwrap(), Color::Rgb(0, 0, 0));
convert_hex_to_color("#111").unwrap();
convert_hex_to_color("#1f1").unwrap();
convert_hex_to_color("#f1f").unwrap();
convert_hex_to_color("#ff1").unwrap();
convert_hex_to_color("#1ab").unwrap();
assert_eq!(
convert_hex_to_color("#1ab").unwrap(),
Color::Rgb(17, 170, 187)
);
assert_eq!(try_hex_to_colour("#000").unwrap(), Color::Rgb(0, 0, 0));
try_hex_to_colour("#111").unwrap();
try_hex_to_colour("#1f1").unwrap();
try_hex_to_colour("#f1f").unwrap();
try_hex_to_colour("#ff1").unwrap();
try_hex_to_colour("#1ab").unwrap();
assert_eq!(try_hex_to_colour("#1ab").unwrap(), Color::Rgb(17, 170, 187));
}
#[test]
fn invalid_hex_colours() {
assert!(convert_hex_to_color("ffffff").is_err());
assert!(convert_hex_to_color("111111").is_err());
assert!(try_hex_to_colour("ffffff").is_err());
assert!(try_hex_to_colour("111111").is_err());
assert!(convert_hex_to_color("fff").is_err());
assert!(convert_hex_to_color("111").is_err());
assert!(convert_hex_to_color("fffffff").is_err());
assert!(convert_hex_to_color("1234567").is_err());
assert!(try_hex_to_colour("fff").is_err());
assert!(try_hex_to_colour("111").is_err());
assert!(try_hex_to_colour("fffffff").is_err());
assert!(try_hex_to_colour("1234567").is_err());
assert!(convert_hex_to_color("#fffffff").is_err());
assert!(convert_hex_to_color("#1234567").is_err());
assert!(convert_hex_to_color("#ff").is_err());
assert!(convert_hex_to_color("#12").is_err());
assert!(convert_hex_to_color("").is_err());
assert!(try_hex_to_colour("#fffffff").is_err());
assert!(try_hex_to_colour("#1234567").is_err());
assert!(try_hex_to_colour("#ff").is_err());
assert!(try_hex_to_colour("#12").is_err());
assert!(try_hex_to_colour("").is_err());
assert!(convert_hex_to_color("#pppppp").is_err());
assert!(convert_hex_to_color("#00000p").is_err());
assert!(convert_hex_to_color("#ppp").is_err());
assert!(try_hex_to_colour("#pppppp").is_err());
assert!(try_hex_to_colour("#00000p").is_err());
assert!(try_hex_to_colour("#ppp").is_err());
assert!(convert_hex_to_color("#一").is_err());
assert!(convert_hex_to_color("#一二").is_err());
assert!(convert_hex_to_color("#一二三").is_err());
assert!(convert_hex_to_color("#一二三四").is_err());
assert!(try_hex_to_colour("#一").is_err());
assert!(try_hex_to_colour("#一二").is_err());
assert!(try_hex_to_colour("#一二三").is_err());
assert!(try_hex_to_colour("#一二三四").is_err());
assert!(convert_hex_to_color("#f一f").is_err());
assert!(convert_hex_to_color("#ff一11").is_err());
assert!(try_hex_to_colour("#f一f").is_err());
assert!(try_hex_to_colour("#ff一11").is_err());
assert!(convert_hex_to_color("#🇨🇦").is_err());
assert!(convert_hex_to_color("#🇨🇦🇨🇦").is_err());
assert!(convert_hex_to_color("#🇨🇦🇨🇦🇨🇦").is_err());
assert!(convert_hex_to_color("#🇨🇦🇨🇦🇨🇦🇨🇦").is_err());
assert!(try_hex_to_colour("#🇨🇦").is_err());
assert!(try_hex_to_colour("#🇨🇦🇨🇦").is_err());
assert!(try_hex_to_colour("#🇨🇦🇨🇦🇨🇦").is_err());
assert!(try_hex_to_colour("#🇨🇦🇨🇦🇨🇦🇨🇦").is_err());
assert!(convert_hex_to_color("#हिन्दी").is_err());
assert!(try_hex_to_colour("#हिन्दी").is_err());
}
#[test]

View File

@ -27,7 +27,7 @@ pub fn init_logger(
// one "[". See https://time-rs.github.io/book/api/format-description.html
"[[[year]-[month]-[day]][[[hour]:[minute]:[second][subsecond digits:9]]"
))
.unwrap(),
.expect("log formatting shouldn't fail"),
record.target(),
record.level(),
message

View File

@ -641,7 +641,11 @@ impl ProcWidgetState {
stack.sort_unstable_by_key(|p| p.pid);
let column = self.table.columns.get(self.table.sort_index()).unwrap();
let column = self
.table
.columns
.get(self.table.sort_index())
.expect("columns should contain the current sort index");
sort_skip_pid_asc(column.inner(), &mut stack, self.table.order());
let mut length_stack = vec![stack.len()];

View File

@ -1,5 +1,8 @@
//! Integration tests for bottom.
#![allow(clippy::unwrap_used)]
#![allow(missing_docs)]
mod util;
mod arg_tests;