cgpdata

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 11 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, which behaves like encoding/json.Decoder:

dec := cgpdata.NewDecoder(r)
for dec.More() {
    v, err := dec.Decode()
    if err != nil {
        log.Fatal(err)
    }
    handle(v)
}

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.

Parse errors are returned as *cgpdata.SyntaxError, which carries the byte offset and 1-based line/column of the problem:

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

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.

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. Parse reports an error if a key repeats, since the specification requires dictionary keys to be unique.

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)

Design notes

  • IP addresses are backed by net/netip.Addr, and delegate parsing/ validation entirely to the standard library rather than reimplementing the specification's simplified IPv6 grammar.
  • Time Stamps always store the represented instant in UTC 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 Parse with a *SyntaxError.

Testing

go test ./...

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 before decoding the first object, since the format allows embedded XML documents whose extent can only be determined by tokenizing them.

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.

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

This section is empty.

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.

func Marshal

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

Marshal returns the compact, single-line CommuniGate Pro textual representation of 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.

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 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.

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.

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.

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.

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.

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.

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 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.

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.

func NewTimeStamp

func NewTimeStamp(t time.Time) TimeStamp

NewTimeStamp returns a TimeStamp for the given instant, converted to UTC since CommuniGate Pro Time Stamps always represent GMT time.

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.

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.

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