Released my first Rust libraries: toml-config and indexed-line-reader
My first experience with Rust has been very positive, I've started working on a side project that might see the light in the next few months and while building it I've ended up creating a couple of libraries that might be useful: toml-config and indexed-line-reader
toml-config
The first one is a library that can be used to load TOML
files into a target
Rust structure.
To load a file into a Config struct, use ConfigFactory
.
Either rustc_serialize
or serde
can be used for serialization.
Example using rustc_serialize
extern crate rustc_serialize;
extern crate toml_config;
use rustc_serialize::{Encodable, Decodable};
use std::path::Path;
use toml_config::ConfigFactory;
#[derive(RustcEncodable, RustcDecodable)]
struct Config {
nested: NestedConfig
}
// Defaults will be used for missing/invalid configurations in the TOML config file
impl Default for Config {
fn default() -> Config {
Config {
nested: NestedConfig::default()
}
}
}
#[derive(RustcEncodable, RustcDecodable)]
struct NestedConfig {
value: String,
values: Vec<u16>
}
impl Default for NestedConfig {
fn default() -> NestedConfig {
NestedConfig {
value: "default".to_owned(),
values: vec![0, 0, 0]
}
}
}
/* config.toml:
* [nested]
* value = "test"
* values = [1, 2, 3]
*/
let config: Config = ConfigFactory::load(Path::new("config.toml"));
assert_eq!(config.nested.value, "test");
assert_eq!(config.nested.values, vec![1, 2, 3]);
Example using serde
extern crate serde;
extern crate toml_config;
use serde::{Serialize, Deserialize};
use std::path::Path;
use toml_config::ConfigFactory;
#[derive(Serialize, Deserialize)]
struct Config {
nested: NestedConfig
}
// Defaults will be used for missing/invalid configurations in the TOML config file
impl Default for Config {
fn default() -> Config {
Config {
nested: NestedConfig::default()
}
}
}
#[derive(Serialize, Deserialize)]
struct NestedConfig {
value: String,
values: Vec<u16>
}
impl Default for NestedConfig {
fn default() -> NestedConfig {
NestedConfig {
value: "default".to_owned(),
values: vec![0, 0, 0]
}
}
}
/* config.toml:
* [nested]
* value = "test"
* values = [1, 2, 3]
*/
let config: Config = ConfigFactory::load(Path::new("config.toml"));
assert_eq!(config.nested.value, "test");
assert_eq!(config.nested.values, vec![1, 2, 3]);
indexed-line-reader
The second is a wrapper on top of the BufReader
implementation
that internally indexes the byte count every n
lines.
IndexedLineReader
implements the Seek
trait to allow seeking to specific lines.
Index Granularity
IndexedLineReader
just stores a byte count every n
lines to allow fast seeking by line.
The granularity can be configured by passing the valut to the IndexedLineReader
constructor.
There is a tradeoff to make between memory occupied by the index and the seek speed, in general lower granularity means more line indexed and higher memory consumption, but fast seek time, while a higher granularity slows down the seek time but less indexes are kept in memory.
As an example, if a file has 100,000,000 lines, usually a granularity of 100,000 gives a good tradeoff between performance and memory consumption.
Example
extern crate indexed_line_reader;
use indexed_line_reader::*;
use std::fs::*;
use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
/* Creates an IndexedLineReader for a file with index granularity of 100 */
let file_reader = OpenOptions::new().read(true).open("file.txt").expect("Unable to open file reader");
let mut indexed_line_reader = &mut IndexedLineReader::new(BufReader::new(file_reader), 100);
/* Seeks to line 100 from the start of the file */
indexed_line_reader.seek(SeekFrom::Start(100));
/* Seeks forward by 50 lines from the current position on the file */
indexed_line_reader.seek(SeekFrom::Current(50));
/* Seeks backward by 50 lines from the current position on the file */
indexed_line_reader.seek(SeekFrom::Current(-50));
/* Seeks to the 100th line from the end on the file */
indexed_line_reader.seek(SeekFrom::End(100));
If you like them make sure to share.