This is the /sys/class/power_supply/BATx/uevent file in linux that contains information about your battery, and I want to extract the 100 as an integer from the POWER_SUPPLY_CAPACITY field, and the Full as a string out of the POWER_SUPPLY_STATUS field.
Here is the code I have so far.
use std::io::{self, Write};
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
fn main() {
let f = File::open("/home/matthew/Downloads/uevent").expect("Can't find file!");
let mut reader = BufReader::with_capacity(1000,f);
let mut contents = String::with_capacity(1000);
//Read the entire file as a string
reader.read_to_string(&mut contents).expect("Can't read file!");
//Print the contents
println!("{}", contents);
}
I'm aware of the .parse::<i32> method to convert a string to an integer, and the .find() method for searching for substrings within strings. However, I can't figure out out to seek to the proper position in the string so that I can read the 100 (or whatever other number happens to be there) as an integer. What else do I need to do?
If this were C, I would simply use strstr() to search for, and seek to the position in the string, and then just use strtol() to read the 100 as an integer. Since I'm trying to learn Rust, I want to do it the Rust way.
I've done something similar (though I'm not sure how idiomatic) for an uncommon file format I needed to parse. I did something like this...
let start_key = contents.find("POWER_SUPPLY_CAPACITY").unwrap();
contents = contents[start_key..];
let start_value = contents.find(|c| c.is_digit(10)).unwrap(); // is_digit is a method on characters.
contents = contents[start_value..];
let end_value = contents.find(|c| !c.is_digit(10)).unwrap();
contents = contents[..end_value];
By the end of this, the "contents" of contents should be "100". Then you can use the parse method you mentioned above.
I'm sure there are much more performant ways to do this, but this was the fastest and easiest for me to write. I also actually dealt with the option values instead of using unwrap.
The Pattern trait used by find can be a String, character, &str, etc. See the very bottom of this documentation page for all the things that implement Pattern.
While it's probably not important in example like this, it's preferred to avoid using unwrap, so here is an example with proper error handling. Requires error-chain (defined in Cargo.toml).
#[macro_use]
extern crate error_chain;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::num::ParseIntError;
error_chain! {
foreign_links {
Io(io::Error);
ParseInt(ParseIntError);
}
errors {
LineNotFound
}
}
fn read_power_supply_capacity() -> Result<u8> {
let f = File::open("/home/matthew/Downloads/uevent")?;
let reader = BufReader::new(f);
const SEARCH_FOR: &str = "POWER_SUPPLY_CAPACITY=";
for line in reader.lines() {
let line = line?;
if line.starts_with(SEARCH_FOR) {
return Ok(line[SEARCH_FOR.len()..].parse()?);
}
}
Err(ErrorKind::LineNotFound.into())
}
fn main() {
match read_power_supply_capacity() {
Err(e) => panic!(e.description().to_string()),
Ok(capacity) => println!("{}", capacity),
}
}
Thank you all for your responses. I feel I'm very close. This doesn't yet compile.
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::num::ParseIntError;
use std::io::ErrorKind;
#[derive(Debug)]
enum BatteryFileParseError {
Io,
Parse,
NotFound,
}
fn read_power_supply_capacity() -> Result<i32, BatteryFileParseError> {
let f = try!(File::open("/home/matthew/Downloads/uevent").map_err(BatteryFileParseError::Io));
let mut reader = BufReader::new(f);
let mut contents = String::with_capacity(1000);
//Read the entire file as a string
reader.read_to_string(&mut contents).expect("Can't read file!");
const SEARCH_FOR: &str = "POWER_SUPPLY_CAPACITY=";
for line in contents.lines() {
if line.starts_with(SEARCH_FOR) {
return line[SEARCH_FOR.len()..].parse::<i32>().map_err(BatteryFileParseError::Parse);
}
}
return Err(BatteryFileParseError::NotFound);
}
fn main() {
println!("{:?}", read_power_supply_capacity());
}
It gives me this:
error[E0277]: the trait bound `BatteryFileParseError: std::ops::FnOnce<(std::io::Error,)>` is not satisfied
--> main.rs:19:63
|
19 | let f = try!(File::open("/home/matthew/Downloads/uevent").map_err(BatteryFileParseError::Io));
| ^^^^^^^ the trait `std::ops::FnOnce<(std::io::Error,)>` is not implemented for `BatteryFileParseError`
error[E0277]: the trait bound `BatteryFileParseError: std::ops::FnOnce<(std::num::ParseIntError,)>` is not satisfied
--> main.rs:30:60
|
30 | return line[SEARCH_FOR.len()..].parse::<i32>().map_err(BatteryFileParseError::Parse);
| ^^^^^^^ the trait `std::ops::FnOnce<(std::num::ParseIntError,)>` is not implemented for `BatteryFileParseError`
error: aborting due to 2 previous errors
fn read_power_supply_capacity() -> Result<i32, BatteryFileParseError> {
let f = try!(File::open("/home/matthew/Downloads/uevent").map_err(|| BatteryFileParseError::Io));
let mut reader = BufReader::new(f);
let mut contents = String::with_capacity(1000);
//Read the entire file as a string
reader.read_to_string(&mut contents).expect("Can't read file!");
const SEARCH_FOR: &str = "POWER_SUPPLY_CAPACITY=";
for line in contents.lines() {
if line.starts_with(SEARCH_FOR) {
return line[SEARCH_FOR.len()..].parse::<i32>().map_err(|| BatteryFileParseError::Parse);
}
}
return Err(BatteryFileParseError::NotFound);
}
fn main() {
println!("{:?}", read_power_supply_capacity());
}
I didn't compile and test this, but this should fix the errors you got. The map_err method takes a closure or function pointer, but it is almost always a closure. Notice the || I added into the two map_err calls.
error[E0281]: type mismatch: the type `[closure@main.rs:19:71: 19:99]` implements the trait `std::ops::FnOnce<()>`, but the trait `std::ops::FnOnce<(std::io::Error,)>` is required (expected tuple, found ())
--> main.rs:19:63
|
19 | let f = try!(File::open("/home/matthew/Downloads/uevent").map_err(|| BatteryFileParseError::Io));
| ^^^^^^^
error[E0281]: type mismatch: the type `[closure@main.rs:24:55: 24:83]` implements the trait `std::ops::FnOnce<()>`, but the trait `std::ops::FnOnce<(std::io::Error,)>` is required (expected tuple, found ())
--> main.rs:24:47
|
24 | try!(reader.read_to_string(&mut contents).map_err(|| BatteryFileParseError::Io));
| ^^^^^^^
error[E0277]: the trait bound `BatteryFileParseError: std::ops::FnOnce<(std::num::ParseIntError,)>` is not satisfied
--> main.rs:30:60
|
30 | return line[SEARCH_FOR.len()..].parse::<i32>().map_err(BatteryFileParseError::Parse);
| ^^^^^^^ the trait `std::ops::FnOnce<(std::num::ParseIntError,)>` is not implemented for `BatteryFileParseError`
error: aborting due to 3 previous errors
Try an underscore in each of those vertical bars, like this. map_err(|_| ...). It needs to be a FnOnce that takes a value of whatever the error type defined in the Result<OkType, ErrType> was.
That actually worked. Thanks! I really appreciate the help! I'm not sure why it worked though. Is there any additional reading I could do besides that documentation? It seems pretty scant on info, lol.
My strongest language is C, and Rust is very different from anything I've ever used.
If I was starting Rust brand new again I'd start with Rust By Example while reading the relevant chapters in The Book. Then, on the documentation page there is a link to the Rust Learning repo with tons of resources. Programming Rust has been good so far, it goes more in depth. Once you get started I've found several git books on all kind of topics from foreign function interfaces to macros.