civ_map_generator 0.1.10

A civilization map generator
Documentation
// JSON utility module for loading and processing JSON files with comments.
//
// This module provides utilities to load JSON files that contain comments
// (both line comments `//...` and block comments `/*...*/`), stripping
// the comments so that the resulting string can be parsed by `serde_json`.
//
// The [`strip_json_comments`] function preserves character positions of
// the original JSON (replacing comments with spaces) so that error messages
// from JSON parsers point to the correct locations.

/// Loads a JSON file from disk and returns the content with comments stripped.
///
/// # Arguments
///
/// - `path`: Path to the JSON file to load.
///
/// # Panics
///
/// Panics if the file cannot be read.
pub fn load_json_file_and_strip_json_comments(path: std::path::PathBuf) -> String {
    let json_string_with_comment = std::fs::read_to_string(path).expect("Failed to read JSON file");
    strip_json_comments(&json_string_with_comment, true)
}

/// Take a JSON string with comments and return the version without comments
/// which can be parsed well by serde_json as the standard JSON string.
/// Support line comment(`//...`) and block comment(`/*...*/`)
///
/// When `preserve_locations` is true this function will replace all the comments with spaces, so that JSON parsing
/// errors can point to the right location.
pub fn strip_json_comments(json_with_comments: &str, preserve_locations: bool) -> String {
    // Pre-allocate capacity to avoid repeated reallocation during string building.
    let mut json_without_comments = String::with_capacity(json_with_comments.len());

    let mut block_comment_depth: u8 = 0;
    let mut is_in_string: bool = false; // Comments cannot be in strings

    for line in json_with_comments.split('\n') {
        let mut last_char: Option<char> = None;
        for cur_char in line.chars() {
            // Check whether we're in a string
            if block_comment_depth == 0 && last_char != Some('\\') && cur_char == '"' {
                is_in_string = !is_in_string;
            }

            // Check for line comment start
            if !is_in_string && last_char == Some('/') && cur_char == '/' {
                last_char = None;
                if preserve_locations {
                    json_without_comments.push_str("  ");
                }
                break; // Stop outputting or parsing this line
            }
            // Check for block comment start
            if !is_in_string && last_char == Some('/') && cur_char == '*' {
                block_comment_depth += 1;
                last_char = None;
                if preserve_locations {
                    json_without_comments.push_str("  ");
                }
            // Check for block comment end
            } else if !is_in_string && last_char == Some('*') && cur_char == '/' {
                if block_comment_depth > 0 {
                    block_comment_depth = block_comment_depth.saturating_sub(1);
                }
                last_char = None;
                if preserve_locations {
                    json_without_comments.push_str("  ");
                }

            // Output last char if not in any block comment
            } else {
                if block_comment_depth != 0 {
                    if preserve_locations {
                        json_without_comments.push(' ');
                    }
                } else if let Some(last_char) = last_char {
                    json_without_comments.push(last_char);
                }
                last_char = Some(cur_char);
            }
        }

        // Add last char and newline if not in any block comment
        if let Some(last_char) = last_char {
            if block_comment_depth == 0 {
                json_without_comments.push(last_char);
            } else if preserve_locations {
                json_without_comments.push(' ');
            }
        }

        // Remove trailing whitespace from line
        while json_without_comments.ends_with(' ') {
            json_without_comments.pop();
        }
        json_without_comments.push('\n');
    }

    json_without_comments
}