dyml

package module
v0.0.0-...-aedddf3 Latest Latest
Warning

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

Go to latest
Published: Apr 28, 2022 License: Apache-2.0 Imports: 7 Imported by: 0

README

++++
<div align="center">
  <img width="128" height="128" src="assets/icon.png">
  <h1>DYML</h1>
</div>
++++

The Double Yielding Markup Language (DYML) aims to be a lightweight alternative to JSON/YAML while being more friendly to write text in, like AsciiDoc or Markdown, but without the weirdness and parsing complexity.

== Quick Overview

This section should give you enough information to get started writing DYML.
Check out link:docs/markup.adoc[] for a complete description of the grammar, some motivation for this project and some more examples.

DYML is used to describe a tree structure with nodes and attributes.
Nodes can be nodes with a name that can contain more nodes and can have attributes, or they are just text.

[source,dyml]
----
#item
#greeting hello world
#house @color{green} {
    This is my house.
    @@color{blue} #door
}
----

This could be translated to XML like so:

[source,xml]
----
<root>
    <item></item>
    <greeting>hello world</greeting>
    <house color="green">
        This is my house.
        <door color="blue"></door>
    </house>
</root>
----

As you can see, nodes are declared like `+#name+` and will contain all following text, until the next node is declared.
All nodes are siblings of each other, unless you declare a block with `+{}+` that can contain child nodes.
In constrast to XML there is also no explicit root node.
Attributes for nodes are set with `+@key{value}+` where the value can be any text.
Attributes must follow the node definition directly, but can also be written as forwarded attributes in front of the node with `+@@key{value}+`.

DYML written in this way is _text first_, as anything that is not an element definition or attribute will be interpreted as text.
You can also create _node first_ elements, which have some interesting properties we will explore in an example:

[source,dyml]
----
#! greeting "hello world"
#! some nested elements;
#! house @color="green" {
    @@color="blue"
    door,
    garage,
}
----

Which would look like this in XML:

[source,xml]
----
<root>
    <greeting>hello world</greeting>
    <some>
        <nested>
            <elements></elements>
        </nested>
    </some>
    <house color="green">
        <door color="blue"></door>
        <garage></garage>
    </house>
</root>
----

Wherever an element can be started in text mode you can start an element in node mode by starting it with `+#!+`.
In node mode all text is interpreted as the names of nodes by default and text has to be enclosed in double quotes.

Nodes are also nested into one another as you can see with `+#! some nested elements;+` where each node is a child of the previous one.
Attributes look slightly differently (`+@key="value"+`) but work like attributes in text mode and can be forwarded too.

Once a node in node mode is completed, nodes in text mode will follow.
There are different ways for a node to be completed, all of which can be seen in the example above.
The first way is to introduce text to stop nesting and therefore end the node.
The second way is to end the definition with a comma or semicolon, they can be used interchangeably, but you might prefer one over the other depending on the context.
The third way is to start a block with children, the node is closed at the same point the block is closed.

You can be creative with the brackets you use for blocks in node mode, all the items in the following example are equivalent except for their block types.

[source,dyml]
----
#! item {child}
#! item <child>
#! item [child]
----

There is one additional thing that can be useful for expressing some concepts: The return arrow `+->+`.
Inspired by some programming languages that use an arrow to denote a function's return parameters, you can do something similar:

[source,dyml]
----
#! x(a, b) -> (c, d)
#! x -> option<int>
----

Which would correspond to the following XML:

[source,xml]
----
<root>
    <x>
        <a></a>
        <b></b>
        <ret>
            <c></c>
            <d></d>
        </ret>
    </x>
    <x>
        <option>
            <int></int>
        </option>
    </x>
</root>
----

As you can see, the return arrow must follow a node definition that can have a block.
Following the arrow there must be a block that can have a name set to rename the default _ret_.

NOTE: Text mode is also referred to as G1 and node mode as G2.

== Packages

* link:token[] contains the lexer that can convert an input stream into tokens.
* link:parser[] contains logic to turn an input stream into a tree representation.
You will also find the types `+Visitor+` and `+Visitable+` here, which you must use if you want to create your own parser.
* link:encoder[] contains an XMLEncoder that can directly convert an input stream into an XML representation.
It serves as an example as to how implement your own parser.
In most cases you do not want to create your own parser, but instead use the `+Unmarshal+` method (defined in link:marshal.go[]) which can parse an input stream into a struct.

== Testing

Run `make test` to run all available tests.
Run `make lint` to check the code against a list of lints with https://golangci-lint.run[golangci-lint].

Documentation

Overview

Package dyml contains the Double Yielding Markup Language.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Unmarshal

func Unmarshal(r io.Reader, into interface{}, strict bool) error

Unmarshal takes dyml input and parses it into the given struct. If "into" is not a struct or a pointer to a struct, this method will panic. As this uses go's reflect package, only exported names can be unmarshalled. Strict mode requires that all fields of the struct are set and defined exactly once. You can set struct tags to influence the unmarshalling process. All tags must have the form `dyml:"..."` and are a list of comma separated identifiers.

The first identifier can be used to rename the field, so that an element with the renamed name is parsed, and not the name of the struct field.

// This dyml snippet...
#!{item{...}}
// could be unmarshalled into this go struct.
type Example struct {
    SomeName Content `dyml:"item"`
}

The second identifier is used to specify what kind of thing is being parsed. This can be used to parse attributes (attr) or text (text).

Attributes can be parsed into primitive types: string, bool and the integer (signed & unsigned) and float types. Should the value not be valid for the target type, e.g. an integer that is too large or a negative value for an uint, an error is returned describing the issue.

// This dyml snippet...
#item @key{value} @X{123}
// could be unmarshalled into this go struct.
type Example struct {
    SomeName string `dyml:"key,attr"` // Notice how you can rename an attribute
    X        int    `dyml:",attr"` // You can choose to not rename it, by omitting the rename parameter.
}

'inner' can be used to parse elements that are the contents of the surrounding element. Consider this example to parse plain text without surrounding elements:

// This dyml snippet...
#! {
    "hello"
    "more text"
}
// could be unmarshalled into this go struct.
type Example struct {
    Something string `dyml:",inner"`
}

When collecting text this way all text inside the node will be concatenated in non-strict mode ("hellomore text" in the above example). In strict mode exactly one text child is required. In the following example inner is used to parse a map-like Dyml definition into a map without a supporting element.

// This dyml snippet...
#! {
    A "B"
    C "D"
}
// could be unmarshalled into this go struct.
type Example struct {
    Something map[string]string `dyml:",inner"`
}

dyml can unmarshal into maps. The map key must be a primitive type. The map value must be a primitive type, parser.TreeNode or *parser.TreeNode. Parsing maps will read first level elements as map keys and the first child of each as the map value. In strict mode the map key is required to have exactly one child. By specifying parser.TreeNode (or a pointer to it) as the value type you can access the raw tree that would be parsed as a value. This is useful if you want to have more control over the value for doing more complex manipulations than just parsing a primitive.

// This dyml snippet...
#! {
    SomeMap {
        a 123,  // Numbers are valid identifiers, so this works
        b "1.5" // but all other values should be enclosed in quotes.
    }
}
// could be unmarshalled into this go struct.
type Example struct {
    SomeMap map[string]float64
}

dyml also supports unmarshalling slices. When no tag is specified in the struct, elements in dyml are unmarshalled into the slice directly. Should you specify a tag on the field in your struct, then only elements with that tag will be parsed. See the examples for more details.

Example
type Animal struct {
	Name string `dyml:"name"`
	Age  uint   `dyml:"age"`
}

input := strings.NewReader("#name Gopher #age 3")

var animal Animal

Unmarshal(input, &animal, false)

fmt.Printf("Hello %d year old %s!", animal.Age, animal.Name)
Output:

Hello 3 year old Gopher !

func UnmarshalTree

func UnmarshalTree(tree *parser.TreeNode, into interface{}, strict bool) error

UnmarshalTree works like Unmarshal, but processes an already parsed tree.

Types

type UnmarshalError

type UnmarshalError struct {
	Node   *parser.TreeNode
	Detail string
	// contains filtered or unexported fields
}

UnmarshalError is an error that occurred during unmarshalling. It contains the offending node, a string with details and an underlying error (if any).

func NewUnmarshalError

func NewUnmarshalError(node *parser.TreeNode, detail string, wrapping error) UnmarshalError

func (UnmarshalError) Error

func (u UnmarshalError) Error() string

func (*UnmarshalError) Unwrap

func (u *UnmarshalError) Unwrap() error

type Unmarshaler

type Unmarshaler interface {
	UnmarshalDyml(node *parser.TreeNode) error
}

Unmarshaler can be implemented on a struct to define custom unmarshalling behavior.

Directories

Path Synopsis
Package parser contains the parser that transforms tokens generated by the lexer in package token to a tree representation.
Package parser contains the parser that transforms tokens generated by the lexer in package token to a tree representation.
Package token contains the token and lexer logic.
Package token contains the token and lexer logic.
gen
Generate token identifiers for all structs in parser2/token.go and place them in parser2/token.gen.go.
Generate token identifiers for all structs in parser2/token.go and place them in parser2/token.gen.go.
Package util contains utility functions that may be useful for different parsers and encoders.
Package util contains utility functions that may be useful for different parsers and encoders.

Jump to

Keyboard shortcuts

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