#simple #serde #language

simple_config

A config language for humans that is not self-describing

38 breaking releases

0.83.0 Oct 20, 2021
0.79.0 Oct 10, 2021
0.45.0 Jul 7, 2021

#115 in Parser implementations

Download history 40/week @ 2021-07-04 3/week @ 2021-07-11 25/week @ 2021-07-18 29/week @ 2021-07-25 82/week @ 2021-08-01 27/week @ 2021-08-08 66/week @ 2021-08-15 109/week @ 2021-08-22 75/week @ 2021-08-29 41/week @ 2021-09-05 25/week @ 2021-09-12 57/week @ 2021-09-19 3/week @ 2021-09-26 52/week @ 2021-10-03 97/week @ 2021-10-10 42/week @ 2021-10-17

221 downloads per month
Used in watchlog

Apache-2.0 and LGPL-2.0-or-later

57KB
1.5K SLoC

Simple Config

A config language for humans that is not self-describing.

Simple Config isn't specific to Rust or serde but the only known implementation uses both.

Why Use Simple Config?

Simple config is based on the concept that you don't need to be able to form the schema from the config file. This is already common but in a more adhoc form, for example you may use a JSON string to hold a date such as "1990-02-14". Simple Config takes advantage of this to provide richer types and less escaping. For example a multi-line string requires no additional markup, the schema says it is a string so it won't try to parse it as a dict. Similarly you won't have type errors because false or no got parsed as a bool, and you don't have to worry about quoting strings that start with numbers.

Why Not Use Simple Config?

  • Can not be parsed without the schema. (Note: Unknown items can be skipped, but it can't be semantically parsed.)
  • No support for code reuse. Simple Config is best for relatively simple config files where reuse is not required.
  • No current support for serialization yet.

Example

This is a small example that shows both the schema (as Rust code) and the config (as a string literal). For a full explanation see below.

#[derive(Debug,PartialEq,serde_derive::Deserialize)]
enum Country {
	CA,
	US,
	NO,
}

#[derive(Debug,PartialEq,serde_derive::Deserialize)]
#[serde(rename_all="kebab-case")]
enum Contact {
	Email(String),
	Matrix(String),
	Phone{
		number: u64,
		#[serde(default)]
		extension: Option<u64>,
	},
}

#[derive(Debug,PartialEq,serde_derive::Deserialize)]
#[serde(rename_all="kebab-case")]
struct Contestant {
	contact: Vec<Contact>,
	country: Country,

	#[serde(with = "humantime_serde")]
	personal_best: std::time::Duration,

	#[serde(with = "humantime_serde")]
	last_seen: std::time::SystemTime,
}

let t: std::collections::BTreeMap<String, Contestant> = simple_config::from_bytes("

kevincox:
	contact:
		:
			email: kevincox@kevincox.ca

		:
			phone:
				number: 18005551234
	country: CA
	personal-best: 1h 13min
	last-seen: 2021-06-08 00:00:00Z
sarah:
	contact:
		:
			matrix: @me:matrix.example
	country: NO
	personal-best: 73min
	last-seen: 2019-06-30 07:24:30Z

").unwrap();

assert_eq!(t, std::array::IntoIter::new([
	("kevincox".to_owned(), Contestant {
		contact: vec![
			Contact::Email("kevincox@kevincox.ca".into()),
			Contact::Phone{number: 18005551234, extension: None},
		],
		country: Country::CA,
		personal_best: std::time::Duration::from_secs(1*3600 + 13*60),
		last_seen: std::time::UNIX_EPOCH + std::time::Duration::from_secs(1623110400),
	}),
	("sarah".to_owned(), Contestant {
		contact: vec![
			Contact::Matrix("@me:matrix.example".into()),
		],
		country: Country::NO,
		personal_best: std::time::Duration::from_secs(73*60),
		last_seen: std::time::UNIX_EPOCH + std::time::Duration::from_secs(1561879470),
	}),
]).collect());

For more examples see the examples directory. Note that some of the examples in this directory re-implementations of existing data structures and may contain hacks to work well with the previous config language, leading to suboptimal presentation in Simple Config.

Reference

Dictionaries

key: value

Dictionaries are a mapping from a key to a value. The key is always a single-line value that doesn't contain a colon followed by a colon, however the exact parsing is dependent on the type.

# The type of the key depends on the schema, but these examples can be parsed as the specified type.
true: boolean key
123: integer key
hello: string key

Values are arbitrary types. There are two ways to specify a value. How these values are parsed depends on the type.

key: inline value
key:
	multi
	line
	value

For full reference on parsing see the docs for the type of the value.

Nesting

Dictionaries can be nested, when nested the value must be specified as a mutli-line value.

vehicle:
	type: Car
	engine:
		capacity_cc: 200
		mass_g: 60k
	wheels:
		diameter_m: 550m

Lists

Lists contain multiple values. Depending on the schema the order may or may not be important.

strings:
	string one
	string two
	string three
numbers:
	1
	1k
	1M

By default list items are a single line long. To put a multi-line item in a list you must use the : character to open a new level of indent. Single and multi-line items can be mixed. Note that Dictionaries must be multi-line.

strings:
	single line string
	:
		multi
		line
		string
	another single line string
dicts:
	:
		species: Cat
		cuteness: 0.99
	:
		species: Dog
		cuteness: 0.8

Strings

Strings can be single-line or multi-line. Currently no escaping is allowed or necessary, strings end at a newline or comment (#) whichever comes first. However single-line strings starting with a double quote " are reserved for future escaping capabilities. Mutli-line strings have the leading indentation stripped. Note that there are no special characters in a multi-line string, including comments. If you want to put a comment in a multi-line string you will need to use the destination comment format.

inline: a string # This comment is not part of the string.
out-of-line:
	a string
multi-line: # This comment is not part of the string.
	this
		is
	a
		string
no-comments:
	# This comment is a part of the string.

Warning: There is currently no way to specify a string where the first line is indented.

# WARNING: This example is invalid.
doesnt-work:
		indented first line
	non-indented line

Numbers

Numbers can be specified as you would expect. Additionally many suffixes are supported for scaling the numbers. It is an error to have an integer that is out-of-range for the associated type. Floating point values will be the nearest representable value.

simple-int: 123
simple-float: 3.14

base2: 0b01001011
base8: 0o664
base16: 0xCC

# Currently arbitrary exponents are only supported for base10.
exponents:
	6.022e1023 # 6.022 ⨉ 10^1023
	2.5e-11 # 2.5 ⨉ 10^-11

si-suffixes:
	1E
	1P
	1T
	1G
	1M
	1k # K is also accepted for consistency.
	1m
	1u
	
	1n
	1p
	1f
	1a

iec-sufixes:
	1Ei
	1Pi
	1Ti
	1Gi
	1Mi
	1ki # Ki is also accepted for consistency.
	1mi
	1ui
	1µi
	1ni
	1pi
	1fi
	1ai

Booleans

Booleans are specified as true or false.

Bytes

Bytes are currently unsupported. https://gitlab.com/kevincox/simple-config-rs/-/issues/1

Optionals

An optional value can be specified as an empty value value.

Note: It is currently impossible to specify an empty string for Option<String>, it is always assumed to mean None.

maybe_none:
maybe_some: 17

Comments

Comments start with a # and extend to the end of the line. Comments can be placed in most places. However note that comments can't be placed in multi-line strings (they are considered part of the string).

# comment
a-number: # comment
	5
another-number: 5 # comment

a-string: # comment
	hi
multi-line-string: # comment
	hi # NOT COMMENT
	# NOT COMMENT
# comment

list: # comment
	1 # comment
	# comment
	2

# comment

Dependencies

~1–1.7MB
~36K SLoC

oa