SYML (Simple YAML-like Markup Language) is a simple markup language with similar structure to YAML, but without all the gewgaws and folderol.
Here's a simple SYML document:
>>> document = """
foo:
- bar
- baz
- blah
boo
baloon
booleans:
- True
- False
- true
- false
- TRUE
- FALSE
"""And the resulting data structure::
>>> import syml
>>> syml.loads(document)
{'foo': ['bar', 'baz', 'blah\nboo\nbaloon'],
'booleans': ['True', 'False', 'true', 'false', 'TRUE', 'FALSE']}All leaf values in SYML are just plain ol' strings. No ints, floats, bools, or nasty remote code execution bugs!
SYML looks like YAML, but several YAML features are deliberately absent. Skimming a YAML file into SYML? Watch for these, in order:
- Comments only at column 0. A line that starts with
#or//, with no indentation, is a comment, anywhere in the file. An indented# noteis text: as the first line of a block it makes that block one string, with no error, and after a block's first entry it raises, with a hint. A trailing comment is text too (port: 80 # defaultkeeps# default), and after a key it takes in the block under it (server: # prodmakes the block under it part ofserver's string). - No block-scalar indicators.
|and>are literal characters, not YAML's literal/folded block markers; indent the lines under the key instead. - No document markers.
---and...are text, not YAML's document separators (a document starting with one is a single string; mid-file, a raise there gets a hint too). - No quoting.
"x"is the three-character string"x", quotation marks included. - Bare scalars are plain strings.
null,true,123,~,[a, b], and{a: 1}, written inline, are all just their literal text. - Keys match
[a-z][a-z0-9_-]*.Name:,firstName:, andURL:are text, not keys: a block of them (env:\n HOME: /h) is one text value, and so is a whole file whose first line is one (Name: app\nport: 80is a string), and so is a list item whose first key is one (- containerPort: 80\n protocol: TCPis one string). A bad key after a good one raises with a hint. - A key or list marker needs a space after it.
port:8080(no space after the colon) is not a key at all — it's text, so it silently makes the whole document, or the whole block it's in, one string. Same for-bin a list. When it raises instead (because a sibling with a proper space already opened the block), the error now includes a hint. A URL value (http://example.com) is exempt: the hint never fires on ascheme://value. - key:sets a sibling column.- server:\n host: xis two siblings ([{'server': '', 'host': 'x'}]);- server:\n host: xnests ([{'server': {'host': 'x'}}]). Use a space after-: a tab there counts as one column too, but is easy to misalign visually.- A blank line inside a value is a paragraph break.
k:\n a\n\n bis{'k': 'a\n\nb'}, not{'k': 'a\nb'}. - Trailing whitespace on a value is kept, and an over-indented line
joins the value above it.
host: db1keeps the trailing spaces ('db1 ');port: 5432too. An indented line deeper than an open value's baseline is that value's text, not a new sibling — YAML-style sub-bullets fall into this:- Budget review\n - Q3 numbersis['Budget review\n- Q3 numbers'], one item, not two.
syml.parse returns a tree whose leaves carry Source objects instead of
plain str (see syml.basetypes.Source). Three
facts distinguish it from str:
Sourceis not astr. It compares equal to astrwith the same text and hashes the same way, so it works as a dict key or in a set alongside plain strings, butisinstance(source, str)isFalse.- An empty
Sourceis truthy.bool(Source.from_text(''))isTrueeven thoughbool('')isFalse, andSourcehas no__len__, solen()raisesTypeErrorrather than returning0. - A multi-line
Source.textis the dedented value. It is exactly the stringas_data()would return for the same node — indentation past the baseline preserved, indentation up to the baseline stripped — not the raw source slice.
syml.dumps and syml.dump serialize plain str/list/dict data back to
SYML text, such that loads(dumps(x)) == x:
>>> import syml
>>> syml.dumps({'foo': ['bar', 'baz']})
"foo:\n - bar\n - baz\n">>> with open('out.syml', 'w', encoding='utf-8') as f:
... syml.dump({'foo': ['bar', 'baz']}, f)SYML has no quoted strings: every value is the literal text on the page, so
a ' or " is ordinary content and "hello" round-trips with its quotation
marks. A string containing a newline is written in block form (the key or
- on its own line, each line of the value indented beneath it), and any
root scalar is written in block form too, keeping its own leading spaces
literally (dumps(' hello') is ' hello\n'); a blank
line inside the value is written back as an empty line (a paragraph break),
not dropped. The output format is otherwise an implementation choice, not
part of the SYML conformance requirements: two-space indentation, exactly
one trailing newline, and keys written in insertion order. dumps accepts
plain str/list/dict data, and also accepts syml.basetypes.Source
wherever a str key or scalar is expected (it is written by its text).
It raises TypeError for anything else (including a mapping key that is
neither a str nor a Source), and UnrepresentableValueError for a
value with no SYML encoding: a control character other than LF/TAB
anywhere; a first line that would lex as structure (at a list position, at
a mapping position for a multi-line value, or at the root regardless of
length), plus any later line whose leading - marker chain holds more
than 32 markers; a value's first line, inline or block, beginning with a
space (mapping or list position only — a root scalar's leading spaces are
literal content, see above); any line whose leading whitespace contains a
tab; a non-empty, whitespace-only line; a multi-line value whose first or
last line is empty; a mapping key that does not match [a-z][a-z0-9_-]*;
an empty list or empty mapping at any depth; and a root scalar with any
line beginning with # or // (see the specification's §11.2.1–§11.2.3).
dump writes through the file object's own codec and does not check it, so
always open the handle with encoding='utf-8' — SYML documents are UTF-8.
Upgrading from 0.6.2? See CHANGELOG.md for every user-visible change.