1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::io::{Error, ErrorKind};
use std::process::Command;

mod tokens;

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

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())
}

pub fn parse<T>(input: T) -> Option<PowerSource>
where
  T: std::convert::AsRef<str>,
{
  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)),
      })
    })
  })
}

pub fn draw(source: PowerSource) -> Option<char> {
  if let PowerSource::Battery(amount) = source {
    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 Some(token);
  }

  None
}

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))?;
  parse(output).ok_or(Error::new(ErrorKind::NotFound, "Unable to produce measurement"))
}

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

  #[test]
  fn none_when_unable_to_parse() {
    let result = parse("whoa");
    assert!(result.is_none());
  }

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

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

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

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

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