<commit_before>//INI files specific functions

package cfgp

import (
	"bufio"
	"errors"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"strings"

	"github.com/eraclitux/stracer"
)

//FIXME has a [2]string sense instead of map[string]string?

// parseKeyValue given one line encoded like "key = value" returns corresponding
// []string with "key" > kv[0] and "value" > kv[1].
func parseKeyValue(line string) []string {
	// Check for inline comments.
	if strings.Contains(line, ";") {
		line = strings.Split(line, ";")[0]
	} else if strings.Contains(line, "#") {
		line = strings.Split(line, "#")[0]
	}
	line = strings.Replace(line, " ", "", -1)
	// Does nothing if no "=" sign.
	if strings.Contains(line, "=") {
		return strings.Split(line, "=")

	}
	return nil
}

func getStructValue(confPtr interface{}) (reflect.Value, error) {
	v := reflect.ValueOf(confPtr)
	if v.Kind() == reflect.Ptr {
		return v.Elem(), nil
	}
	return reflect.Value{}, errors.New("pointer to struct expected")
}

func putInStruct(structValue reflect.Value, kv []string) error {
	// FIXME handle different types.
	stracer.Traceln("handling", kv)
	f := strings.Title(kv[0])
	fieldValue := structValue.FieldByName(f)
	stracer.Traceln("k to title:", f, "type:", fieldValue.Kind(), "is settable:", fieldValue.CanSet())
	if fieldValue.CanSet() {
		switch fieldValue.Kind() {
		case reflect.Int:
			i, err := strconv.Atoi(kv[1])
			if err != nil {
				return err
			}
			fieldValue.SetInt(int64(i))
		case reflect.String:
			fieldValue.SetString(kv[1])
		}
	}
	return nil
}

// parseINI opens configuration file specified by path and populate Conf.IniData.
// Files must follows INI informal standard:
//
//	https://en.wikipedia.org/wiki/INI_file
//
func parseINI(path string, confPtr interface{}) error {
	conf := make(map[string][][]string)
	structValue, err := getStructValue(confPtr)
	if err != nil {
		return err
	}
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	sectionExp := regexp.MustCompile(`^(\[).+(\])$`)
	commentExp := regexp.MustCompile(`^(#|;)`)
	// Adds default section "default" in case no one is specified
	section := "default"
	for scanner.Scan() {
		line := scanner.Text()
		stracer.Traceln("raw line to parse:", line)
		if commentExp.MatchString(line) {
			continue
		} else if sectionExp.MatchString(line) {
			// Removes spaces too so "[ section]" is parsed correctly
			section = strings.Trim(line, "[] ")
			continue
		}
		kv := parseKeyValue(line)
		// This even prevents empty line to be added
		if len(kv) > 0 {
			putInStruct(structValue, kv)
			conf[section] = append(conf[section], kv)
		}
	}
	if err := scanner.Err(); err != nil {
		return err
	}
	stracer.Traceln("coded map:", conf)
	return nil
}
<commit_msg>Define custom error to return<commit_after>//INI files specific functions

package cfgp

import (
	"bufio"
	"errors"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"strings"

	"github.com/eraclitux/stracer"
)

var ErrNeedPointer = errors.New("pointer to struct expected")

// parseKeyValue given one line encoded like "key = value" returns corresponding
// []string with "key" > kv[0] and "value" > kv[1].
func parseKeyValue(line string) []string {
	// Check for inline comments.
	if strings.Contains(line, ";") {
		line = strings.Split(line, ";")[0]
	} else if strings.Contains(line, "#") {
		line = strings.Split(line, "#")[0]
	}
	line = strings.Replace(line, " ", "", -1)
	// Does nothing if no "=" sign.
	if strings.Contains(line, "=") {
		return strings.Split(line, "=")

	}
	return nil
}

func getStructValue(confPtr interface{}) (reflect.Value, error) {
	v := reflect.ValueOf(confPtr)
	if v.Kind() == reflect.Ptr {
		return v.Elem(), nil
	}
	return reflect.Value{}, ErrNeedPointer
}

func putInStruct(structValue reflect.Value, kv []string) error {
	// FIXME handle different types.
	stracer.Traceln("handling pair:", kv)
	f := strings.Title(kv[0])
	fieldValue := structValue.FieldByName(f)
	stracer.Traceln("k to title:", f, "kind in struct:", fieldValue.Kind(), "is settable:", fieldValue.CanSet())
	if fieldValue.CanSet() {
		switch fieldValue.Kind() {
		case reflect.Int:
			i, err := strconv.Atoi(kv[1])
			if err != nil {
				return err
			}
			fieldValue.SetInt(int64(i))
		case reflect.String:
			fieldValue.SetString(kv[1])
		}
	}
	return nil
}

// parseINI opens configuration file specified by path and populate Conf.IniData.
// Files must follows INI informal standard:
//
//	https://en.wikipedia.org/wiki/INI_file
//
func parseINI(path string, confPtr interface{}) error {
	conf := make(map[string][][]string)
	structValue, err := getStructValue(confPtr)
	if err != nil {
		return err
	}
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	sectionExp := regexp.MustCompile(`^(\[).+(\])$`)
	commentExp := regexp.MustCompile(`^(#|;)`)
	// Adds default section "default" in case no one is specified
	section := "default"
	for scanner.Scan() {
		line := scanner.Text()
		stracer.Traceln("raw line to parse:", line)
		if commentExp.MatchString(line) {
			continue
		} else if sectionExp.MatchString(line) {
			// Removes spaces too so "[ section]" is parsed correctly
			section = strings.Trim(line, "[] ")
			continue
		}
		kv := parseKeyValue(line)
		// This even prevents empty line to be added
		if len(kv) > 0 {
			putInStruct(structValue, kv)
			conf[section] = append(conf[section], kv)
		}
	}
	if err := scanner.Err(); err != nil {
		return err
	}
	stracer.Traceln("coded map:", conf)
	return nil
}

{'fraction_non_alphanumeric': 0.15873873873873873, 'fraction_numerical': 0.004144144144144144, 'mean_word_length': 3.877855887521968, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, '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': '4281748', 'n_tokens_mistral': 2047, 'n_tokens_neox': 1923, 'n_words': 510}