use std::fs::{File, OpenOptions};
use std::io::prelude::*;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::prelude::v1::String;
use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};
use serde::de::DeserializeOwned;
use crate::blocks::Block;
use crate::config::SharedConfig;
use crate::errors::*;
use crate::widgets::i3block_data::I3BlockData;
pub const USR_SHARE_PATH: &str = "/usr/share/i3status-rust";
pub fn pseudo_uuid() -> usize {
static ID: AtomicUsize = AtomicUsize::new(usize::MAX);
ID.fetch_sub(1, Ordering::SeqCst)
}
pub fn find_file(file: &str, subdir: Option<&str>, extension: Option<&str>) -> Option<PathBuf> {
let mut file = String::from(file);
if let Some(extension) = extension {
if !file.ends_with(extension) {
file.push_str(extension);
}
}
let full_path = PathBuf::from(&file);
let mut xdg_path = xdg_config_home().join("i3status-rust");
if let Some(subdir) = subdir {
xdg_path = xdg_path.join(subdir);
}
xdg_path = xdg_path.join(&file);
let mut share_path = PathBuf::from(USR_SHARE_PATH);
if let Some(subdir) = subdir {
share_path = share_path.join(subdir);
}
share_path = share_path.join(&file);
if full_path.exists() {
Some(full_path)
} else if xdg_path.exists() {
Some(xdg_path)
} else if share_path.exists() {
Some(share_path)
} else {
None
}
}
pub fn escape_pango_text(text: String) -> String {
text.chars()
.map(|x| match x {
'&' => "&".to_string(),
'<' => "<".to_string(),
'>' => ">".to_string(),
'\'' => "'".to_string(),
_ => x.to_string(),
})
.collect()
}
pub fn battery_level_to_icon(charge_level: Result<u64>) -> &'static str {
match charge_level {
Ok(0..=5) => "bat_empty",
Ok(6..=25) => "bat_quarter",
Ok(26..=50) => "bat_half",
Ok(51..=75) => "bat_three_quarters",
_ => "bat_full",
}
}
pub fn xdg_config_home() -> PathBuf {
let config_path = std::env::var("XDG_CONFIG_HOME").unwrap_or(format!(
"{}/.config",
std::env::var("HOME").unwrap_or_else(|_| "".to_string())
));
PathBuf::from(&config_path)
}
pub fn deserialize_file<T>(path: &Path) -> Result<T>
where
T: DeserializeOwned,
{
let file = path.to_str().unwrap();
let mut contents = String::new();
let mut file = BufReader::new(
File::open(file).internal_error("util", &format!("failed to open file: {}", file))?,
);
file.read_to_string(&mut contents)
.internal_error("util", "failed to read file")?;
toml::from_str(&contents).configuration_error("failed to parse TOML from file contents")
}
pub fn read_file(blockname: &str, path: &Path) -> Result<String> {
let mut f = OpenOptions::new().read(true).open(path).block_error(
blockname,
&format!("failed to open file {}", path.to_string_lossy()),
)?;
let mut content = String::new();
f.read_to_string(&mut content).block_error(
blockname,
&format!("failed to read {}", path.to_string_lossy()),
)?;
content.pop();
Ok(content)
}
pub fn has_command(block_name: &str, command: &str) -> Result<bool> {
let exit_status = Command::new("sh")
.args(&[
"-c",
format!("command -v {} >/dev/null 2>&1", command).as_ref(),
])
.status()
.block_error(
block_name,
format!("failed to start command to check for {}", command).as_ref(),
)?;
Ok(exit_status.success())
}
macro_rules! map {
($($key:expr => $value:expr),+ $(,)*) => {{
let mut m = ::std::collections::HashMap::new();
$(m.insert($key, $value);)+
m
}};
}
macro_rules! map_to_owned {
{ $($key:expr => $value:expr),+ } => {
{
let mut m = ::std::collections::HashMap::new();
$(
m.insert($key.to_owned(), $value.to_owned());
)+
m
}
};
}
pub fn print_blocks(blocks: &[Box<dyn Block>], config: &SharedConfig) -> Result<()> {
let mut last_bg: Option<String> = None;
let mut rendered_blocks = vec![];
let visible_count = blocks
.iter()
.filter(|block| !block.view().is_empty())
.count();
let mut alternator = visible_count % 2 == 0;
for block in blocks.iter() {
let widgets = block.view();
if widgets.is_empty() {
continue;
}
let mut rendered_widgets = widgets
.iter()
.map(|widget| {
let mut data = widget.get_data();
if alternator {
data.background = add_colors(
data.background.as_deref(),
config.theme.alternating_tint_bg.as_deref(),
)
.unwrap();
data.color = add_colors(
data.color.as_deref(),
config.theme.alternating_tint_bg.as_deref(),
)
.unwrap();
}
data
})
.collect::<Vec<I3BlockData>>();
alternator = !alternator;
if config.theme.native_separators == Some(true) {
rendered_widgets.last_mut().unwrap().separator = None;
rendered_widgets.last_mut().unwrap().separator_block_width = None;
}
let block_str = rendered_widgets
.iter()
.map(|w| w.render())
.collect::<Vec<String>>()
.join(",");
if config.theme.native_separators == Some(true) {
rendered_blocks.push(block_str.to_string());
continue;
}
let first_bg = rendered_widgets
.first()
.unwrap()
.background
.clone()
.internal_error("util", "couldn't get background color")?;
let sep_fg = if config.theme.separator_fg == Some("auto".to_string()) {
Some(first_bg.to_string())
} else {
config.theme.separator_fg.clone()
};
let sep_bg = if config.theme.separator_bg == Some("auto".to_string()) {
last_bg
} else {
config.theme.separator_bg.clone()
};
let mut separator = I3BlockData::default();
separator.full_text = config.theme.separator.clone();
separator.background = sep_bg;
separator.color = sep_fg;
rendered_blocks.push(format!("{},{}", separator.render(), block_str));
last_bg = Some(
rendered_widgets
.last()
.unwrap()
.background
.clone()
.internal_error("util", "couldn't get background color")?,
);
}
println!("[{}],", rendered_blocks.join(","));
Ok(())
}
pub fn color_from_rgba(
color: &str,
) -> ::std::result::Result<(u8, u8, u8, u8), Box<dyn std::error::Error>> {
Ok((
u8::from_str_radix(&color.get(1..3).ok_or("invalid rgba color")?, 16)?,
u8::from_str_radix(&color.get(3..5).ok_or("invalid rgba color")?, 16)?,
u8::from_str_radix(&color.get(5..7).ok_or("invalid rgba color")?, 16)?,
u8::from_str_radix(&color.get(7..9).unwrap_or("FF"), 16)?,
))
}
pub fn color_to_rgba(color: (u8, u8, u8, u8)) -> String {
format!(
"#{:02X}{:02X}{:02X}{:02X}",
color.0, color.1, color.2, color.3
)
}
pub fn add_colors(
a: Option<&str>,
b: Option<&str>,
) -> ::std::result::Result<Option<String>, Box<dyn std::error::Error>> {
match (a, b) {
(None, _) => Ok(None),
(Some(a), None) => Ok(Some(a.to_string())),
(Some(a), Some(b)) => {
let (r_a, g_a, b_a, a_a) = color_from_rgba(a)?;
let (r_b, g_b, b_b, a_b) = color_from_rgba(b)?;
Ok(Some(color_to_rgba((
r_a.saturating_add(r_b),
g_a.saturating_add(g_b),
b_a.saturating_add(b_b),
a_a.saturating_add(a_b),
))))
}
}
}
pub fn format_vec_to_bar_graph(content: &[f64], min: Option<f64>, max: Option<f64>) -> String {
static BARS: [char; 8] = [
'\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}',
'\u{2588}',
];
let mut min_v = std::f64::INFINITY;
let mut max_v = -std::f64::INFINITY;
for v in content {
if *v < min_v {
min_v = *v;
}
if *v > max_v {
max_v = *v;
}
}
let min = min.unwrap_or(min_v);
let max = max.unwrap_or(max_v);
let extant = max - min;
if extant.is_normal() {
let length = BARS.len() as f64 - 1.0;
content
.iter()
.map(|x| BARS[((x.clamp(min, max) - min) / extant * length) as usize])
.collect()
} else {
(0..content.len() - 1).map(|_| BARS[0]).collect::<_>()
}
}
#[cfg(test)]
mod tests {
use crate::util::{color_from_rgba, has_command};
#[test]
fn test_has_command_ok() {
let has_command = has_command("none", "sh");
assert!(has_command.is_ok());
let has_command = has_command.unwrap();
assert!(has_command);
}
#[test]
fn test_has_command_err() {
let has_command = has_command("none", "thequickbrownfoxjumpsoverthelazydog");
assert!(has_command.is_ok());
let has_command = has_command.unwrap();
assert!(!has_command)
}
#[test]
fn test_color_from_rgba() {
let valid_rgb = "#AABBCC";
let rgba = color_from_rgba(valid_rgb);
assert!(rgba.is_ok());
assert_eq!(rgba.unwrap(), (0xAA, 0xBB, 0xCC, 0xFF));
let valid_rgba = "#AABBCC00";
let rgba = color_from_rgba(valid_rgba);
assert!(rgba.is_ok());
assert_eq!(rgba.unwrap(), (0xAA, 0xBB, 0xCC, 0x00));
}
#[test]
fn test_color_from_rgba_invalid() {
let invalid = "invalid";
let rgba = color_from_rgba(invalid);
assert!(rgba.is_err());
let invalid = "AA";
let rgba = color_from_rgba(invalid);
assert!(rgba.is_err());
let invalid = "AABBCC";
let rgba = color_from_rgba(invalid);
assert!(rgba.is_err());
}
}