battrs 0.2.0

Battery level information
Documentation
use std::fmt::{Display, Error as FormatError, Formatter};
use std::io::{Error, ErrorKind};
use std::process::Command;

mod tokens;

#[derive(Debug, PartialEq)]
pub enum PowerSource {
  Battery(u8),
  Other(String),
  Unknown,
}

fn parse_life(input: &str) -> Option<u8> {
  input
    .split(';')
    .nth(0)
    .and_then(|v| v.split_whitespace().nth(2))
    .and_then(|v| v.trim_end_matches('%').parse::<u8>().ok())
}

impl<T> std::convert::From<T> for PowerSource
where
  T: std::convert::AsRef<str>,
{
  fn from(input: T) -> Self {
    let mut lines = input.as_ref().lines();
    let (first, second) = (lines.next(), lines.next());

    second
      .and_then(parse_life)
      .and_then(|amount| {
        first.and_then(|line| {
          line.splitn(3, '\'').nth(1).map(|v| match v {
            "Battery Power" => PowerSource::Battery(amount),
            other => PowerSource::Other(String::from(other)),
          })
        })
      })
      .unwrap_or(PowerSource::Unknown)
  }
}

impl Display for PowerSource {
  fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
    if let PowerSource::Battery(amount) = self {
      let token = match amount {
        0..=10 => tokens::TEN,
        11..=20 => tokens::TWENTY,
        21..=30 => tokens::THIRTY,
        31..=40 => tokens::FOURTY,
        41..=50 => tokens::FIFTY,
        51..=60 => tokens::SIXTY,
        61..=70 => tokens::SEVENTY,
        71..=80 => tokens::EIGHTY,
        81..=90 => tokens::NINETY,
        _ => tokens::HUNDRED,
      };
      return write!(formatter, "{}", token);
    }
    Ok(())
  }
}

pub fn measure() -> Result<PowerSource, Error> {
  let result = Command::new("pmset").arg("-g").arg("batt").output()?;
  let output = String::from_utf8(result.stdout).map_err(|e| Error::new(ErrorKind::Other, e))?;
  Ok(PowerSource::from(output))
}

#[cfg(test)]
mod tests {
  use super::{tokens, PowerSource};

  #[test]
  fn test_draw_canon() {
    let result = PowerSource::Battery(10);
    let out = format!("{}", result);
    assert_eq!(out, format!("{}", tokens::TEN));
  }

  #[test]
  fn test_draw_debug() {
    let result = PowerSource::Battery(10);
    let out = format!("{:?}", result);
    assert_eq!(out, String::from("Battery(10)"));
  }

  #[test]
  fn none_when_unable_to_parse() {
    let result = PowerSource::from("whoa");
    assert_eq!(result, PowerSource::Unknown);
  }

  #[test]
  fn battery_with_amount_100() {
    let result = PowerSource::from(
      r#"Now drawing from 'Battery Power'
-InternalBattery-0 (id=4522083)	100%; charged; 0:00 remaining present: true
    "#,
    );
    assert_eq!(result, PowerSource::Battery(100));
  }

  #[test]
  fn none_invalid_amount() {
    let result = PowerSource::from(
      r#"Now drawing from 'Battery Power'
-InternalBattery-0 (id=4522083)	abc%; charged; 0:00 remaining present: true
    "#,
    );
    assert_eq!(result, PowerSource::Unknown);
  }

  #[test]
  fn battery_with_amount_10() {
    let result = PowerSource::from(
      r#"Now drawing from 'Battery Power'
-InternalBattery-0 (id=4522083)	10%; charged; 0:00 remaining present: true
    "#,
    );
    assert_eq!(result, PowerSource::Battery(10));
  }

  #[test]
  fn battery_with_amount_1() {
    let result = PowerSource::from(
      r#"Now drawing from 'Battery Power'
-InternalBattery-0 (id=4522083)	1%; charged; 0:00 remaining present: true
    "#,
    );
    assert_eq!(result, PowerSource::Battery(1));
  }

  #[test]
  fn other_with_name() {
    let result = PowerSource::from(
      r#"Now drawing from 'AC Power'
-InternalBattery-0 (id=4522083)	100%; charged; 0:00 remaining present: true
    "#,
    );
    assert_eq!(result, PowerSource::Other(String::from("AC Power")));
  }
}