Skip to content

Commit 36d858a

Browse files
committed
Make inline comment separators separate from comment separators
1 parent 1f6f20c commit 36d858a

File tree

2 files changed

+127
-11
lines changed

2 files changed

+127
-11
lines changed

src/ini.rs

+52-11
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub struct Ini {
3232
map: Map<String, Map<String, Option<String>>>,
3333
default_section: std::string::String,
3434
comment_symbols: Vec<char>,
35+
inline_comment_symbols: Option<Vec<char>>,
3536
delimiters: Vec<char>,
3637
boolean_values: HashMap<bool, Vec<String>>,
3738
case_sensitive: bool,
@@ -71,6 +72,17 @@ pub struct IniDefault {
7172
///assert_eq!(default.comment_symbols, vec![';', '#']);
7273
///```
7374
pub comment_symbols: Vec<char>,
75+
///Denotes the set of inline comment symbols for the object. The default of
76+
///`None` means to fall back to the normal comment symbols.
77+
///## Example
78+
///```rust
79+
///use configparser::ini::Ini;
80+
///
81+
///let mut config = Ini::new();
82+
///let default = config.defaults();
83+
///assert_eq!(default.inline_comment_symbols, None);
84+
///```
85+
pub inline_comment_symbols: Option<Vec<char>>,
7486
///Denotes the set delimiters for the key-value pairs.
7587
///## Example
7688
///```rust
@@ -109,6 +121,7 @@ impl Default for IniDefault {
109121
Self {
110122
default_section: "default".to_owned(),
111123
comment_symbols: vec![';', '#'],
124+
inline_comment_symbols: None,
112125
delimiters: vec!['=', ':'],
113126
multiline: false,
114127
boolean_values: [
@@ -285,6 +298,7 @@ impl Ini {
285298
map: Map::new(),
286299
default_section: defaults.default_section,
287300
comment_symbols: defaults.comment_symbols,
301+
inline_comment_symbols: defaults.inline_comment_symbols,
288302
delimiters: defaults.delimiters,
289303
boolean_values: defaults.boolean_values,
290304
case_sensitive: defaults.case_sensitive,
@@ -305,6 +319,7 @@ impl Ini {
305319
IniDefault {
306320
default_section: self.default_section.to_owned(),
307321
comment_symbols: self.comment_symbols.to_owned(),
322+
inline_comment_symbols: self.inline_comment_symbols.to_owned(),
308323
delimiters: self.delimiters.to_owned(),
309324
boolean_values: self.boolean_values.to_owned(),
310325
case_sensitive: self.case_sensitive,
@@ -330,6 +345,7 @@ impl Ini {
330345
pub fn load_defaults(&mut self, defaults: IniDefault) {
331346
self.default_section = defaults.default_section;
332347
self.comment_symbols = defaults.comment_symbols;
348+
self.inline_comment_symbols = defaults.inline_comment_symbols;
333349
self.delimiters = defaults.delimiters;
334350
self.boolean_values = defaults.boolean_values;
335351
self.case_sensitive = defaults.case_sensitive;
@@ -366,6 +382,21 @@ impl Ini {
366382
self.comment_symbols = symlist.to_vec();
367383
}
368384

385+
///Sets the default inline comment symbols to the defined character slice (the default is `None` which falls back to the normal comment symbols).
386+
///Keep in mind that this will remove the default symbols. It must be set before `load()` or `read()` is called in order to take effect.
387+
///## Example
388+
///```rust
389+
///use configparser::ini::Ini;
390+
///
391+
///let mut config = Ini::new();
392+
///config.set_inline_comment_symbols(Some(&['!', '#']));
393+
///let map = config.load("tests/test.ini").unwrap();
394+
///```
395+
///Returns nothing.
396+
pub fn set_inline_comment_symbols(&mut self, symlist: Option<&[char]>) {
397+
self.inline_comment_symbols = symlist.map(|val| val.to_vec());
398+
}
399+
369400
///Sets multiline string support.
370401
///It must be set before `load()` or `read()` is called in order to take effect.
371402
///## Example
@@ -724,6 +755,10 @@ impl Ini {
724755

725756
///Private function that parses ini-style syntax into a Map.
726757
fn parse(&self, input: String) -> Result<Map<String, Map<String, Option<String>>>, String> {
758+
let inline_comment_symbols: &[char] = self
759+
.inline_comment_symbols
760+
.as_deref()
761+
.unwrap_or_else(|| self.comment_symbols.as_ref());
727762
let mut map: Map<String, Map<String, Option<String>>> = Map::new();
728763
let mut section = self.default_section.clone();
729764
let mut current_key: Option<String> = None;
@@ -740,23 +775,29 @@ impl Ini {
740775
let mut blank_lines = 0usize;
741776

742777
for (num, raw_line) in input.lines().enumerate() {
743-
let line = match raw_line.find(|c: char| self.comment_symbols.contains(&c)) {
744-
Some(idx) => &raw_line[..idx],
745-
None => raw_line,
778+
let line = raw_line.trim();
779+
780+
// If the line is _just_ a comment, skip it entirely.
781+
let line = match line.find(|c: char| self.comment_symbols.contains(&c)) {
782+
Some(0) => continue,
783+
Some(_) | None => line,
746784
};
747785

748-
let trimmed = line.trim();
786+
let line = line.trim();
749787

750788
// Skip empty lines, but keep track of them for multiline values.
751-
if trimmed.is_empty() {
752-
// If a line is _just_ a comment (regardless of whether it's preceded by
753-
// whitespace), ignore it.
754-
if line == raw_line {
755-
blank_lines += 1;
756-
}
789+
if line.is_empty() {
790+
blank_lines += 1;
757791
continue;
758792
}
759793

794+
let line = match line.find(|c: char| inline_comment_symbols.contains(&c)) {
795+
Some(idx) => &line[..idx],
796+
None => line,
797+
};
798+
799+
let trimmed = line.trim();
800+
760801
match (trimmed.find('['), trimmed.rfind(']')) {
761802
(Some(0), Some(end)) => {
762803
section = caser(trimmed[1..end].trim());
@@ -774,7 +815,7 @@ impl Ini {
774815
_ => {}
775816
}
776817

777-
if line.starts_with(char::is_whitespace) && self.multiline {
818+
if raw_line.starts_with(char::is_whitespace) && self.multiline {
778819
let key = match current_key.as_ref() {
779820
Some(x) => x,
780821
None => {

tests/test.rs

+75
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,81 @@ basic_option=basic_value
250250
Ok(())
251251
}
252252

253+
#[test]
254+
fn inline_comment_symbols() -> Result<(), Box<dyn Error>> {
255+
const FILE_CONTENTS: &str = "
256+
[basic_section]
257+
; basic comment
258+
; comment with space
259+
! extra_comment=
260+
basic_option=value
261+
basic_with_comment=value ; Simple comment
262+
basic_with_extra_inline=value ! comment
263+
empty_option=
264+
";
265+
266+
let mut config = Ini::new();
267+
config.read(FILE_CONTENTS.to_owned())?;
268+
269+
assert_eq!(
270+
config.get("basic_section", "basic_option"),
271+
Some(String::from("value"))
272+
);
273+
assert_eq!(
274+
config.get("basic_section", "basic_with_comment"),
275+
Some(String::from("value"))
276+
);
277+
assert_eq!(
278+
config.get("basic_section", "basic_with_extra_inline"),
279+
Some(String::from("value ! comment"))
280+
);
281+
assert_eq!(
282+
config.get("basic_section", "! extra_comment"),
283+
Some(String::from(""))
284+
);
285+
286+
assert_eq!(
287+
config.get("basic_section", "empty_option"),
288+
Some(String::from(""))
289+
);
290+
291+
config.set_inline_comment_symbols(Some(&['!']));
292+
293+
config.read(FILE_CONTENTS.to_owned())?;
294+
295+
assert_eq!(
296+
config.get("basic_section", "basic_option"),
297+
Some(String::from("value"))
298+
);
299+
assert_eq!(
300+
config.get("basic_section", "basic_with_comment"),
301+
Some(String::from("value ; Simple comment"))
302+
);
303+
assert_eq!(
304+
config.get("basic_section", "basic_with_extra_inline"),
305+
Some(String::from("value"))
306+
);
307+
308+
config.set_inline_comment_symbols(Some(&[]));
309+
310+
config.read(FILE_CONTENTS.to_owned())?;
311+
312+
assert_eq!(
313+
config.get("basic_section", "basic_option"),
314+
Some(String::from("value"))
315+
);
316+
assert_eq!(
317+
config.get("basic_section", "basic_with_comment"),
318+
Some(String::from("value ; Simple comment"))
319+
);
320+
assert_eq!(
321+
config.get("basic_section", "basic_with_extra_inline"),
322+
Some(String::from("value ! comment"))
323+
);
324+
325+
Ok(())
326+
}
327+
253328
#[test]
254329
#[cfg(feature = "indexmap")]
255330
fn sort_on_write() -> Result<(), Box<dyn Error>> {

0 commit comments

Comments
 (0)