cgpdata

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 14 Imported by: 0

README

go-cgp-data

A Go parser and serializer for the CommuniGate Pro internal Data Format: the textual representation CGPro uses for settings files, CLI/API command responses, and similar data.

import cgpdata "github.com/gmyzovsky/go-cgp-data"

Supported object kinds

Format Go type
Atoms and quoted strings cgpdata.String
DataBlocks ([Base64...]) cgpdata.DataBlock
Numbers (#123, #0x1F, ...) cgpdata.Number
Time Stamps (#T22-10-2009_...) cgpdata.TimeStamp
IP Addresses (#I[...]:port) cgpdata.IPAddress
The Null object (#NULL#) cgpdata.Null
Arrays ((a, b, c)) cgpdata.Array
Dictionaries ({k=v; ...}) cgpdata.Dictionary
Embedded XML cgpdata.XML

All nine types implement the sealed cgpdata.Value interface, so a type switch over a Value is exhaustive:

switch v := v.(type) {
case cgpdata.String:
case cgpdata.DataBlock:
case cgpdata.Number:
case cgpdata.TimeStamp:
case cgpdata.IPAddress:
case cgpdata.Null:
case cgpdata.Array:
case cgpdata.Dictionary:
case cgpdata.XML:
}

Embedded XML objects (vCards, iCalendar, SDP, and similar structures the format allows to appear as raw XML) are not semantically decoded. A cgpdata.XML value holds the exact source text of the XML document verbatim; this package only tokenizes it far enough to find where it ends.

Parsing

Parse reads a single object from a byte slice:

v, err := cgpdata.Parse([]byte(`{Name="Alice"; Age=#30; Tags=(admin, ops);}`))
if err != nil {
    log.Fatal(err)
}

dict := v.(cgpdata.Dictionary)
name, _ := dict.Get("Name") // cgpdata.String("Alice")

ParseString is a convenience wrapper for string input. For a stream containing several objects back to back, use Decoder. The recommended loop drives Decode directly and treats io.EOF as the end of the stream:

dec := cgpdata.NewDecoder(r)
for {
    v, err := dec.Decode()
    if errors.Is(err, io.EOF) {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
    handle(v)
}

A for dec.More() { ... } loop also works, but More returns false both at the end of the input and when the Decoder has failed (a read error, an unterminated comment); if you use it, check dec.Err() after the loop.

A Decoder reads its entire input into memory before decoding the first object: an embedded XML document's extent can only be found by tokenizing it, and the format has no length prefix to bound that lookahead otherwise. The amount of input a Decoder will read is bounded by Options.MaxSize (64 MiB by default).

Parse errors are returned as *cgpdata.SyntaxError, which carries the byte offset and 1-based line/column of the problem (the column counts Unicode characters, not bytes):

var synErr *cgpdata.SyntaxError
if errors.As(err, &synErr) {
    fmt.Printf("line %d, column %d: %v\n", synErr.Line, synErr.Column, synErr)
}

Parse modes and limits

The Data Format has two normative textual representations, and the CommuniGate Pro server accepts a number of deviations beyond both, so the grammar variant is an explicit choice. ParseWith and NewDecoderWith take an Options value:

v, err := cgpdata.ParseWith(data, cgpdata.Options{
    Mode:     cgpdata.ServerCompatible,
    MaxDepth: 100,       // Array/Dictionary/XML nesting; default 200
    MaxSize:  16 << 20,  // Decoder input bound; default 64 MiB
})
  • StrictMultiLine (the default, used by Parse and NewDecoder) - the multi-line text-file form: comments are recognized, and a string value is a run of consecutive string tokens
    • atoms and quoted strings in any combination - that concatenate into one value, the grammar's way of splitting a long string across lines. Foo "Bar" and Foo Bar both parse as FooBar.
  • StrictSingleLine - the single-line form used in CLI/API responses: no comments, no string concatenation, and the only whitespace between tokens is the space character.
  • ServerCompatible - parses the way the server actually does: a UTF-8 BOM before any object is skipped, Base64 is decoded leniently (unpadded [YQ] is fine, foreign characters are skipped, = ends the block, and a truncated group still yields the byte it began), the extra escapes \b, \f, \/, \' and \uXXXX are accepted, numbers may use a leading + and uppercase 0X/0O/0B prefixes, #(name:address) object references parse as Null, a repeated dictionary key replaces the earlier value, IP addresses follow the server's own reader (optional brackets, decimal IPv4 octets that may carry leading zeros, hex IPv6 groups of any length, blanks around the separators, and the dotted v6.x.x.x.x.x.x.x.x.v6 notation), and Time Stamp fields may be digit runs of any width with the seconds left out. Unlike the strict modes, it concatenates only quoted strings with quoted strings, as the server does: Foo"Bar" and "Foo" Bar are rejected, "Foo" "Bar" is not.

Because the server keeps a Time Stamp as a count of seconds from the start of 1970, ServerCompatible reads a date it cannot count that way as a sentinel rather than as an error: #TPAST below the epoch - including the epoch second itself, which is the server's own remote-past marker - and #TFUTURE from year 2100 on. A year below 100 is a two-digit year mapped into 1970-2069.

In every mode, a parsed String is guaranteed to be valid UTF-8 with no NUL byte.

Serializing

Marshal renders a Value as compact, single-line text:

data, err := cgpdata.Marshal(cgpdata.Dictionary{
    {Key: "Name", Value: cgpdata.String("Alice")},
    {Key: "Age", Value: cgpdata.Number(30)},
})
// data: {Name=Alice; Age=#30; }

MarshalIndent, or Encoder with SetIndent, spread Array and Dictionary elements across multiple indented lines instead:

data, err := cgpdata.MarshalIndent(v, "", "  ")

String values are written as a bare atom when their content allows it (letters, digits, . - _ @, and any non-ASCII character), and as a backslash-escaped quoted string otherwise. Number is always emitted in decimal, regardless of the base it may have been parsed from.

Validation

Marshal, MarshalIndent and Encoder.Encode validate the value first and refuse to encode anything whose textual form would be invalid or would parse back to a different value - a malformed XML value, duplicate dictionary keys, an invalid IPAddress or a port outside 0-65535, a TimeStamp with sub-second precision, a string that is not valid UTF-8 or contains NUL, a cyclic or too deeply nested structure. For every value they accept, the round-trip invariant holds:

data, err := cgpdata.Marshal(v)
if err != nil {
    log.Fatal(err) // v is not a well-formed value
}
back, err := cgpdata.Parse(data)
if err != nil {
    log.Fatal(err) // cannot happen: Marshal only writes parsable text
}
cgpdata.Equal(back, v) // true whenever Marshal succeeded

The same check is exported as cgpdata.Validate(v) error.

Working with Dictionaries and comparing values

Dictionary is an ordered slice of key/value pairs, not a Go map, so that the order the textual format preserves survives a parse/marshal round trip. In the strict modes Parse reports an error if a key repeats; in ServerCompatible mode the last value wins, as on the server.

d := cgpdata.Dictionary{{Key: "A", Value: cgpdata.Number(1)}}
d = d.Set("B", cgpdata.Number(2)) // returns an updated copy
v, ok := d.Get("A")
for _, key := range d.Keys() { ... }

Because a TimeStamp embeds a time.Time and an IPAddress embeds a netip.Addr, comparing two Values with == or reflect.DeepEqual can report unequal for values that represent the same instant or address in different internal forms. Use cgpdata.Equal instead, which compares TimeStamps by the instant they represent and recurses correctly through Array and Dictionary:

cgpdata.Equal(a, b)

Equal compares Dictionaries as unordered sets of key/value pairs: two Dictionaries with the same pairs in a different order are equal. To compare the order-preserving textual representation instead, compare the Marshal outputs.

Design notes

  • IP addresses are backed by net/netip.Addr. The strict modes delegate parsing and validation to the standard library rather than reimplementing the specification's simplified IPv6 grammar; the address must be bracketed, as the grammar requires. ServerCompatible cannot use it, because the server has its own, laxer address reader, so that mode parses the address itself (optional brackets, decimal IPv4 octets that may carry leading zeros, hex IPv6 groups of any length, blanks around the separators, and the dotted v6.x.x.x.x.x.x.x.x.v6 notation) and builds the netip.Addr from the groups it read. In every mode, IPv6 zones, which the format cannot represent, are rejected.
  • Time Stamps always store the represented instant in UTC, truncated to whole seconds (the format's precision), and expose the #TPAST/#TFUTURE sentinels as TimeStampPast()/TimeStampFuture(). A Time Stamp with no time-of-day parses to midnight UTC; Marshal always writes the full _HH:MM:SS suffix, since it is an equally valid encoding of the same value.
  • Application-specific object references (#(objectName:address)), which the specification produces only when the server serializes custom internal objects and states must never appear in input, are rejected by the strict modes with a *SyntaxError; ServerCompatible mode turns them into Null, as the server does.
  • Limits: nesting depth and Decoder input size are bounded (configurable via Options), and Marshal/Equal detect cyclic values instead of recursing forever.

Testing

go test ./...
go test -run=NONE -fuzz=FuzzParse -fuzztime=60s -fuzzminimizetime=5s .

-fuzzminimizetime is worth setting for a time-boxed run: the engine minimizes every input that expands coverage, and its 60-second default can outlast a short -fuzztime and end the run with a "context deadline exceeded" failure that says nothing about the code.

Documentation

Overview

Package cgpdata implements a parser and serializer for the CommuniGate Pro internal Data Format, as specified at https://doc.communigatepro.ru/development/Data.html.

The format describes a small, closed set of object kinds: Strings (atoms and quoted strings), DataBlocks (Base64-encoded binary data), Numbers (64-bit signed integers), Time Stamps, IP Addresses, a Null object, ordered Arrays, and Dictionaries. This package represents each kind as a distinct Go type implementing the Value interface: Null, String, DataBlock, Number, TimeStamp, IPAddress, Array, and Dictionary.

Embedded XML objects (vCards, iCalendar, SDP, and similar structures the format allows to appear as raw XML) are not semantically decoded. They are preserved verbatim as XML, an opaque string holding the exact source text of the XML document.

Parsing

Parse parses a single object from a byte slice. Decoder parses a sequence of objects from an io.Reader, similar to encoding/json. A Decoder reads its entire input into memory (bounded by Options.MaxSize) before decoding the first object, since the format allows embedded XML documents whose extent can only be determined by tokenizing them.

ParseWith and NewDecoderWith accept Options: a ParseMode selecting between the strict multi-line and single-line grammars and a mode compatible with the CommuniGate Pro server's actual observed behavior, plus limits on input size and nesting depth.

In every mode, a parsed String is guaranteed to be valid UTF-8 and to contain no NUL byte.

Serializing

Marshal renders a Value as compact, single-line text. Use Encoder with Encoder.SetIndent, or MarshalIndent, to spread Array and Dictionary elements across multiple indented lines.

All three validate the value with Validate first, and refuse to encode a value whose text would be syntactically invalid or would parse back to a different value: whenever they succeed, parsing the text they wrote yields a value Equal to the one they were given.

Example (EmbeddedXML)

Embedded XML objects are preserved verbatim as opaque text rather than being parsed into a structured Go type.

package main

import (
	"fmt"

	cgpdata "github.com/gmyzovsky/go-cgp-data"
)

func main() {
	v, err := cgpdata.ParseString(`{Contact=<vCard><NAME><VALUE>Bjorn Jensen</VALUE></NAME></vCard>;}`)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	dict := v.(cgpdata.Dictionary)
	contact, _ := dict.Get("Contact")
	fmt.Println(contact.(cgpdata.XML))

}
Output:
<vCard><NAME><VALUE>Bjorn Jensen</VALUE></NAME></vCard>

Index

Examples

Constants

View Source
const (
	// DefaultMaxDepth bounds the combined nesting depth of Arrays,
	// Dictionaries and embedded XML elements accepted by the parser
	// and by [Validate].
	DefaultMaxDepth = 200

	// DefaultMaxSize bounds the input size, in bytes, that a [Decoder]
	// reads into memory.
	DefaultMaxSize = 64 << 20 // 64 MiB
)

Default limits, applied when the corresponding Options field is zero.

Variables

This section is empty.

Functions

func Equal

func Equal(a, b Value) bool

Equal reports whether a and b represent the same CommuniGate Pro data object. Unlike reflect.DeepEqual, it compares TimeStamp values by the instant in time they represent (via time.Time.Equal) rather than by field-for-field equality of the underlying time.Time, and it compares Array and Dictionary elements recursively with Equal.

Dictionaries are compared as unordered sets of key/value pairs: two Dictionaries with the same pairs in a different order are equal. To compare the order-preserving textual representation instead, compare the Marshal outputs.

Cyclic values are handled the way reflect.DeepEqual handles them: a pair of values already being compared further up the call stack is assumed equal rather than recursed into, so Equal always terminates.

func Marshal

func Marshal(v Value) ([]byte, error)

Marshal returns the compact, single-line CommuniGate Pro textual representation of v.

Marshal validates v with Validate first and refuses to encode a value that would produce syntactically invalid text or text that parses back to a different value: whenever Marshal succeeds, parsing its output yields a value Equal to v.

Example
package main

import (
	"fmt"

	cgpdata "github.com/gmyzovsky/go-cgp-data"
)

func main() {
	v := cgpdata.Dictionary{
		{Key: "Name", Value: cgpdata.String("Alice")},
		{Key: "Age", Value: cgpdata.Number(30)},
	}

	data, err := cgpdata.Marshal(v)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(string(data))

}
Output:
{Name=Alice; Age=#30; }

func MarshalIndent

func MarshalIndent(v Value, prefix, indent string) ([]byte, error)

MarshalIndent is like Marshal, but formats the output with the given prefix and indent applied at each nesting level of Arrays and Dictionaries, similar to encoding/json.MarshalIndent. Both prefix and indent must consist only of whitespace (space, tab, CR, LF), so that the output remains parseable; anything else is an error.

func Validate added in v0.2.0

func Validate(v Value) error

Validate reports whether v is a well-formed CommuniGate Pro data object that can be marshaled into text that parses back to an equal value. It checks, recursively:

  • every String, including Dictionary keys, is valid UTF-8 and contains no NUL byte;
  • Dictionary keys are unique;
  • a DataBlock is not empty (the grammar has no representation for an empty one);
  • a TimeStamp is a sentinel, or carries a non-zero UTC time with whole-second precision and a year in the normative 1970-2038 range;
  • an IPAddress has a valid address without an IPv6 zone and a port in the range -1 (no port) to 65535;
  • an XML value is exactly one well-formed XML document starting with '<';
  • Arrays and Dictionaries nest no deeper than DefaultMaxDepth and contain no cycles.

Marshal, MarshalIndent and Encoder.Encode call Validate before writing anything, so they can never emit text that is syntactically invalid or that changes meaning when parsed back.

Types

type Array

type Array []Value

Array is an ordered sequence of Values, represented in text as a comma-separated, parenthesized list.

type DataBlock

type DataBlock []byte

DataBlock is a block of arbitrary binary data, represented in text as Base64 enclosed in square brackets, e.g. "[HcqHfHI=]".

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder reads a sequence of CommuniGate Pro data objects from an input stream, similar to encoding/json.Decoder.

A Decoder reads its entire input into memory (up to Options.MaxSize bytes) the first time Decode or More is called, rather than truly streaming it, because an embedded XML object's extent can only be found by tokenizing it and the format has no length prefix to bound that lookahead otherwise.

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder returns a new Decoder that reads from r using the zero Options.

func NewDecoderWith added in v0.2.0

func NewDecoderWith(r io.Reader, opts Options) *Decoder

NewDecoderWith returns a new Decoder that reads from r according to opts.

func (*Decoder) Decode

func (d *Decoder) Decode() (Value, error)

Decode reads the next CommuniGate Pro object from the input stream and returns its Value representation. It returns io.EOF when there are no more objects to read. Any other error is sticky: every subsequent Decode call returns it again.

func (*Decoder) Err added in v0.2.0

func (d *Decoder) Err() error

Err returns the first error the Decoder encountered, or nil. It never returns io.EOF: a clean end of input is not an error. Err reports, in particular, the errors that made Decoder.More return false, which More itself cannot distinguish from the end of the input.

func (*Decoder) InputOffset

func (d *Decoder) InputOffset() int64

InputOffset returns the byte offset of the Decoder's current position within its input.

func (*Decoder) More

func (d *Decoder) More() bool

More reports whether there is at least one more object to decode. It returns false both at a clean end of input and when the Decoder has failed (for example, because reading the input failed or an unterminated comment was found); after a loop conditioned on More, call Decoder.Err to distinguish the two.

type DictEntry

type DictEntry struct {
	Key   string
	Value Value
}

DictEntry is a single key/value pair within a Dictionary.

type Dictionary

type Dictionary []DictEntry

Dictionary is an ordered sequence of key/value pairs, represented in text as a sequence of "key=value;" pairs enclosed in curly braces. Keys are case-sensitive and unique within a Dictionary; Parse reports an error if a key repeats.

Dictionary is a slice rather than a map so that the insertion order preserved by the textual format survives a parse/marshal round trip.

func (Dictionary) Get

func (d Dictionary) Get(key string) (Value, bool)

Get returns the value associated with key and reports whether key was present.

func (Dictionary) Keys

func (d Dictionary) Keys() []string

Keys returns the Dictionary's keys, in order.

func (Dictionary) Set

func (d Dictionary) Set(key string, v Value) Dictionary

Set returns a copy of d with key set to v, replacing the value of an existing entry with that key or appending a new entry if key is not already present.

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder writes CommuniGate Pro textual object representations to an output stream.

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

NewEncoder returns a new Encoder that writes to w.

func (*Encoder) Encode

func (e *Encoder) Encode(v Value) error

Encode writes the textual representation of v, followed by a newline. Like Marshal, it validates v with Validate first and writes nothing if v is not a well-formed value or if a non- whitespace prefix/indent was configured with Encoder.SetIndent.

func (*Encoder) SetIndent

func (e *Encoder) SetIndent(prefix, indent string)

SetIndent configures the Encoder to format each subsequent [Encode] call with the given prefix and per-level indent, spreading Array and Dictionary elements across multiple lines. The zero value (no prefix, no indent) produces compact single-line output. Both prefix and indent must consist only of whitespace (space, tab, CR, LF), so that the output remains parseable; Encoder.Encode fails otherwise.

type IPAddress

type IPAddress struct {
	Addr netip.Addr
	// Port is the associated port number, or -1 if the object has no
	// port, e.g. "#I[10.0.44.55]" without a trailing ":port".
	Port int
}

IPAddress is a CommuniGate Pro IP Address object: an IPv4 or IPv6 address with an optional port number.

A well-formed IPAddress - the only kind Validate accepts and Marshal encodes - has a valid Addr without an IPv6 zone (the format cannot represent zones) and a Port in the range -1 (no port) to 65535.

func (IPAddress) HasPort

func (a IPAddress) HasPort() bool

HasPort reports whether the IP Address carries a port number.

func (IPAddress) String

func (a IPAddress) String() string

String returns the CommuniGate Pro textual representation of a, e.g. "#I[10.0.44.55]:25" or "#I[2001:470:1f01:2565::a:80f]".

type Null

type Null struct{}

Null represents the CommuniGate Pro #NULL# object, used to denote the absence of any other object.

type Number

type Number int64

Number is a 64-bit signed integer object. Its text form starts with "#", an optional "-", and decimal digits, or a 0x/0o/0b base indicator followed by digits of that base. Marshal always emits decimal.

type Options added in v0.2.0

type Options struct {
	// Mode selects the grammar variant.
	Mode ParseMode

	// MaxDepth bounds the combined nesting depth of Arrays,
	// Dictionaries and embedded XML elements. Zero (or a negative
	// value) means DefaultMaxDepth.
	MaxDepth int

	// MaxSize bounds, in bytes, how much input a Decoder reads into
	// memory before parsing; a Decoder whose input is longer fails
	// rather than truncating it. Zero (or a negative value) means
	// DefaultMaxSize. ParseWith ignores MaxSize, since its input is
	// already in memory.
	MaxSize int64
}

Options configures parsing for ParseWith and NewDecoderWith. The zero value selects StrictMultiLine, DefaultMaxDepth and DefaultMaxSize.

type ParseMode added in v0.2.0

type ParseMode int

ParseMode selects the grammar variant used when parsing.

The CommuniGate Pro Data Format has two normative textual representations - a single-line form used in CLI/API responses and a multi-line form used in settings and other text files - and, beyond both, the CommuniGate Pro server accepts a number of extensions and deviations from the formal grammar. No single grammar can be strict and server-compatible at the same time, so the mode is explicit.

const (
	// StrictMultiLine parses the multi-line text-file form of the
	// format: comments are recognized, and a string value is a
	// sequence of one or more consecutive string tokens - atoms and
	// quoted strings in any combination - that concatenate into a
	// single value, the grammar's way of splitting a long string
	// across lines. This is the default mode.
	StrictMultiLine ParseMode = iota

	// StrictSingleLine parses the single-line form used in CLI/API
	// responses: comments are not recognized, strings do not
	// concatenate, and the only whitespace between tokens is the
	// space character.
	StrictSingleLine

	// ServerCompatible parses input the way the CommuniGate Pro server
	// does, accepting its known deviations from the formal grammar: a
	// UTF-8 BOM in front of any object, nested ones included, is
	// skipped, though not one in front of a dictionary key, which is
	// read as a string rather than as an object; Base64 data is decoded
	// leniently (characters outside the alphabet are skipped, "=" ends
	// the block, a truncated group still contributes the byte it had
	// begun, and an empty data block is allowed); the additional
	// escapes \b, \f, \/, \' and \uXXXX are recognized; numbers may use
	// a leading "+" and uppercase 0X/0O/0B base prefixes;
	// application-specific object references (#(name:address)) parse as
	// [Null]; a repeated dictionary key replaces the earlier value
	// instead of failing; and the fields of a Time Stamp are digit runs
	// of any width whose seconds, and whose time of day as a whole, may
	// be left out, an empty run counting as zero.
	//
	// IP addresses follow the server's own address reader rather than
	// the normative grammar: the square brackets are optional, an IPv4
	// octet is a run of decimal digits below 256 (so "010.000.000.001"
	// is 10.0.0.1), an IPv6 group is a run of hexadecimal digits below
	// 0x10000 of any length, blanks may surround the separators, and an
	// IPv6 address may also be written in the dotted notation
	// v6.x.x.x.x.x.x.x.x.v6.
	//
	// Time Stamps also follow the server's value model rather than the
	// normative year range. The server counts seconds from the start of
	// 1970, so a date it cannot count that way is not an error but one
	// of the two sentinels: [TimeStampPast] below the epoch, including
	// the epoch second itself, which is the server's own remote-past
	// marker, and [TimeStampFuture] from year 2100 on. That mapping
	// happens before the calendar fields are checked, so an impossible
	// day or month on such a date is not diagnosed. A year below 100 is
	// a two-digit year, mapped into 1970-2069.
	//
	// It also mirrors the server's restrictions that the normative
	// multi-line grammar does not have: an atom never concatenates
	// with an adjacent quoted string (only quoted strings concatenate
	// with each other).
	//
	// Even in this mode, strings must be valid UTF-8, must never
	// contain NUL, and may not contain unescaped control characters;
	// that part of the package's string contract is not relaxed.
	//
	// The mode reproduces how the server reads the multi-line form. It
	// does not reproduce one observed difference in how the server
	// reads the single-line form of CLI/API traffic: there a Time Stamp
	// whose third character after "#T" is a digit is taken to be
	// written in the compact calendar form (YYYYMMDD with an optional
	// Thhmmss), so "#T1-1-5" is rejected there while "#T01-01-69" is
	// not.
	//
	// Note that this mode can produce values outside the normative
	// format - an empty DataBlock, a TimeStamp between 2039 and 2099 -
	// which [Marshal] will refuse to encode, since their output could
	// not be parsed back by the strict grammar.
	ServerCompatible
)

type String

type String string

String is a CommuniGate Pro string. In text it is represented either as an atom (a run of letters, digits, and the punctuation ". - _ @", plus any non-ASCII UTF-8 character) or as a quoted string with backslash escapes. Parse unescapes quoted strings; Marshal prefers the atom form when the value's content allows it, and falls back to a quoted string otherwise.

type SyntaxError

type SyntaxError struct {
	Offset int64
	Line   int
	Column int
	// contains filtered or unexported fields
}

SyntaxError reports a malformed CommuniGate Pro data object, including the byte offset and the 1-based line/column at which the problem was detected. Lines are terminated by LF, CRLF, or a lone CR, each counting as a single line break; Column counts Unicode characters, not bytes.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

type TimeSpecial

type TimeSpecial int

TimeSpecial identifies one of the two sentinel Time Stamp values, #TPAST and #TFUTURE, that do not carry a concrete time.

const (
	// TimeNone marks a TimeStamp that carries a concrete Time value.
	TimeNone TimeSpecial = iota
	// TimePast is the CommuniGate Pro "remote past" sentinel, #TPAST.
	TimePast
	// TimeFuture is the CommuniGate Pro "remote future" sentinel, #TFUTURE.
	TimeFuture
)

type TimeStamp

type TimeStamp struct {
	// Time holds the represented instant, always in UTC. It is the
	// zero time.Time when Special is not TimeNone.
	Time time.Time
	// Special identifies a sentinel value; TimeNone for a concrete Time.
	Special TimeSpecial
}

TimeStamp is a CommuniGate Pro Time Stamp object: a GMT time value, or one of the two sentinel values representing the remote past or the remote future.

A well-formed TimeStamp - the only kind Validate accepts and Marshal encodes - is either a sentinel (Special is TimePast or TimeFuture, Time is the zero time.Time), or a concrete time with whole-second precision and a year within the normative grammar's 1970-2038 range. Construct values with NewTimeStamp, TimeStampPast and TimeStampFuture rather than with struct literals: the zero TimeStamp is not a valid value.

The textual format itself gives no special meaning to boundary dates, so the strict parse modes read the start of the Unix epoch (#T01-01-1970_00:00:00) as an ordinary time value. The server does not: it counts seconds from that instant and reserves the count zero for the remote past, so ServerCompatible mode reads that timestamp, and every earlier one, as TimeStampPast.

func NewTimeStamp

func NewTimeStamp(t time.Time) TimeStamp

NewTimeStamp returns a TimeStamp for the given instant, converted to UTC (CommuniGate Pro Time Stamps always represent GMT time) and truncated to whole seconds, since the textual format cannot represent sub-second precision.

func TimeStampFuture

func TimeStampFuture() TimeStamp

TimeStampFuture returns the "remote future" sentinel Time Stamp (#TFUTURE).

func TimeStampPast

func TimeStampPast() TimeStamp

TimeStampPast returns the "remote past" sentinel Time Stamp (#TPAST).

func (TimeStamp) String

func (t TimeStamp) String() string

String returns the CommuniGate Pro textual representation of t, e.g. "#T22-10-2009_15:24:45" or "#TPAST".

type Value

type Value interface {
	// contains filtered or unexported methods
}

Value is implemented by every CommuniGate Pro data object recognized by this package: Null, String, DataBlock, Number, TimeStamp, IPAddress, Array, Dictionary, and XML.

The interface is sealed - only types defined in this package implement it - so a type switch over a Value covering those nine kinds is exhaustive.

func Parse

func Parse(data []byte) (Value, error)

Parse parses a single CommuniGate Pro data object from data and returns its Value representation. Parse fails if data contains anything other than optional surrounding whitespace/comments plus exactly one object. It is equivalent to ParseWith with the zero Options: StrictMultiLine mode and DefaultMaxDepth.

Example
package main

import (
	"fmt"

	cgpdata "github.com/gmyzovsky/go-cgp-data"
)

func main() {
	v, err := cgpdata.ParseString(`{Name="Alice"; Age=#30; Tags=(admin, ops);}`)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	dict := v.(cgpdata.Dictionary)
	name, _ := dict.Get("Name")
	fmt.Println(name)

}
Output:
Alice

func ParseString

func ParseString(s string) (Value, error)

ParseString is a convenience wrapper around Parse for callers that already have the input as a string.

func ParseWith added in v0.2.0

func ParseWith(data []byte, opts Options) (Value, error)

ParseWith is like Parse but parses according to opts.

type XML

type XML string

XML is an embedded XML document. This package does not parse or interpret its structure: XML holds the exact source text of the document verbatim, as produced by Parse or supplied by the caller for Marshal.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL