Trying to extract data from a file


#1

I am super new to Rust, and I need some help.

I’m attempting to read a file that looks like this:

POWER_SUPPLY_NAME=BAT0
POWER_SUPPLY_STATUS=Full
POWER_SUPPLY_PRESENT=1
POWER_SUPPLY_TECHNOLOGY=Li-poly
POWER_SUPPLY_CYCLE_COUNT=0
POWER_SUPPLY_VOLTAGE_MIN_DESIGN=11400000
POWER_SUPPLY_VOLTAGE_NOW=12774000
POWER_SUPPLY_POWER_NOW=0
POWER_SUPPLY_ENERGY_FULL_DESIGN=90060000
POWER_SUPPLY_ENERGY_FULL=78710000
POWER_SUPPLY_ENERGY_NOW=78710000
POWER_SUPPLY_CAPACITY=100
POWER_SUPPLY_CAPACITY_LEVEL=Full
POWER_SUPPLY_MODEL_NAME=00NY492
POWER_SUPPLY_MANUFACTURER=LGC
POWER_SUPPLY_SERIAL_NUMBER=  699

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.


#2

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.


#3

I would do something like

contents.lines()
    .find(|s| s.contains("POWER_SUPPLY_CAPACITY"))
    .nth(0 as usize)
    .unwrap()
    .split("=")
    .nth(1 as usize)
    .unwrap()
    .parse::<i32>()

But I didn’t test it so maybe it doesn’t work


#4

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

#5

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

#6
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::&lt;i32&gt;().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.


#7

Unfortunately, that didn’t work:

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

#8

However, If I change the enum to be:

enum BatteryFileParseError {
    Io(std::io::Error),
    Parse(ParseIntError),
    NotFound,
}

instead of

enum BatteryFileParseError {
    Io,
    Parse,
    NotFound,
}

then my original code, without the || works fine. And I’m not sure why.


#9

doh! :tired_face:

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.

Here is the documentation for map_err.


#10

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.


#11

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.