<commit_before>//
// Flambe - Rapid game development
// https://github.com/aduros/flambe/blob/master/LICENSE.txt

package flambe.util;

using StringTools;

typedef ConfigSection = Map<String,String>;

/**
 * An INI-like config file parser.
 *
 * <pre>
 * ; This is a comment
 * foo = some value
 * [my section]
 * password = "  quotes are optional, and useful if you want to preserve surrounding spaces  "
 * </pre>
 */
class Config
{
    public var mainSection (default, null) :ConfigSection;
    public var sections (default, null) :Map<String,ConfigSection>;

    public function new ()
    {
        mainSection = new ConfigSection();
        sections = new Map();
    }

    /** Parse the contents of an INI file. */
    public static function parse (text :String) :Config
    {
        var config = new Config();

        var commentPattern = ~/^\s*;/;
        var sectionPattern = ~/^\s*\[\s*([^\]]*)\s*\]/;
        var pairPattern = ~/^\s*([\w\.\-_]+)\s*=\s*(.*)/;

        var currentSection = config.mainSection;
        for (line in ~/\r\n|\r|\n/g.split(text)) {
            if (commentPattern.match(line)) {
                // Ignore this line

            } else if (sectionPattern.match(line)) {
                var name = sectionPattern.matched(1);
                if (config.sections.exists(name)) {
                    // Handle duplicate sections, should this be allowed?
                    currentSection = config.sections.get(name);
                } else {
                    currentSection = new ConfigSection();
                    config.sections.set(name, currentSection);
                }

            } else if (pairPattern.match(line)) {
                var key = pairPattern.matched(1);
                var value = pairPattern.matched(2);
                var quote = value.fastCodeAt(0);
                if ((quote == "\"".code || quote == "'".code) &&
                        value.fastCodeAt(value.length-1) == quote) {
                    // Trim off quotes
                    value = value.substr(1, value.length-2);
                }
                currentSection.set(key, value);
            }
        }

        return config;
    }

    /** Shorthand for sections.get(name). */
    inline public function section (name :String) :ConfigSection
    {
        return sections.get(name);
    }

    /**
     * Searches for a value with a full path. A path is a section and key name separated by a dot. A
     * path without a dot is assumed to be in the main section.
     *
     * Eg: get("foo.bar") is the same as section("foo").get("bar");
     */
    public function get (path :String) :String
    {
        var idx = path.indexOf(".");
        if (idx < 0) {
            return mainSection.get(path);
        }

        var section = sections.get(path.substr(0, idx));
        return (section != null) ? section.get(path.substr(idx+1)) : null;
    }

    /** Serialize back to an INI file. */
    public function toString () :String
    {
        // TODO(bruno): Use StringBuf if Flambe is ever ported to hxcpp
        var str = "";
        for (key in mainSection.keys()) {
            str += key + " = \"" + mainSection.get(key) + "\"\n";
        }
        for (name in sections.keys()) {
            str += "[" + name + "]\n";
            var section = sections.get(name);
            for (key in section.keys()) {
                str += key + " = \"" + section.get(key) + "\"\n";
            }
        }
        return str;
    }
}
<commit_msg>Unescape certain characters during parsing.<commit_after>//
// Flambe - Rapid game development
// https://github.com/aduros/flambe/blob/master/LICENSE.txt

package flambe.util;

using StringTools;

typedef ConfigSection = Map<String,String>;

/**
 * An INI-like config file parser.
 *
 * <pre>
 * ; This is a comment
 * foo = some value
 * [my section]
 * password = "  quotes are optional, and useful if you want to preserve surrounding spaces  "
 * </pre>
 */
class Config
{
    public var mainSection (default, null) :ConfigSection;
    public var sections (default, null) :Map<String,ConfigSection>;

    public function new ()
    {
        mainSection = new ConfigSection();
        sections = new Map();
    }

    /** Parse the contents of an INI file. */
    public static function parse (text :String) :Config
    {
        var config = new Config();

        var commentPattern = ~/^\s*;/;
        var sectionPattern = ~/^\s*\[\s*([^\]]*)\s*\]/;
        var pairPattern = ~/^\s*([\w\.\-_]+)\s*=\s*(.*)/;

        var currentSection = config.mainSection;
        for (line in ~/\r\n|\r|\n/g.split(text)) {
            if (commentPattern.match(line)) {
                // Ignore this line

            } else if (sectionPattern.match(line)) {
                var name = sectionPattern.matched(1);
                if (config.sections.exists(name)) {
                    // Handle duplicate sections, should this be allowed?
                    currentSection = config.sections.get(name);
                } else {
                    currentSection = new ConfigSection();
                    config.sections.set(name, currentSection);
                }

            } else if (pairPattern.match(line)) {
                var key = pairPattern.matched(1);
                var value = pairPattern.matched(2);
                var quote = value.fastCodeAt(0);
                if ((quote == "\"".code || quote == "'".code) &&
                        value.fastCodeAt(value.length-1) == quote) {
                    // Trim off quotes
                    value = value.substr(1, value.length-2);
                }
                currentSection.set(key, value
                    // Unescape certain characters
                    .replace("\\n", "\n")
                    .replace("\\r", "\r")
                    .replace("\\t", "\t")
                    .replace("\\'", "\'")
                    .replace("\\\"", "\"")
                    .replace("\\\\", "\\")
                );
            }
        }

        return config;
    }

    /** Shorthand for sections.get(name). */
    inline public function section (name :String) :ConfigSection
    {
        return sections.get(name);
    }

    /**
     * Searches for a value with a full path. A path is a section and key name separated by a dot. A
     * path without a dot is assumed to be in the main section.
     *
     * Eg: get("foo.bar") is the same as section("foo").get("bar");
     */
    public function get (path :String) :String
    {
        var idx = path.indexOf(".");
        if (idx < 0) {
            return mainSection.get(path);
        }

        var section = sections.get(path.substr(0, idx));
        return (section != null) ? section.get(path.substr(idx+1)) : null;
    }

    /** Serialize back to an INI file. */
    public function toString () :String
    {
        // TODO(bruno): Use StringBuf if Flambe is ever ported to hxcpp
        var str = "";
        for (key in mainSection.keys()) {
            str += key + " = \"" + mainSection.get(key) + "\"\n";
        }
        for (name in sections.keys()) {
            str += "[" + name + "]\n";
            var section = sections.get(name);
            for (key in section.keys()) {
                str += key + " = \"" + section.get(key) + "\"\n";
            }
        }
        return str;
    }
}

{'fraction_non_alphanumeric': 0.14330515407690209, 'fraction_numerical': 0.002727024815925825, 'mean_word_length': 1.6224526278155167, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 11, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3483939', 'n_tokens_mistral': 2109, 'n_tokens_neox': 2028, 'n_words': 578}