Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pretty-print functionality #44

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified CHANGELOG.md
100755 → 100644
Empty file.
Empty file modified Cargo.toml
100755 → 100644
Empty file.
11 changes: 9 additions & 2 deletions README.md
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ If you read the above sections carefully, you'll know that 1) all the keys are s
manner and 3) we can use `getuint()` to parse the `Uint` value into an `u64`. Let's see that in action.

```rust
use configparser::ini::Ini;
use configparser::ini::{Ini, WriteOptions};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
Expand Down Expand Up @@ -142,9 +142,16 @@ fn main() -> Result<(), Box<dyn Error>> {
let innermap = map["topsecret"].clone();
// Remember that all indexes are stored in lowercase!

// You can easily write the currently stored configuration to a file like:
// You can easily write the currently stored configuration to a file with the `write` method. This creates a compact format with as little spacing as possible:
config.write("output.ini");

// You can write the currently stored configuration with more spacing to a file with the `pretty_write` method. You must supply the method with a configuratio specification:
let mut write_options = WriteOptions::default(); // The defaults match the formatting used in the `write` method
write_options.space_around_delimiters = true;
write_options.multiline_line_indentation = 2;
write_options.blank_lines_between_sections = 1;
config.pretty_write("pretty_output.ini", &write_options);

// If you want to simply mutate the stored hashmap, you can use get_mut_map()
let map = config.get_mut_map();
// You can then use normal HashMap functions on this map at your convenience.
Expand Down
165 changes: 152 additions & 13 deletions src/ini.rs
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,56 @@ impl Default for IniDefault {
}
}

/// Use this struct to define formatting options for the `pretty_write` functions.
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct WriteOptions {
///If true then the keys and values will be separated by " = ". In the special case where the value is empty, the
///line ends with " =".
///If false then keys and values will be separated by "=".
///Default is `false`.
///## Example
///```rust
///use configparser::ini::WriteOptions;
///
///let mut write_options = WriteOptions::default();
///assert_eq!(write_options.space_around_delimiters, false);
///```
pub space_around_delimiters: bool,

///Defines the number of spaces for indentation of for multiline values.
///Default is 4 spaces.
///## Example
///```rust
///use configparser::ini::WriteOptions;
///
///let mut write_options = WriteOptions::default();
///assert_eq!(write_options.multiline_line_indentation, 4);
///```
pub multiline_line_indentation: usize,

///Defines the number of blank lines between sections.
///Default is 0.
///## Example
///```rust
///use configparser::ini::WriteOptions;
///
///let mut write_options = WriteOptions::default();
///assert_eq!(write_options.blank_lines_between_sections, 0);
///```
pub blank_lines_between_sections: usize,
}

impl Default for WriteOptions {
fn default() -> Self {
Self {
space_around_delimiters: false,
multiline_line_indentation: 4,
blank_lines_between_sections: 0,
}
}
}

#[cfg(windows)]
const LINE_ENDING: &str = "\r\n";
#[cfg(not(windows))]
Expand Down Expand Up @@ -465,8 +515,8 @@ impl Ini {
Ok(self.map.clone())
}

///Writes the current configuation to the specified path. If a file is not present, it is automatically created for you, if a file already
///exists, it is truncated and the configuration is written to it.
///Writes the current configuation to the specified path using default formatting.
///If a file is not present then it is automatically created for you. If a file already exists then it is overwritten.
///## Example
///```rust
///use configparser::ini::Ini;
Expand All @@ -481,11 +531,39 @@ impl Ini {
///```
///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not.
pub fn write<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()> {
fs::write(path.as_ref(), self.unparse())
fs::write(path.as_ref(), self.unparse(&WriteOptions::default()))
}

///Writes the current configuation to the specified path using the given formatting options.
///If a file is not present then it is automatically created for you. If a file already exists then it is overwritten.
///## Example
///```rust
///use configparser::ini::{Ini, WriteOptions};
///
///fn main() -> std::io::Result<()> {
/// let mut write_options = WriteOptions::default();
/// write_options.space_around_delimiters = true;
/// write_options.multiline_line_indentation = 2;
/// write_options.blank_lines_between_sections = 1;
///
/// let mut config = Ini::new();
/// config.read(String::from(
/// "[2000s]
/// 2020 = bad"));
/// config.pretty_write("output.ini", &write_options)
///}
///```
///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not.
pub fn pretty_write<T: AsRef<Path>>(
&self,
path: T,
write_options: &WriteOptions,
) -> std::io::Result<()> {
fs::write(path.as_ref(), self.unparse(write_options))
}

///Returns a string with the current configuration formatted with valid ini-syntax. This is always safe since the configuration is validated during
///parsing.
///Returns a string with the current configuration formatted with valid ini-syntax using default formatting.
///This is always safe since the configuration is validated during parsing.
///## Example
///```rust
///use configparser::ini::Ini;
Expand All @@ -498,22 +576,51 @@ impl Ini {
///```
///Returns a `String` type contatining the ini-syntax file.
pub fn writes(&self) -> String {
self.unparse()
self.unparse(&WriteOptions::default())
}

///Returns a string with the current configuration formatted with valid ini-syntax using the given formatting options.
///This is always safe since the configuration is validated during parsing.
///## Example
///```rust
///use configparser::ini::{Ini, WriteOptions};
///
///let mut write_options = WriteOptions::default();
///write_options.space_around_delimiters = true;
///write_options.multiline_line_indentation = 2;
///write_options.blank_lines_between_sections = 1;
///
///let mut config = Ini::new();
///config.read(String::from(
/// "[2000s]
/// 2020 = bad"));
///let outstring = config.pretty_writes(&write_options);
///```
///Returns a `String` type contatining the ini-syntax file.
pub fn pretty_writes(&self, write_options: &WriteOptions) -> String {
self.unparse(write_options)
}

///Private function that converts the currently stored configuration into a valid ini-syntax string.
fn unparse(&self) -> String {
fn unparse(&self, write_options: &WriteOptions) -> String {
// push key/value pairs in outmap to out string.
fn unparse_key_values(
out: &mut String,
outmap: &Map<String, Option<String>>,
multiline: bool,
space_around_delimiters: bool,
indent: usize,
) {
let delimiter = if space_around_delimiters { " = " } else { "=" };
for (key, val) in outmap.iter() {
out.push_str(key);

if let Some(value) = val {
out.push('=');
if value.is_empty() {
out.push_str(delimiter.trim_end());
} else {
out.push_str(delimiter);
}

if multiline {
let mut lines = value.lines();
Expand All @@ -522,7 +629,7 @@ impl Ini {

for line in lines {
out.push_str(LINE_ENDING);
out.push_str(" ");
out.push_str(" ".repeat(indent).as_ref());
out.push_str(line);
}
} else {
Expand All @@ -534,18 +641,36 @@ impl Ini {
}
}

let line_endings = LINE_ENDING.repeat(write_options.blank_lines_between_sections);
let mut out = String::new();

if let Some(defaultmap) = self.map.get(&self.default_section) {
unparse_key_values(&mut out, defaultmap, self.multiline);
unparse_key_values(
&mut out,
defaultmap,
self.multiline,
write_options.space_around_delimiters,
write_options.multiline_line_indentation,
);
}

let mut is_first = true;
for (section, secmap) in self.map.iter() {
if !is_first {
out.push_str(line_endings.as_ref());
}
if section != &self.default_section {
write!(out, "[{}]", section).unwrap();
out.push_str(LINE_ENDING);
unparse_key_values(&mut out, secmap, self.multiline);
unparse_key_values(
&mut out,
secmap,
self.multiline,
write_options.space_around_delimiters,
write_options.multiline_line_indentation,
);
}
is_first = false;
}
out
}
Expand Down Expand Up @@ -1102,13 +1227,27 @@ impl Ini {
Ok(self.map.clone())
}

///Writes the current configuation to the specified path asynchronously. If a file is not present, it is automatically created for you, if a file already
///Writes the current configuation to the specified path asynchronously using default formatting. If a file is not present, it is automatically created for you, if a file already
///exists, it is truncated and the configuration is written to it.
///
///Usage is the same as `write`, but `.await` must be called after along with the usual async rules.
///
///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not.
pub async fn write_async<T: AsRef<Path>>(&self, path: T) -> std::io::Result<()> {
async_fs::write(path.as_ref(), self.unparse()).await
async_fs::write(path.as_ref(), self.unparse(&WriteOptions::default())).await
}

///Writes the current configuation to the specified path asynchronously using the given formatting options. If a file is not present, it is automatically created for you, if a file already
///exists, it is truncated and the configuration is written to it.
///
///Usage is the same as `pretty_pretty_write`, but `.await` must be called after along with the usual async rules.
///
///Returns a `std::io::Result<()>` type dependent on whether the write was successful or not.
pub async fn pretty_write_async<T: AsRef<Path>>(
&self,
path: T,
write_options: &WriteOptions,
) -> std::io::Result<()> {
async_fs::write(path.as_ref(), self.unparse(write_options)).await
}
}
11 changes: 9 additions & 2 deletions src/lib.rs
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ If you read the above sections carefully, you'll know that 1) all the keys are s
manner and 3) we can use `getint()` to parse the `Int` value into an `i64`. Let's see that in action.

```rust
use configparser::ini::Ini;
use configparser::ini::{Ini, WriteOptions};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
Expand Down Expand Up @@ -134,9 +134,16 @@ fn main() -> Result<(), Box<dyn Error>> {
let innermap = map["topsecret"].clone();
// Remember that all indexes are stored in lowercase!

// You can easily write the currently stored configuration to a file like:
// You can easily write the currently stored configuration to a file with the `write` method. This creates a compact format with as little spacing as possible:
config.write("output.ini");

// You can write the currently stored configuration with more spacing to a file with the `pretty_write` method. You must supply the method with a configuratio specification:
let mut write_options = WriteOptions::default(); // The defaults match the formatting used in the `write` method
write_options.space_around_delimiters = true;
write_options.multiline_line_indentation = 2;
write_options.blank_lines_between_sections = 1;
config.pretty_write("pretty_output.ini", &write_options);

// If you want to simply mutate the stored hashmap, you can use get_mut_map()
let map = config.get_mut_map();
// You can then use normal HashMap functions on this map at your convenience.
Expand Down
Empty file modified tests/test.ini
100755 → 100644
Empty file.
Loading
Loading