Improve API ergonomics and add optional additional derives

This commit is contained in:
NGnius (Graham) 2023-08-05 22:52:35 -04:00
parent 346341b804
commit 16f32e7e9d
10 changed files with 207 additions and 35 deletions

View file

@ -6,3 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[features]
default = []
derive = []

View file

@ -5,7 +5,7 @@ use std::str::FromStr;
use crate::EitherErr2;
/// sysfs entity attribute file functionality
pub trait SysAttribute: Eq {
pub trait SysAttribute {
/// attribute file name
fn filename(&self) -> PathBuf;
@ -29,6 +29,11 @@ pub trait SysAttribute: Eq {
fn write_value(&self, entity: &dyn crate::SysEntity, value: &[u8]) -> IoResult<()> {
std::fs::write(self.path(entity), value)
}
/// Returns true if the attribute is readonly
fn readonly(&self, entity: &dyn crate::SysEntity) -> bool {
self.path(entity).metadata().map(|meta| meta.permissions().readonly()).unwrap_or(false)
}
}
impl SysAttribute for String {
@ -37,8 +42,14 @@ impl SysAttribute for String {
}
}
impl SysAttribute for &str {
fn filename(&self) -> PathBuf {
PathBuf::from(self)
}
}
/// sysfs entity attribute functionality extension
pub trait SysAttributeExt: SysAttribute {
pub trait SysAttributeExt: SysAttribute + Eq {
/// read attribute as string
fn read_str(&self, entity: &dyn crate::SysEntity) -> IoResult<String> {
std::fs::read_to_string(self.path(entity))
@ -56,4 +67,4 @@ pub trait SysAttributeExt: SysAttribute {
}
}
impl <X: SysAttribute> SysAttributeExt for X {}
impl <X: SysAttribute + Eq> SysAttributeExt for X {}

View file

@ -5,6 +5,7 @@ use std::io::Result as IoResult;
const SYS_CLASS_PATH: &str = "sys/class";
/// Generic entity path with basic functionality
#[cfg_attr(feature = "derive", derive(Debug, Clone))]
pub struct BasicEntityPath {
path: PathBuf
}
@ -61,7 +62,7 @@ impl crate::SysEntity for BasicEntityPath {
}
}
impl crate::SysEntityAttributes<String, String, <String as std::str::FromStr>::Err> for BasicEntityPath {
impl crate::SysEntityAttributes<String> for BasicEntityPath {
fn capabilities(&self) -> Vec<String> {
if let Ok(dir_iter) = self.path.read_dir() {
dir_iter.filter_map(

53
src/capability.rs Normal file
View file

@ -0,0 +1,53 @@
//! sysfs class entity capability filtering
/// Capabilities filter
pub trait Capabilities<A: crate::SysAttribute, R: std::borrow::Borrow<A>> {
/// Are the required capabilities (self) satisfied by the provided capabilities (the parameter)?
fn can(&mut self, capabilities: &Vec<R>) -> bool;
}
impl <F: FnMut(&Vec<R>) -> bool, A: crate::SysAttribute, R: std::borrow::Borrow<A>> Capabilities<A, R> for F {
fn can(&mut self, capabilities: &Vec<R>) -> bool {
(self)(capabilities)
}
}
/// Build a Capabilities implementor which needs specific attributes to exist
pub fn attributes<'a, A: crate::SysAttribute + Eq + 'a, R: std::borrow::Borrow<A> + 'a>(attributes: impl Iterator<Item=R>) -> impl Capabilities<A, R> + 'a {
AllNeedsCapabilities::new(
attributes.map(|attr: R| { move |other_attr: &R| attr.borrow() == other_attr.borrow() }).collect()
)
}
struct AllNeedsCapabilities<'a, 'f, A: crate::SysAttribute + 'a, R: std::borrow::Borrow<A> + 'a, F: (FnMut(&R) -> bool) + 'f> {
wanted: Vec<F>,
_attr_ty: std::marker::PhantomData<A>,
_attr_brw_ty: std::marker::PhantomData<R>,
_life_attr_ty: std::marker::PhantomData<&'a ()>,
_life_fn_ty: std::marker::PhantomData<&'f ()>,
}
impl <'a, 'f, A: crate::SysAttribute + 'a, R: std::borrow::Borrow<A> + 'a, F: (FnMut(&R) -> bool) + 'f> AllNeedsCapabilities<'a, 'f, A, R, F> {
pub(crate) fn new(wanted: Vec<F>) -> Self {
Self {
wanted,
_attr_ty: Default::default(),
_attr_brw_ty: Default::default(),
_life_attr_ty: Default::default(),
_life_fn_ty: Default::default(),
}
}
}
impl <'a, 'f, A: crate::SysAttribute + 'a, R: std::borrow::Borrow<A> + 'a, F: (FnMut(&R) -> bool) + 'f> Capabilities<A, R> for AllNeedsCapabilities<'a, 'f, A, R, F> {
fn can(&mut self, capabilities: &Vec<R>) -> bool {
let mut wants = vec![false; self.wanted.len()];
for capability in capabilities {
if let Some(pos) = self.wanted.iter_mut().position(|w| w(capability)) {
wants[pos] = true;
}
}
wants.into_iter().all(|item| item)
}
}

View file

@ -6,18 +6,39 @@ use crate::EitherErr2;
use crate::SysAttributeExt;
/// sysfs class entity functionality
#[cfg(feature = "derive")]
pub trait SysEntity: AsRef<Path> + core::fmt::Debug {
/// Convert specialized entity into general entity path container
fn to_entity_path(self) -> EntityPath;
/// Get the entity's name
fn name(&self) -> IoResult<String>;
/// Try to get the entity's root sysfs dir (probably `/`)
fn root(&self) -> Option<crate::SysPath> {
crate::SysPath::from_entity_path(self)
}
}
/// sysfs class entity functionality
#[cfg(not(feature = "derive"))]
pub trait SysEntity: AsRef<Path> {
/// Convert specialized entity into general entity path container
fn to_entity_path(self) -> EntityPath;
/// Get the entity's name
fn name(&self) -> IoResult<String>;
/// Try to get the entity's root sysfs dir (probably `/`)
fn root(&self) -> Option<crate::SysPath> {
crate::SysPath::from_entity_path(self)
}
}
/// sysfs class entity functionality extension
pub trait SysEntityRawExt: SysEntity {
/// Get an attribute on the entity
fn attribute<A: crate::SysAttribute, T: std::str::FromStr<Err=E>, E>(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>>;
fn attribute<A: crate::SysAttribute + Eq, T: std::str::FromStr<Err=E>, E>(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>>;
/// Get an attribute by filename in the entity's directory
fn attribute_str<A: AsRef<Path>>(&self, attr: A) -> IoResult<String>;
@ -29,7 +50,7 @@ pub trait SysEntityRawExt: SysEntity {
}
impl <X: SysEntity> SysEntityRawExt for X {
fn attribute<A: crate::SysAttribute, T: std::str::FromStr<Err=E>, E>(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>> {
fn attribute<A: crate::SysAttribute + Eq, T: std::str::FromStr<Err=E>, E>(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>> {
attr.parse(self)
}
@ -48,21 +69,26 @@ impl <X: SysEntity> SysEntityRawExt for X {
/// sysfs class entity attribute type indicator
pub trait SysEntityAttributes<A: crate::SysAttribute, T: std::str::FromStr<Err=E>, E>: SysEntity + Sized {
/// Get an attribute on the entity
fn attribute(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>> {
attr.parse(self)
}
pub trait SysEntityAttributes<A: crate::SysAttribute + Eq>: SysEntity + Sized {
/// Get attributes available on this entity;
fn capabilities(&self) -> Vec<A>;
}
/*pub trait SysEntityAttributesExt<A: crate::SysAttribute, T: std::str::FromStr<Err=E>, E>: SysEntityAttributes<A, T, E> {
fn attribute()
}*/
pub trait SysEntityAttributesExt<A: crate::SysAttribute + Eq>: SysEntityAttributes<A> {
fn capable<C: crate::capability::Capabilities<A, A>>(&self, mut capabilities: C) -> bool {
capabilities.can(&self.capabilities())
}
/// Get an attribute on the entity
fn attribute<T: std::str::FromStr<Err=E>, E>(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>> {
attr.parse(self)
}
}
impl <A: crate::SysAttribute + Eq, X: SysEntityAttributes<A>> SysEntityAttributesExt<A> for X {}
/// sysfs class entity implementors
#[cfg_attr(feature = "derive", derive(Debug, Clone))]
pub enum EntityPath {
/// hwmon
HwMon(crate::HwMonPath),
@ -71,7 +97,7 @@ pub enum EntityPath {
/// Generic
Generic(crate::BasicEntityPath),
/// Miscellaneous
Custom(Box<dyn SysEntity>),
Custom(std::sync::Arc<Box<dyn SysEntity>>),
}
impl EntityPath {
@ -80,19 +106,14 @@ impl EntityPath {
Self::HwMon(inner) => inner,
Self::PowerSupply(inner) => inner,
Self::Generic(inner) => inner,
Self::Custom(inner) => inner.as_ref(),
Self::Custom(inner) => inner.as_ref().as_ref(),
}
}
}
impl AsRef<Path> for EntityPath {
fn as_ref(&self) -> &Path {
match self {
Self::HwMon(inner) => inner.as_ref(),
Self::PowerSupply(inner) => inner.as_ref(),
Self::Generic(inner) => inner.as_ref(),
Self::Custom(inner) => inner.as_ref().as_ref(),
}
self.inner().as_ref()
}
}

View file

@ -1,5 +1,5 @@
/// Multi-error convenience enum
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum EitherErr2<T1, T2> {
/// Type 1
First(T1),
@ -32,7 +32,7 @@ impl <T1: std::error::Error, T2: std::error::Error> std::error::Error for Either
/// Value parse error for an enumeration.
/// This usually indicates an unexpected value (one without an enum variant) was received while parsing.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ValueEnumError<'a> {
pub(crate) got: String,
pub(crate) allowed: &'a [&'a str],

View file

@ -7,11 +7,13 @@ const HWMON_DIR_PATH: &'static str = "sys/class/hwmon/";
/// Attribute representation.
/// Attribute files are usually in the format `<type><number>_<item>`.
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive", derive(Debug))]
pub struct HwMonAttribute {
inner: HwMonAttributeInner,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive", derive(Debug))]
enum HwMonAttributeInner {
Name,
Standard {
@ -20,6 +22,7 @@ enum HwMonAttributeInner {
item: HwMonAttributeItem,
},
Uevent,
Custom(&'static str),
}
impl std::str::FromStr for HwMonAttributeInner {
@ -46,6 +49,7 @@ impl std::str::FromStr for HwMonAttributeInner {
/// Attribute type in the format `<type><number>_<item>`
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive", derive(Debug))]
pub enum HwMonAttributeType {
/// Voltage
In,
@ -84,6 +88,7 @@ impl HwMonAttributeType {
/// Attribute item in the format `<type><number>_<item>`
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive", derive(Debug))]
pub enum HwMonAttributeItem {
/// Input
Input,
@ -137,11 +142,19 @@ impl HwMonAttribute {
}
}
/// Custom entity attribute
pub const fn custom(attr: &'static str) -> Self {
Self {
inner: HwMonAttributeInner::Custom(attr)
}
}
fn to_attr_str(&self) -> String {
match self.inner {
HwMonAttributeInner::Name => "name".to_string(),
HwMonAttributeInner::Standard { ty, number, item } => format!("{}{}_{}", ty.to_attr_str(), number, item.to_attr_str()),
HwMonAttributeInner::Uevent => "uevent".to_string(),
HwMonAttributeInner::Custom(s) => s.to_owned(),
}
}
}
@ -153,6 +166,7 @@ impl crate::SysAttribute for HwMonAttribute {
}
/// hwmon<number>/ directory
#[cfg_attr(feature = "derive", derive(Debug, Clone))]
pub struct HwMonPath {
path: PathBuf
}
@ -226,7 +240,7 @@ impl crate::SysEntity for HwMonPath {
}
}
impl crate::SysEntityAttributes<HwMonAttribute, String, <String as std::str::FromStr>::Err> for HwMonPath {
impl crate::SysEntityAttributes<HwMonAttribute> for HwMonPath {
fn capabilities(&self) -> Vec<HwMonAttribute> {
if let Ok(dir_iter) = self.path.read_dir() {
dir_iter.filter_map(

View file

@ -10,6 +10,8 @@ pub use attribute::{SysAttribute, SysAttributeExt};
mod basic;
pub use basic::BasicEntityPath;
pub mod capability;
mod entity;
pub use entity::{EntityPath, SysEntity, SysEntityRawExt, SysEntityAttributes};

View file

@ -11,7 +11,8 @@ const CLASS_PATH: &'static str = "power_supply";
/// Attribute files in a power supply directory
/// These attributes are taken from https://github.com/torvalds/linux/blob/master/Documentation/ABI/testing/sysfs-class-power
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive", derive(Debug))]
pub enum PowerSupplyAttribute {
/// Reports the name of the device manufacturer.
Manufacturer,
@ -65,6 +66,12 @@ pub enum PowerSupplyAttribute {
ChargeType,
/// Reports the charging current value which is used to determine when the battery is considered full and charging should end (uA).
ChargeTermCurrent,
/// Reports the instantaneous charge value of the power supply (uA).
ChargeNow,
/// Reports the maximum charge value of the power supply (uA).
ChargeFull,
/// Reports the maximum rated charge value of the power supply (uA).
ChargeFullDesign,
/// Reports the health of the battery or battery side of charger functionality ( "Unknown", "Good", "Overheat", "Dead", "Over voltage", "Unspecified failure", "Cold", "Watchdog timer expire", "Safety timer expire", "Over current", "Calibration required", "Warm", "Cool", "Hot", or "No battery").
Health,
/// Reports the charging current applied during pre-charging phase for a battery charge cycle (uA).
@ -82,6 +89,8 @@ pub enum PowerSupplyAttribute {
/// Reports the number of full charge + discharge cycles the battery has undergone.
CycleCount,
// TODO USB properties
/// Custom entity attribute
Custom(&'static str)
}
impl crate::SysAttribute for PowerSupplyAttribute {
@ -113,6 +122,9 @@ impl crate::SysAttribute for PowerSupplyAttribute {
Self::ChargeControlEndThreshold => "charge_control_end_threshold",
Self::ChargeType => "charge_type",
Self::ChargeTermCurrent => "charge_term_current",
Self::ChargeNow => "charge_now",
Self::ChargeFull => "charge_full",
Self::ChargeFullDesign => "charge_full_design",
Self::Health => "health",
Self::PrechargeCurrent => "precharge_current",
Self::Present => "present",
@ -121,13 +133,15 @@ impl crate::SysAttribute for PowerSupplyAttribute {
Self::Technology => "technology",
Self::VoltageAverage => "voltage_avg",
Self::CycleCount => "cycle_count",
Self::Custom(s) => s,
};
s.into()
}
}
/// power_supply/<entity>/type attribute value
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive", derive(Debug))]
pub enum PowerSupplyType {
/// Battery
Battery,
@ -184,6 +198,7 @@ impl std::str::FromStr for PowerSupplyType {
}
/// power_supply/<entity>/ directory
#[cfg_attr(feature = "derive", derive(Debug, Clone))]
pub struct PowerSupplyPath {
inner: crate::BasicEntityPath,
}
@ -229,7 +244,7 @@ impl crate::SysEntity for PowerSupplyPath {
}
}
impl crate::SysEntityAttributes<PowerSupplyAttribute, String, <String as std::str::FromStr>::Err> for PowerSupplyPath {
impl crate::SysEntityAttributes<PowerSupplyAttribute> for PowerSupplyPath {
fn capabilities(&self) -> Vec<PowerSupplyAttribute> {
self.inner.filter_capabilities(vec![
PowerSupplyAttribute::Manufacturer,
@ -278,6 +293,10 @@ mod tests {
#[test]
fn power_supply_all() -> std::io::Result<()> {
if std::fs::read_dir("/sys/class/power_supply")?.count() == 0 {
eprintln!("power_supply test skipped since none exist (maybe running on a desktop PC?)");
return Ok(())
}
let sys = crate::SysPath::default();
let all_psu: Vec<_> = PowerSupplyPath::all(sys)?.collect();
assert!(!all_psu.is_empty());

View file

@ -1,9 +1,10 @@
use std::path::{Path, PathBuf};
use std::convert::AsRef;
use crate::SysEntity;
use crate::{SysEntity, SysEntityAttributes};
/// sysfs root
#[cfg_attr(feature = "derive", derive(Debug))]
pub struct SysPath {
root: PathBuf,
}
@ -35,16 +36,42 @@ impl SysPath {
}
/// Find a hardware monitor entry by index
pub fn hwmon_by_index(&self, index: u64) -> std::io::Result<crate::HwMonPath> {
pub fn hwmon_by_index(&self, index: u64) -> crate::HwMonPath {
let entry = crate::HwMonPath::entry(self, index);
entry.attribute(crate::HwMonAttribute::name())?;
Ok(entry)
//entry.attribute(crate::HwMonAttribute::name())?;
entry
}
/// Find a hardware monitor by capabilities
pub fn hwmon(&self, mut c: impl crate::capability::Capabilities<crate::HwMonAttribute, crate::HwMonAttribute>) -> std::io::Result<impl Iterator<Item=crate::HwMonPath>> {
Ok(crate::HwMonPath::all(self.root.clone())?
.filter_map(|p| p.ok())
.filter(move |p| c.can(&p.capabilities())))
}
/// Find a power supply entry by name
pub fn power_supply(&self, name: &str) -> crate::PowerSupplyPath {
pub fn power_supply_by_name(&self, name: &str) -> crate::PowerSupplyPath {
crate::PowerSupplyPath::name(self, name)
}
/// Find a power supply by capabilities
pub fn power_supply(&self, mut c: impl crate::capability::Capabilities<crate::PowerSupplyAttribute, crate::PowerSupplyAttribute>) -> std::io::Result<impl Iterator<Item=crate::PowerSupplyPath>> {
Ok(crate::PowerSupplyPath::all(self.root.clone())?
.filter_map(|p| p.ok())
.filter(move |p| c.can(&p.capabilities())))
}
/// Find entities in a sysfs class by capabilities
pub fn class(&self, class: impl AsRef<Path>, mut c: impl crate::capability::Capabilities<String, String>) -> std::io::Result<impl Iterator<Item=crate::BasicEntityPath>> {
Ok(crate::BasicEntityPath::all(self.root.clone(), class)?
.filter_map(|p| p.ok())
.filter(move |p| c.can(&p.capabilities()))
)
}
pub(crate) fn from_entity_path(entity: impl AsRef<Path>) -> Option<Self> {
Some(Self::path(entity.as_ref().parent()?.parent()?.parent()?.parent()?))
}
}
impl std::default::Default for SysPath {
@ -60,3 +87,23 @@ impl AsRef<Path> for SysPath {
self.root.as_path()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SysEntity;
#[test]
fn syspath_root_recovery() -> std::io::Result<()> {
let sys = crate::SysPath::default();
let all_ent: Vec<_> = crate::BasicEntityPath::all(&sys, "hwmon")?.collect();
assert!(!all_ent.is_empty());
for p in all_ent.into_iter() {
let p = p?;
let root_opt = p.root();
assert!(root_opt.is_some());
assert_eq!(sys.as_ref(), root_opt.unwrap().as_ref());
}
Ok(())
}
}