~poldi1405/lyml

98e7ca0a — Moritz Poldrack 1 year, 4 months ago
add ability to set negative numbers
b8ff5c26 — Moritz Poldrack 1 year, 4 months ago
initial commit

refs

master
browse  log 

clone

read-only
https://git.sr.ht/~poldi1405/lyml
read/write
git@git.sr.ht:~poldi1405/lyml

You can also use your local clone with git send-email.

#LYrical Markup Language

Lyrical adjective – having a pleasantly flowing quality suggestive of music

#Why yet another config format?

Just because. And maybe someone actually finds it useful.

#Core Ideas

LYML combines some ideas from various sources:

#Example

address "ta.rba.sh"

listen-for [
	protocol "http" on-port 80 with-tls disabled
	protocol "http" on-port 443 with-tls enabled and-advanced-options {
		cert-domains "ta.rba.sh" "rba.sh"
		from-ca "ZeroSSL"
	}
	protocol "plain" on-port 5
]

limit {
	filesize "256M"
	per-ip {
		upload "512M"
		download "5G"
	}
	tarball-files-to 10
	inactive-time-to "7d"
}

matches this JSON

{
	"address":               "ta.rba.sh",
	"listen-for.0.protocol": "http",
	"listen-for.0.on-port":   80,
	"listen-for.0.with-tls":  false,
	"listen-for.1.protocol":  "https",
	"listen-for.1.on-port":   443,
	"listen-for.1.with-tls":  true,
	"listen-for.1.and-advanced-options.cert-domains": [
		"ta.rba.sh",
		"rba.sh",
	],
	"listen-for.1.and-advanced-options.from-ca": "ZeroSSL",
	"listen-for.2.protocol":                     "plain",
	"listen-for.2.on-port":                      5,
	"listen-for.2.with-tls":                     false,
	"limit.filesize":                            "256M",
	"limit.per-ip.upload":                       "512M",
	"limit.per-ip.download":                     "5G",
	"limit.tarball-files-to":                    10,
	"limit.inactive-time-to":                    "7d",
}

or this Go-struct:

type Config struct {
	Address     string
	MaxFilesize string
	Listener    []struct{
		Protocol string         `lyml:"protocol"`
		Port     int            `lyml:"on-port"`
		TLS      bool           `lyml:"with-tls"`
		Advanced map[string]any `lyml:"and-advanced-options"`
	} `lyml:"listen-for"`
	Limit struct{
		Filesize       string
		PerIP          map[string]
		TarballFiles   int    `lyml:"tarball-files-to"`
		InactivePeriod string `lyml:"inactive-time-to"`
	}
}

#Spec

LYML aims to be readable "like a text". For that purpose, special characters have been reduced as much as possible.

Important:

  • LYML paths are not case sensitive
  • LYML documents must be encoded using UTF-8
  • Whitespace means tab (U+0009) or space (U+0020)
  • Newline means linefeed (U+000A)
    • Parsers may ignore leading carriage returns (U+000D)

#Keys

Keys are case-insensitive!

Keys must be made from at least one ASCII letter and can be followed by an arbitrary number of ASCII letters, numbers, dashes, or underscores. Dashes and underscores must not follow each other or stand at the end of a key. Keys should be conducive to a readable configuration.

Regular Expression: [a-z]([a-z0-9]+|_[a-z0-9]+|-[a-z0-9]+)*

key "value"
key-name "value"
key_name "value"
very-long_key-name_with-mixed_underscores-and_dashes "value"

#Paths

Keys can be combined to paths using a period . to separate elements.

map { key "value" }

map2.key "another value"
{
	"map.key":  "value",
	"map2.key": "another value",
}

#Values

Values can be composed of types string, float, integer, boolean, arrays, and maps. Their type is determined based on their structure or content. Types in arrays must not be mixed or booleans.

  • Strings
    • enclosed in quotation marks "
    • contained quotation marks are escaped \"
    • contained linebreaks are escaped \n
    • supports \x__ for ASCII and \u____ for Unicode characters
  • Raw Strings
    • special case of strings
    • enclosed in triple single-quotes '''
  • Floats
    • contains a period . to separate integer and fraction
  • Integer
    • consist of nothing but numbers
  • Booleans
    • true, on, enable, enabled
    • false, off, disable, disabled
  • Arrays
    • can consist of strings, floats, integers, and maps

Regular Expression: ("(\"|[^"\t\n])+"([ \t]+"[^"\t\n]+")*|\d+\.\d+([ \t]+\d+\.\d+)*|\d+([ \t]+\d+)*|(true|yes|enabled?|false|no|disabled?))

#Maps

Maps are created by enclosing the structures in curly braces {…}.

key1 {
	key "value" another-key 42
}

key2 {
	key "something"
	another-key 42
}
{
	"key1.key":         "value",
	"key1.another-key": 42,
	"key2.key":         "something",
	"key2.another-key": 42,
}
#Arrays

Depending on the parser, array keys may be encoded using path-elements that do not conform to the key-format requirements. Addressing these fields afterwards, is not permitted in that case.

Arrays are created by chaining values or enclosing structure in […] and using line-wise notation.

array "value1" "value2"

map-array [
	map-key1 42 map-key2 "this is the second key"
	map-key1 69 map-key2 "this is the second array of the array"
]
{
	"array":                ["value1", "value2"],
	"map-array.0.map-key1": 42,
	"map-array.0.map-key2": "this is the second key",
	"map-array.1.map-key1": 69,
	"map-array.1.map-key1": "this is the second array of the array",
}

#Why no…

#…boolean arrays?

Because they don't exactly lend themselves to "readable" configurations:

tls enabled on no disable yes
#…time type?

Times are either stored as a string or an integer. Parsing of data is left to libraries.

#…numeric keys?

Numeric keys are not readable.