use core::{cmp, fmt, str::FromStr};
use flipperzero_sys as sys;
use ufmt::derive::uDebug;
#[derive(Copy, Clone, Debug, uDebug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Level(LevelInner);
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct LevelFilter(LevelFilterInner);
#[derive(Clone, Debug, uDebug)]
pub struct ParseLevelFilterError(());
impl Level {
pub const ERROR: Level = Level(LevelInner::Error);
pub const WARN: Level = Level(LevelInner::Warn);
pub const INFO: Level = Level(LevelInner::Info);
pub const DEBUG: Level = Level(LevelInner::Debug);
pub const TRACE: Level = Level(LevelInner::Trace);
pub fn as_str(&self) -> &'static str {
match *self {
Level::TRACE => "TRACE",
Level::DEBUG => "DEBUG",
Level::INFO => "INFO",
Level::WARN => "WARN",
Level::ERROR => "ERROR",
}
}
pub(crate) fn to_furi(self) -> sys::FuriLogLevel {
match self {
Level::TRACE => sys::FuriLogLevelTrace,
Level::DEBUG => sys::FuriLogLevelDebug,
Level::INFO => sys::FuriLogLevelInfo,
Level::WARN => sys::FuriLogLevelWarn,
Level::ERROR => sys::FuriLogLevelError,
}
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Level::TRACE => f.pad("TRACE"),
Level::DEBUG => f.pad("DEBUG"),
Level::INFO => f.pad("INFO"),
Level::WARN => f.pad("WARN"),
Level::ERROR => f.pad("ERROR"),
}
}
}
impl ufmt::uDisplay for Level {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
f.write_str(self.as_str())
}
}
impl core::error::Error for ParseLevelError {}
impl FromStr for Level {
type Err = ParseLevelError;
fn from_str(s: &str) -> Result<Self, ParseLevelError> {
match s {
s if s.eq_ignore_ascii_case("error") => Ok(Level::ERROR),
s if s.eq_ignore_ascii_case("warn") => Ok(Level::WARN),
s if s.eq_ignore_ascii_case("info") => Ok(Level::INFO),
s if s.eq_ignore_ascii_case("debug") => Ok(Level::DEBUG),
s if s.eq_ignore_ascii_case("trace") => Ok(Level::TRACE),
_ => Err(ParseLevelError { _p: () }),
}
}
}
#[repr(usize)]
#[derive(Copy, Clone, Debug, uDebug, Hash, Eq, PartialEq, PartialOrd, Ord)]
enum LevelInner {
Trace = sys::FuriLogLevelTrace.0 as usize,
Debug = sys::FuriLogLevelDebug.0 as usize,
Info = sys::FuriLogLevelInfo.0 as usize,
Warn = sys::FuriLogLevelWarn.0 as usize,
Error = sys::FuriLogLevelError.0 as usize,
}
impl From<Level> for LevelFilter {
#[inline]
fn from(level: Level) -> Self {
Self::from_level(level)
}
}
impl From<Option<Level>> for LevelFilter {
#[inline]
fn from(level: Option<Level>) -> Self {
level.map(Self::from_level).unwrap_or(Self::OFF)
}
}
impl From<LevelFilter> for Option<Level> {
#[inline]
fn from(filter: LevelFilter) -> Self {
filter.into_level()
}
}
impl LevelFilter {
pub const OFF: LevelFilter = LevelFilter(LevelFilterInner::Off);
pub const ERROR: LevelFilter = LevelFilter::from_level(Level::ERROR);
pub const WARN: LevelFilter = LevelFilter::from_level(Level::WARN);
pub const INFO: LevelFilter = LevelFilter::from_level(Level::INFO);
pub const DEBUG: LevelFilter = LevelFilter::from_level(Level::DEBUG);
pub const TRACE: LevelFilter = LevelFilter::from_level(Level::TRACE);
pub const fn from_level(level: Level) -> Self {
Self(match level.0 {
LevelInner::Trace => LevelFilterInner::Trace,
LevelInner::Debug => LevelFilterInner::Debug,
LevelInner::Info => LevelFilterInner::Info,
LevelInner::Warn => LevelFilterInner::Warn,
LevelInner::Error => LevelFilterInner::Error,
})
}
pub const fn into_level(self) -> Option<Level> {
match self.0 {
LevelFilterInner::Trace => Some(Level::TRACE),
LevelFilterInner::Debug => Some(Level::DEBUG),
LevelFilterInner::Info => Some(Level::INFO),
LevelFilterInner::Warn => Some(Level::WARN),
LevelFilterInner::Error => Some(Level::ERROR),
LevelFilterInner::Off => None,
}
}
#[inline(always)]
pub fn current() -> Self {
match unsafe { sys::furi_log_get_level() } {
sys::FuriLogLevelDefault => Self::INFO,
sys::FuriLogLevelNone => Self::OFF,
sys::FuriLogLevelError => Self::ERROR,
sys::FuriLogLevelWarn => Self::WARN,
sys::FuriLogLevelInfo => Self::INFO,
sys::FuriLogLevelDebug => Self::DEBUG,
sys::FuriLogLevelTrace => Self::TRACE,
#[cfg(debug_assertions)]
unknown => unreachable!(
"/!\\ `LevelFilter` representation seems to have changed! /!\\ \n\
This is a bug (and it's pretty bad). Please contact the `flipperzero` \
maintainers. Thank you and I'm sorry.\n \
The offending repr was: {:?}",
unknown,
),
#[cfg(not(debug_assertions))]
_ => unsafe {
core::hint::unreachable_unchecked()
},
}
}
}
impl fmt::Display for LevelFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
LevelFilter::OFF => f.pad("off"),
LevelFilter::ERROR => f.pad("error"),
LevelFilter::WARN => f.pad("warn"),
LevelFilter::INFO => f.pad("info"),
LevelFilter::DEBUG => f.pad("debug"),
LevelFilter::TRACE => f.pad("trace"),
}
}
}
impl fmt::Debug for LevelFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
LevelFilter::OFF => f.pad("LevelFilter::OFF"),
LevelFilter::ERROR => f.pad("LevelFilter::ERROR"),
LevelFilter::WARN => f.pad("LevelFilter::WARN"),
LevelFilter::INFO => f.pad("LevelFilter::INFO"),
LevelFilter::DEBUG => f.pad("LevelFilter::DEBUG"),
LevelFilter::TRACE => f.pad("LevelFilter::TRACE"),
}
}
}
impl ufmt::uDisplay for LevelFilter {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
match *self {
LevelFilter::OFF => f.write_str("off"),
LevelFilter::ERROR => f.write_str("error"),
LevelFilter::WARN => f.write_str("warn"),
LevelFilter::INFO => f.write_str("info"),
LevelFilter::DEBUG => f.write_str("debug"),
LevelFilter::TRACE => f.write_str("trace"),
}
}
}
impl ufmt::uDebug for LevelFilter {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
match *self {
LevelFilter::OFF => f.write_str("LevelFilter::OFF"),
LevelFilter::ERROR => f.write_str("LevelFilter::ERROR"),
LevelFilter::WARN => f.write_str("LevelFilter::WARN"),
LevelFilter::INFO => f.write_str("LevelFilter::INFO"),
LevelFilter::DEBUG => f.write_str("LevelFilter::DEBUG"),
LevelFilter::TRACE => f.write_str("LevelFilter::TRACE"),
}
}
}
impl FromStr for LevelFilter {
type Err = ParseLevelFilterError;
fn from_str(from: &str) -> Result<Self, Self::Err> {
match from {
"" => Some(LevelFilter::ERROR),
s if s.eq_ignore_ascii_case("error") => Some(LevelFilter::ERROR),
s if s.eq_ignore_ascii_case("warn") => Some(LevelFilter::WARN),
s if s.eq_ignore_ascii_case("info") => Some(LevelFilter::INFO),
s if s.eq_ignore_ascii_case("debug") => Some(LevelFilter::DEBUG),
s if s.eq_ignore_ascii_case("trace") => Some(LevelFilter::TRACE),
s if s.eq_ignore_ascii_case("off") => Some(LevelFilter::OFF),
_ => None,
}
.ok_or(ParseLevelFilterError(()))
}
}
#[derive(Debug)]
pub struct ParseLevelError {
_p: (),
}
impl fmt::Display for ParseLevelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(
"error parsing level: expected one of \"error\", \"warn\", \
\"info\", \"debug\", \"trace\"",
)
}
}
impl ufmt::uDisplay for ParseLevelError {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
f.write_str(
"error parsing level: expected one of \"error\", \"warn\", \
\"info\", \"debug\", \"trace\"",
)
}
}
impl fmt::Display for ParseLevelFilterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(
"error parsing level filter: expected one of \"off\", \"error\", \
\"warn\", \"info\", \"debug\", \"trace\"",
)
}
}
impl ufmt::uDisplay for ParseLevelFilterError {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
f.write_str(
"error parsing level filter: expected one of \"off\", \"error\", \
\"warn\", \"info\", \"debug\", \"trace\"",
)
}
}
impl core::error::Error for ParseLevelFilterError {}
#[repr(usize)]
#[derive(Copy, Clone, Debug, uDebug, Hash, Eq, PartialEq, PartialOrd, Ord)]
enum LevelFilterInner {
Trace = sys::FuriLogLevelTrace.0 as usize,
Debug = sys::FuriLogLevelDebug.0 as usize,
Info = sys::FuriLogLevelInfo.0 as usize,
Warn = sys::FuriLogLevelWarn.0 as usize,
Error = sys::FuriLogLevelError.0 as usize,
Off = sys::FuriLogLevelNone.0 as usize,
}
impl PartialEq<LevelFilter> for Level {
#[inline(always)]
fn eq(&self, other: &LevelFilter) -> bool {
self.0 as usize == (other.0 as usize)
}
}
impl PartialOrd<LevelFilter> for Level {
#[inline(always)]
fn partial_cmp(&self, other: &LevelFilter) -> Option<cmp::Ordering> {
Some((self.0 as usize).cmp(&(other.0 as usize)))
}
}
impl PartialEq<Level> for LevelFilter {
#[inline(always)]
fn eq(&self, other: &Level) -> bool {
(self.0 as usize) == other.0 as usize
}
}
impl PartialOrd<Level> for LevelFilter {
#[inline(always)]
fn partial_cmp(&self, other: &Level) -> Option<cmp::Ordering> {
Some((self.0 as usize).cmp(&(other.0 as usize)))
}
}
#[flipperzero_test::tests]
mod tests {
use super::*;
use core::mem;
#[test]
fn level_from_str() {
assert_eq!("error".parse::<Level>().unwrap(), Level::ERROR);
}
#[test]
fn filter_level_conversion() {
let mapping = [
(LevelFilter::OFF, None),
(LevelFilter::ERROR, Some(Level::ERROR)),
(LevelFilter::WARN, Some(Level::WARN)),
(LevelFilter::INFO, Some(Level::INFO)),
(LevelFilter::DEBUG, Some(Level::DEBUG)),
(LevelFilter::TRACE, Some(Level::TRACE)),
];
for (filter, level) in mapping.iter() {
assert_eq!(filter.into_level(), *level);
match level {
Some(level) => {
let actual: LevelFilter = (*level).into();
assert_eq!(actual, *filter);
}
None => {
let actual: LevelFilter = None.into();
assert_eq!(actual, *filter);
}
}
}
}
#[test]
fn level_filter_is_usize_sized() {
assert_eq!(
mem::size_of::<LevelFilter>(),
mem::size_of::<usize>(),
"`LevelFilter` is no longer `usize`-sized! global MAX_LEVEL may now be invalid!"
)
}
#[test]
fn level_filter_reprs() {
let mapping = [
(LevelFilter::OFF, LevelFilterInner::Off as usize),
(LevelFilter::ERROR, LevelFilterInner::Error as usize),
(LevelFilter::WARN, LevelFilterInner::Warn as usize),
(LevelFilter::INFO, LevelFilterInner::Info as usize),
(LevelFilter::DEBUG, LevelFilterInner::Debug as usize),
(LevelFilter::TRACE, LevelFilterInner::Trace as usize),
];
for &(filter, expected) in &mapping {
let repr = unsafe {
mem::transmute::<LevelFilter, usize>(filter)
};
assert_eq!(expected, repr, "repr changed for {:?}", filter)
}
}
}