Repository navigation
Add TOML file parsing functionality #164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f2c0348
3951576
74c8a63
777948d
de3b6c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import ( | |
| "io" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "emperror.dev/errors" | ||
| "github.com/apex/log" | ||
|
|
@@ -14,6 +15,7 @@ import ( | |
| "github.com/goccy/go-json" | ||
| "github.com/icza/dyno" | ||
| "github.com/magiconair/properties" | ||
| "github.com/pelletier/go-toml/v2" | ||
| "github.com/tidwall/pretty" | ||
| "gopkg.in/ini.v1" | ||
| "gopkg.in/yaml.v3" | ||
|
|
@@ -30,6 +32,7 @@ const ( | |
| Ini = "ini" | ||
| Json = "json" | ||
| Xml = "xml" | ||
| Toml = "toml" | ||
| ) | ||
|
|
||
| type ReplaceValue struct { | ||
|
|
@@ -228,6 +231,8 @@ func (f *ConfigurationFile) Parse(file ufs.File) error { | |
| err = f.parseIniFile(file) | ||
| case Xml: | ||
| err = f.parseXmlFile(file) | ||
| case Toml: | ||
| err = f.parseTomlFile(file) | ||
| } | ||
| return err | ||
| } | ||
|
|
@@ -480,6 +485,90 @@ func (f *ConfigurationFile) parseYamlFile(file ufs.File) error { | |
| return nil | ||
| } | ||
|
|
||
| // Parses a toml file and updates any matching key/value pairs before persisting | ||
| // it back to the disk. | ||
| func (f *ConfigurationFile) parseTomlFile(file ufs.File) error { | ||
| b, err := io.ReadAll(file) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| i := make(map[string]interface{}) | ||
| if err := toml.Unmarshal(b, &i); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Convert to JSON to reuse IterateOverJson for value replacement. | ||
| jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| data, err := f.IterateOverJson(jsonBytes) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| var jsonData interface{} | ||
| decoder := json.NewDecoder(bytes.NewReader(data)) | ||
| decoder.UseNumber() | ||
| if err := decoder.Decode(&jsonData); err != nil { | ||
| return err | ||
| } | ||
| jsonData = normalizeTomlTypes(jsonData) | ||
|
|
||
| // Remarshal back to TOML format. | ||
| marshaled, err := toml.Marshal(jsonData) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if _, err := file.Seek(0, io.SeekStart); err != nil { | ||
| return err | ||
| } | ||
| if err := file.Truncate(0); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if _, err := io.Copy(file, bytes.NewReader(marshaled)); err != nil { | ||
| return errors.Wrap(err, "parser: failed to write toml file to disk") | ||
| } | ||
| return nil | ||
| } | ||
|
vanlueckn marked this conversation as resolved.
|
||
|
|
||
| func normalizeTomlTypes(value interface{}) interface{} { | ||
| switch typed := value.(type) { | ||
| case map[string]interface{}: | ||
| for key, item := range typed { | ||
| typed[key] = normalizeTomlTypes(item) | ||
| } | ||
| return typed | ||
| case []interface{}: | ||
| for i := range typed { | ||
| typed[i] = normalizeTomlTypes(typed[i]) | ||
| } | ||
| return typed | ||
| case json.Number: | ||
| if intVal, err := typed.Int64(); err == nil { | ||
| return intVal | ||
| } | ||
| if floatVal, err := typed.Float64(); err == nil { | ||
| return floatVal | ||
| } | ||
| return typed.String() | ||
| case string: | ||
| if timeVal, err := time.Parse(time.RFC3339Nano, typed); err == nil { | ||
| return timeVal | ||
| } | ||
| if timeVal, err := time.Parse(time.RFC3339, typed); err == nil { | ||
| return timeVal | ||
| } | ||
| return typed | ||
|
Comment on lines
+559
to
+566
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Time string coercion may produce false positives on plain string values. After the JSON round-trip, both TOML native datetimes and plain string values that happen to match RFC 3339 format are indistinguishable — they're all strings. This means a TOML value like This is an inherent limitation of the JSON round-trip approach and probably acceptable for the game-config use case. Just worth being aware of if TOML configs with date-like string values are encountered. 🤖 Prompt for AI Agents |
||
| default: | ||
| return value | ||
| } | ||
| } | ||
|
|
||
| // Parses a text file using basic find and replace. This is a highly inefficient method of | ||
| // scanning a file and performing a replacement. You should attempt to use anything other | ||
| // than this function where possible. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.