Support Unmarshaler interface for tables and array tables (#1027)
Fixes #873 Extend the unstable.Unmarshaler interface support to work with tables and array tables, not just single values. When a type implementing unstable.Unmarshaler is the target of a table (e.g., [table] or [[array]]), the UnmarshalTOML method receives a synthetic InlineTable node containing all the key-value pairs belonging to that table. Key changes: - Add handleKeyValuesUnmarshaler to collect and process table content - Add copyExpressionNodes to deep-copy AST nodes for synthetic tables - Add helper functions in unstable/ast.go for node manipulation - Update documentation for EnableUnmarshalerInterface - Add comprehensive tests for table and array table unmarshaling * Implement bytes-based Unmarshaler interface for tables and arrays (#873) This change brings back support for the unstable.Unmarshaler interface for tables and array tables, addressing issue #873. Key changes: - Changed UnmarshalTOML signature from (*Node) to ([]byte) to provide raw TOML bytes instead of AST nodes - Added RawMessage type (similar to json.RawMessage) for capturing raw TOML bytes for later processing - Updated handleKeyValuesUnmarshaler to reconstruct key-value lines from the parsed keys and raw value bytes - Added support for slice types implementing Unmarshaler (e.g., RawMessage) - Removed unused AST helper functions from unstable/ast.go The bytes-based interface allows users to: - Get raw TOML bytes for custom parsing - Delay TOML decoding using RawMessage - Implement custom unmarshaling logic for complex types Tests added for: - Table unmarshaler with various scenarios - Array table unmarshaler - Split tables (same parent defined in multiple places) - RawMessage usage - Nested tables and mixed regular fields * Fix lint issues and improve test coverage for Unmarshaler interface - Apply De Morgan's law in keyNeedsQuoting to satisfy staticcheck QF1001 - Remove unused splitTableUnmarshaler type from test - Fix unused parameter lint warning in errorUnmarshaler873 - Add test for quoted keys that need special handling - Add test for error propagation from UnmarshalTOML - Update customTable873 parser to handle quoted keys properly Coverage improved: - handleKeyValuesUnmarshaler: 80.0% -> 93.3% - keyNeedsQuoting: 66.7% -> 83.3% - Overall main package: 97.2% -> 97.5% * Add test for dotted keys to improve coverage Add TestIssue873_DottedKeys to test dotted key handling (e.g., sub.key = value) in the Unmarshaler interface. This improves coverage for handleKeyValuesUnmarshaler from 93.3% to 96.7%. * Add double pointer test to achieve 100% coverage for handleKeyValues Add TestIssue873_DoublePointerUnmarshaler to test pointer-to-pointer to Unmarshaler types. This covers the pointer dereferencing loop in handleKeyValues, bringing its coverage from 88% to 100%. Total coverage: 97.4% * Add Example tests and fix raw value extraction for boolean types Add two godoc Example tests: - ExampleDecoder_EnableUnmarshalerInterface_dynamicConfig: shows dynamic unmarshaling based on a type field - ExampleDecoder_EnableUnmarshalerInterface_rawMessage: demonstrates RawMessage usage for deferred parsing Fix handleKeyValuesUnmarshaler to handle values where Raw.Length == 0 (like boolean types) by using value.Data as fallback. * Preserve original formatting in Unmarshaler by using raw byte ranges Instead of reconstructing key-value lines from parsed components, now uses the original raw bytes from the document. This preserves: - Whitespace around '=' (e.g., "key = value") - String quoting style (basic vs literal) - Number formats (hex, octal, binary) - Inline table formatting Changes: - Add Raw range tracking to KeyValue expressions in parseKeyval - Update handleKeyValuesUnmarshaler to use expr.Raw directly - Remove keyNeedsQuoting helper (no longer needed) - Add TestIssue873_FormattingPreservation test - Update expected output in ExampleParser_comments * Prevent test matrix from canceling on first failure Add fail-fast: false to the test workflow strategy so that all OS/Go version combinations continue running even if one fails. This provides better visibility into which specific combinations have issues. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -328,6 +328,9 @@ func (p *Parser) parseStdTable(b []byte) (reference, []byte, error) {
|
||||
|
||||
func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) {
|
||||
// keyval = key keyval-sep val
|
||||
// Track the start position for Raw range
|
||||
startB := b
|
||||
|
||||
ref := p.builder.Push(Node{
|
||||
Kind: KeyValue,
|
||||
})
|
||||
@@ -360,6 +363,10 @@ func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) {
|
||||
p.builder.Chain(valRef, key)
|
||||
p.builder.AttachChild(ref, valRef)
|
||||
|
||||
// Set Raw to span the entire key-value expression
|
||||
node := p.builder.NodeAt(ref)
|
||||
node.Raw = p.rangeOfToken(startB[:len(startB)-len(b)], b)
|
||||
|
||||
return ref, b, err
|
||||
}
|
||||
|
||||
|
||||
@@ -539,7 +539,7 @@ key5 = [ # Next to start of inline array.
|
||||
// ---
|
||||
// 6:1->6:22 (105->126) | Comment [# Above simple value.]
|
||||
// ---
|
||||
// 1:1->1:1 (0->0) | KeyValue []
|
||||
// 7:1->7:14 (127->140) | KeyValue []
|
||||
// 7:7->7:14 (133->140) | String [value]
|
||||
// 7:1->7:4 (127->130) | Key [key]
|
||||
// 7:15->7:38 (141->164) | Comment [# Next to simple value.]
|
||||
@@ -552,12 +552,12 @@ key5 = [ # Next to start of inline array.
|
||||
// ---
|
||||
// 14:1->14:22 (252->273) | Comment [# Above inline table.]
|
||||
// ---
|
||||
// 1:1->1:1 (0->0) | KeyValue []
|
||||
// 15:1->15:50 (274->323) | KeyValue []
|
||||
// 15:8->15:9 (281->282) | InlineTable []
|
||||
// 1:1->1:1 (0->0) | KeyValue []
|
||||
// 15:10->15:23 (283->296) | KeyValue []
|
||||
// 15:18->15:23 (291->296) | String [Tom]
|
||||
// 15:10->15:15 (283->288) | Key [first]
|
||||
// 1:1->1:1 (0->0) | KeyValue []
|
||||
// 15:25->15:48 (298->321) | KeyValue []
|
||||
// 15:32->15:48 (305->321) | String [Preston-Werner]
|
||||
// 15:25->15:29 (298->302) | Key [last]
|
||||
// 15:1->15:5 (274->278) | Key [name]
|
||||
@@ -567,7 +567,7 @@ key5 = [ # Next to start of inline array.
|
||||
// ---
|
||||
// 18:1->18:15 (371->385) | Comment [# Above array.]
|
||||
// ---
|
||||
// 1:1->1:1 (0->0) | KeyValue []
|
||||
// 19:1->19:20 (386->405) | KeyValue []
|
||||
// 1:1->1:1 (0->0) | Array []
|
||||
// 19:11->19:12 (396->397) | Integer [1]
|
||||
// 19:14->19:15 (399->400) | Integer [2]
|
||||
@@ -579,7 +579,7 @@ key5 = [ # Next to start of inline array.
|
||||
// ---
|
||||
// 22:1->22:26 (448->473) | Comment [# Above multi-line array.]
|
||||
// ---
|
||||
// 1:1->1:1 (0->0) | KeyValue []
|
||||
// 23:1->31:2 (474->694) | KeyValue []
|
||||
// 1:1->1:1 (0->0) | Array []
|
||||
// 23:10->23:42 (483->515) | Comment [# Next to start of inline array.]
|
||||
// 24:3->24:38 (518->553) | Comment [# Second line before array content.]
|
||||
|
||||
+28
-3
@@ -1,7 +1,32 @@
|
||||
package unstable
|
||||
|
||||
// The Unmarshaler interface may be implemented by types to customize their
|
||||
// behavior when being unmarshaled from a TOML document.
|
||||
// Unmarshaler is implemented by types that can unmarshal a TOML
|
||||
// description of themselves. The input is a valid TOML document
|
||||
// containing the relevant portion of the parsed document.
|
||||
//
|
||||
// For tables (including split tables defined in multiple places),
|
||||
// the data contains the raw key-value bytes from the original document
|
||||
// with adjusted table headers to be relative to the unmarshaling target.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalTOML(value *Node) error
|
||||
UnmarshalTOML(data []byte) error
|
||||
}
|
||||
|
||||
// RawMessage is a raw encoded TOML value. It implements Unmarshaler
|
||||
// and can be used to delay TOML decoding or capture raw content.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// type Config struct {
|
||||
// Plugin RawMessage `toml:"plugin"`
|
||||
// }
|
||||
//
|
||||
// var cfg Config
|
||||
// toml.NewDecoder(r).EnableUnmarshalerInterface().Decode(&cfg)
|
||||
// // cfg.Plugin now contains the raw TOML bytes for [plugin]
|
||||
type RawMessage []byte
|
||||
|
||||
// UnmarshalTOML implements Unmarshaler.
|
||||
func (m *RawMessage) UnmarshalTOML(data []byte) error {
|
||||
*m = append((*m)[0:0], data...)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user