package consenttool import ( "bufio" "os" "path/filepath" "strconv" "strings" ) type Config struct { Consent ConsentConfig Log LogConfig } type ConsentConfig struct { InputCSV string OutputJSON string DisplayFields []string ConsentFields []string ConsentValues []string NormalizeCase bool AllowDuplicates bool } type LogConfig struct { InputPath string OutputPath string IncludeRegex string KeepNonMatch bool } func LoadConfig(path string) (*Config, error) { cfg := &Config{ Consent: ConsentConfig{ InputCSV: "config/consent.csv", OutputJSON: "config/consent_list.json", DisplayFields: []string{"display_name", "vrchat_name", "name"}, ConsentFields: []string{"consent", "agree", "agreed", "checked"}, ConsentValues: []string{"1", "true", "yes", "y", "on", "checked"}, NormalizeCase: false, AllowDuplicates: false, }, Log: LogConfig{ InputPath: "runtime/output_log.txt", OutputPath: "runtime/research_log.txt", IncludeRegex: `\[[A-Z0-9:_ -]+\]`, KeepNonMatch: false, }, } file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() var section string scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) line = strings.TrimPrefix(line, "\ufeff") if line == "" || strings.HasPrefix(line, "#") { continue } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { section = strings.Trim(line, "[]") continue } parts := strings.SplitN(line, "=", 2) if len(parts) != 2 { continue } key := strings.TrimSpace(parts[0]) val := strings.TrimSpace(parts[1]) val = strings.Trim(val, "\"'") switch section { case "consent": switch key { case "input_csv": cfg.Consent.InputCSV = val case "output_json": cfg.Consent.OutputJSON = val case "display_fields": cfg.Consent.DisplayFields = parseList(val) case "consent_fields": cfg.Consent.ConsentFields = parseList(val) case "consent_values": cfg.Consent.ConsentValues = parseList(val) case "normalize_case": cfg.Consent.NormalizeCase = parseBool(val) case "allow_duplicates": cfg.Consent.AllowDuplicates = parseBool(val) } case "log": switch key { case "input_path": cfg.Log.InputPath = val case "output_path": cfg.Log.OutputPath = val case "include_regex": cfg.Log.IncludeRegex = val case "keep_non_match": cfg.Log.KeepNonMatch = parseBool(val) } } } return cfg, scanner.Err() } func parseList(value string) []string { value = strings.TrimSpace(value) value = strings.TrimPrefix(value, "[") value = strings.TrimSuffix(value, "]") if value == "" { return nil } items := strings.Split(value, ",") out := make([]string, 0, len(items)) for _, item := range items { item = strings.TrimSpace(item) item = strings.Trim(item, "\"'") if item != "" { out = append(out, item) } } return out } func parseBool(value string) bool { n, err := strconv.ParseBool(strings.TrimSpace(value)) return err == nil && n } func resolvePath(baseDir, value string) string { if value == "" { return "" } if filepath.IsAbs(value) { return value } return filepath.Clean(filepath.Join(baseDir, value)) }