Preserves Path
The preserves.path module implements Preserves Path.
Preserves Path is roughly analogous to
XPath, but for Preserves values: just as
XPath selects portions of an XML document, a Preserves Path uses path expressions to select
portions of a Value
.
Use parse to compile a path expression, and then use the exec method on the result to apply it to a given input:
parse(PATH_EXPRESSION_STRING).exec(PRESERVES_VALUE)
-> SEQUENCE_OF_PRESERVES_VALUES
Command-line usage
When preserves.path is run as a __main__
module, sys.argv[1]
is
parsed, interpreted as a path expression, and
run against human-readable values read from standard
input. Each matching result is passed to stringify and printed to
standard output.
Examples
Setup: Loading test data
The following examples use testdata
:
>>> with open('tests/samples.bin', 'rb') as f:
... testdata = decode_with_annotations(f.read())
Recall that samples.bin
contains a binary-syntax form of the human-readable
[samples.pr](https://preserves.dev/tests/samples.pr) test data file, intended to exercise most
of the features of Preserves. In particular, the root
Value` in the file has a number of
annotations (for documentation and other purposes).
Example 1: Selecting string-valued documentation annotations
The path expression .annotations ^ Documentation . 0 / string
proceeds in five steps:
.annotations
selects each annotation on the root document^ Documentation
retains only those values (each an annotation of the root) that areRecord
s with label equal to the symbolDocumentation
. 0
moves into the first child (the first field) of each suchRecord
, which in our case is a list of otherValue
s/
selects all immediate children of these listsstring
retains only those values that are strings
The result of evaluating it on testdata
is as follows:
>>> selector = parse('.annotations ^ Documentation . 0 / string')
>>> for result in selector.exec(testdata):
... print(stringify(result))
"Individual test cases may be any of the following record types:"
"In each test, let stripped = strip(annotatedValue),"
" encodeBinary(·) produce canonical ordering and no annotations,"
" looseEncodeBinary(·) produce any ordering, but with annotations,"
" annotatedBinary(·) produce “canonical ordering”, but with annotations,"
" decodeBinary(·) include annotations,"
" encodeText(·) include annotations,"
" decodeText(·) include annotations,"
"and check the following numbered expectations according to the table above:"
"Implementations may vary in their treatment of the difference between expectations"
"21/22 and 31/32, depending on how they wish to treat end-of-stream conditions."
"The idea of canonical-ordering-with-annotations is to encode, say, sets with their elements"
"in sorted order of their canonical annotationless binary encoding, but then actually"
"*serialized* with the annotations present."
Example 2: Selecting tests with Records as their annotatedValues
The path expression // [.^ [= Test + = NondeterministicTest]] [. 1 rec]
proceeds in three steps:
-
//
recursively decomposes the input, yielding all direct and indirect descendants of each input value -
[.^ [= Test + = NondeterministicTest]]
retains only those inputs (each a descendant of the root) that yield more than zero results when executed against the expression within the brackets:.^
selects only labels of values that areRecords
, filtering by type and transforming in a single step[= Test + = NondeterministicTest]
again filters by a path expression:- the infix
+
operator takes the union of matches of its arguments - the left-hand argument,
= Test
selects values (remember, record labels) equal to the symbolTest
- the right-hand argument
= NondeterministicTest
selects values equal toNondeterministicTest
- the infix
The result is thus all
Record
s anywhere insidetestdata
that have eitherTest
orNondeterministicTest
as their labels. -
[. 1 rec]
filters theseRecord
s by another path expression:. 1
selects their second field (fields are numbered from 0)rec
retains only values that areRecord
s
Evaluating the expression against testdata
yields the following:
>>> selector = parse('// [.^ [= Test + = NondeterministicTest]] [. 1 rec]')
>>> for result in selector.exec(testdata):
... print(stringify(result))
<Test #[tLMHY2FwdHVyZbSzB2Rpc2NhcmSEhA==] <capture <discard>>>
<Test #[tLMHb2JzZXJ2ZbSzBXNwZWFrtLMHZGlzY2FyZIS0swdjYXB0dXJltLMHZGlzY2FyZISEhIQ=] <observe <speak <discard> <capture <discard>>>>>
<Test #[tLWzBnRpdGxlZLMGcGVyc29usAECswV0aGluZ7ABAYSwAWWxCUJsYWNrd2VsbLSzBGRhdGWwAgcdsAECsAEDhLECRHKE] <[titled person 2 thing 1] 101 "Blackwell" <date 1821 2 3> "Dr">>
<Test #[tLMHZGlzY2FyZIQ=] <discard>>
<Test #[tLABB7WEhA==] <7 []>>
<Test #[tLMHZGlzY2FyZLMIc3VycHJpc2WE] <discard surprise>>
<Test #[tLEHYVN0cmluZ7ABA7ABBIQ=] <"aString" 3 4>>
<Test #[tLSzB2Rpc2NhcmSEsAEDsAEEhA==] <<discard> 3 4>>
<Test #[hbMCYXK0swFShbMCYWazAWaE] @ar <R @af f>>
<Test #[tIWzAmFyswFShbMCYWazAWaE] <@ar R @af f>>
Predicate = syntax.Predicate
module-attribute
Schema definition for representing a Preserves Path Predicate
.
Selector = syntax.Selector
module-attribute
Schema definition for representing a sequence of Preserves Path Step
s.
dumps = stringify
module-attribute
This alias for stringify
provides a familiar pythonesque name for converting a Preserves Value
to a string.
loads = parse
module-attribute
This alias for parse
provides a familiar pythonesque name for converting a string to a Preserves Value
.
syntax = load_schema_file(pathlib.Path(__file__).parent / 'path.prb').path
module-attribute
This value is a Python representation of a Preserves Schema definition for the Preserves Path expression language. The language is defined in the file path.prs.
Annotated(item)
Bases: object
A Preserves Value
along with a sequence of Value
s annotating it. Compares equal to
the underlying Value
, ignoring the annotations. See the specification document for more
about annotations.
>>> import preserves
>>> a = preserves.parse('''
... # A comment
... [1 2 3]
... ''', include_annotations=True)
>>> a
@'A comment' (1, 2, 3)
>>> a.item
(1, 2, 3)
>>> a.annotations
['A comment']
>>> a == (1, 2, 3)
True
>>> a == preserves.parse('@xyz [1 2 3]', include_annotations=True)
True
>>> a[0]
Traceback (most recent call last):
...
TypeError: 'Annotated' object is not subscriptable
>>> a.item[0]
1
>>> type(a.item[0])
<class 'preserves.values.Annotated'>
>>> a.item[0].annotations
[]
>>> print(preserves.stringify(a))
@"A comment" [1 2 3]
>>> print(preserves.stringify(a, include_annotations=False))
[1 2 3]
Attributes:
Name | Type | Description |
---|---|---|
item |
Value
|
the underlying annotated |
annotations |
list[Value]
|
the annotations attached to |
Source code in preserves/values.py
456 457 458 |
|
peel()
Calls strip_annotations on self
with depth=1
.
Source code in preserves/values.py
479 480 481 |
|
strip(depth=inf)
Calls strip_annotations on self
and depth
.
Source code in preserves/values.py
475 476 477 |
|
DecodeError
Bases: ValueError
Raised whenever preserves.binary.Decoder or preserves.text.Parser detect invalid input.
Decoder(packet=b'', include_annotations=False, decode_embedded=lambda x: x)
Bases: BinaryCodec
Implementation of a decoder for the machine-oriented binary Preserves syntax.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
packet |
bytes
|
initial contents of the input buffer; may subsequently be extended by calling extend. |
b''
|
include_annotations |
bool
|
if |
False
|
decode_embedded |
function accepting a |
lambda x: x
|
Normal usage is to supply a buffer, and keep calling next until a ShortPacket exception is raised:
>>> d = Decoder(b'\xb0\x01{\xb1\x05hello\x85\xb3\x01x\xb5\x84')
>>> d.next()
123
>>> d.next()
'hello'
>>> d.next()
()
>>> d.next()
Traceback (most recent call last):
...
preserves.error.ShortPacket: Short packet
Alternatively, keep calling try_next until it yields
None
, which is not in the domain of Preserves Value
s:
>>> d = Decoder(b'\xb0\x01{\xb1\x05hello\x85\xb3\x01x\xb5\x84')
>>> d.try_next()
123
>>> d.try_next()
'hello'
>>> d.try_next()
()
>>> d.try_next()
For convenience, Decoder implements the iterator interface, backing it with try_next, so you can simply iterate over all complete values in an input:
>>> d = Decoder(b'\xb0\x01{\xb1\x05hello\x85\xb3\x01x\xb5\x84')
>>> list(d)
[123, 'hello', ()]
>>> for v in Decoder(b'\xb0\x01{\xb1\x05hello\x85\xb3\x01x\xb5\x84'):
... print(repr(v))
123
'hello'
()
Supply include_annotations=True
to read annotations alongside the annotated values:
>>> d = Decoder(b'\xb0\x01{\xb1\x05hello\x85\xb3\x01x\xb5\x84', include_annotations=True)
>>> list(d)
[123, 'hello', @#x ()]
If you are incrementally reading from, say, a socket, you can use extend to add new input as if comes available:
>>> d = Decoder(b'\xb0\x01{\xb1\x05he')
>>> d.try_next()
123
>>> d.try_next() # returns None because the input is incomplete
>>> d.extend(b'llo')
>>> d.try_next()
'hello'
>>> d.try_next()
Attributes:
Name | Type | Description |
---|---|---|
packet |
bytes
|
buffered input waiting to be processed |
index |
int
|
read position within |
Source code in preserves/binary.py
127 128 129 130 131 132 |
|
extend(data)
Appends data
to the remaining bytes in self.packet
, trimming already-processed
bytes from the front of self.packet
and resetting self.index
to zero.
Source code in preserves/binary.py
134 135 136 137 138 |
|
next()
Reads the next complete Value
from the internal buffer, raising
ShortPacket if too few bytes are available, or
DecodeError if the input is invalid somehow.
Source code in preserves/binary.py
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
|
try_next()
Like next, but returns None
instead of raising
ShortPacket.
Source code in preserves/binary.py
228 229 230 231 232 233 234 235 236 |
|
Embedded(embeddedValue)
Representation of a Preserves Embedded
value. For more on the meaning and use of
embedded values, see the specification.
>>> import io
>>> e = Embedded(io.StringIO('some text'))
>>> e # doctest: +ELLIPSIS
#:<_io.StringIO object at ...>
>>> e.embeddedValue # doctest: +ELLIPSIS
<_io.StringIO object at ...>
>>> import preserves
>>> print(preserves.stringify(Embedded(None)))
Traceback (most recent call last):
...
TypeError: Cannot preserves-format: None
>>> print(preserves.stringify(Embedded(None), format_embedded=lambda x: 'abcdef'))
#:"abcdef"
Attributes:
Name | Type | Description |
---|---|---|
embeddedValue |
any Python value; could be a platform object, could be a representation of a
Preserves |
Source code in preserves/values.py
601 602 |
|
EncodeError
Bases: ValueError
Raised whenever preserves.binary.Encoder or preserves.text.Formatter are unable to proceed.
Encoder(encode_embedded=lambda x: x, canonicalize=False, include_annotations=None)
Bases: BinaryCodec
Implementation of an encoder for the machine-oriented binary Preserves syntax.
>>> e = Encoder()
>>> e.append(123)
>>> e.append('hello')
>>> e.append(annotate([], Symbol('x')))
>>> e.contents()
b'\xb0\x01{\xb1\x05hello\x85\xb3\x01x\xb5\x84'
Parameters:
Name | Type | Description | Default |
---|---|---|---|
encode_embedded |
function accepting an Embedded.embeddedValue and
returning a |
lambda x: x
|
|
canonicalize |
bool
|
if |
False
|
include_annotations |
bool | None
|
if |
None
|
Attributes:
Name | Type | Description |
---|---|---|
buffer |
bytearray
|
accumulator for the output of the encoder |
Source code in preserves/binary.py
298 299 300 301 302 303 304 305 306 307 308 309 |
|
append(v)
Extend self.buffer
with an encoding of v
.
Source code in preserves/binary.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
|
contents()
Returns a bytes
constructed from the contents of self.buffer
.
Source code in preserves/binary.py
320 321 322 |
|
reset()
Clears self.buffer
to a fresh empty bytearray
.
Source code in preserves/binary.py
311 312 313 |
|
Formatter(format_embedded=lambda x: x, indent=None, with_commas=False, trailing_comma=False, include_annotations=True)
Bases: TextCodec
Printer (and indenting pretty-printer) for producing human-readable syntax from
Preserves Value
s.
>>> f = Formatter()
>>> f.append({'a': 1, 'b': 2})
>>> f.append(Record(Symbol('label'), ['field1', ['field2item1', 'field2item2']]))
>>> print(f.contents())
{"a": 1 "b": 2} <label "field1" ["field2item1" "field2item2"]>
>>> f = Formatter(indent=4)
>>> f.append({'a': 1, 'b': 2})
>>> f.append(Record(Symbol('label'), ['field1', ['field2item1', 'field2item2']]))
>>> print(f.contents())
{
"a": 1
"b": 2
}
<label "field1" [
"field2item1"
"field2item2"
]>
Parameters:
Name | Type | Description | Default |
---|---|---|---|
format_embedded |
function accepting an Embedded.embeddedValue and
returning a |
lambda x: x
|
|
indent |
int | None
|
|
None
|
with_commas |
bool
|
|
False
|
trailing_comma |
bool
|
|
False
|
include_annotations |
bool
|
|
True
|
Attributes:
Name | Type | Description |
---|---|---|
indent_delta |
int
|
indentation per nesting-level |
chunks |
list[str]
|
fragments of output |
Source code in preserves/text.py
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 |
|
append(v)
Extend self.chunks
with at least one chunk, together making up the text
representation of v
.
Source code in preserves/text.py
541 542 543 544 545 546 547 548 549 550 |
|
contents()
Returns a str
constructed from the join of the chunks in self.chunks
.
Source code in preserves/text.py
492 493 494 |
|
is_indenting()
Returns True
iff this Formatter is in pretty-printing
indenting mode.
Source code in preserves/text.py
496 497 498 499 |
|
ImmutableDict(*args, **kwargs)
Bases: dict
A subclass of Python's built-in dict
that overrides methods that could mutate the
dictionary, causing them to raise TypeError('Immutable')
if called.
Implements the __hash__
method, allowing ImmutableDict
instances to be used whereever immutable data are permitted; in particular, as keys in
other dictionaries.
>>> d = ImmutableDict([('a', 1), ('b', 2)])
>>> d
{'a': 1, 'b': 2}
>>> d['c'] = 3
Traceback (most recent call last):
...
TypeError: Immutable
>>> del d['b']
Traceback (most recent call last):
...
TypeError: Immutable
Source code in preserves/values.py
340 341 342 343 |
|
from_kvs(kvs)
staticmethod
Constructs an ImmutableDict from a sequence of alternating keys and values; compare to the ImmutableDict constructor, which takes a sequence of key-value pairs.
>>> ImmutableDict.from_kvs(['a', 1, 'b', 2])
{'a': 1, 'b': 2}
>>> ImmutableDict.from_kvs(['a', 1, 'b', 2])['c'] = 3
Traceback (most recent call last):
...
TypeError: Immutable
Source code in preserves/values.py
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
|
Parser(input_buffer='', include_annotations=False, parse_embedded=lambda x: x)
Bases: TextCodec
Parser for the human-readable Preserves text syntax.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
input_buffer |
str
|
initial contents of the input buffer; may subsequently be extended by calling extend. |
''
|
include_annotations |
bool
|
if |
False
|
parse_embedded |
function accepting a |
lambda x: x
|
Normal usage is to supply input text, and keep calling next until a ShortPacket exception is raised:
>>> d = Parser('123 "hello" @x []')
>>> d.next()
123
>>> d.next()
'hello'
>>> d.next()
()
>>> d.next()
Traceback (most recent call last):
...
preserves.error.ShortPacket: Short input buffer
Alternatively, keep calling try_next until it yields
None
, which is not in the domain of Preserves Value
s:
>>> d = Parser('123 "hello" @x []')
>>> d.try_next()
123
>>> d.try_next()
'hello'
>>> d.try_next()
()
>>> d.try_next()
For convenience, Parser implements the iterator interface, backing it with try_next, so you can simply iterate over all complete values in an input:
>>> d = Parser('123 "hello" @x []')
>>> list(d)
[123, 'hello', ()]
>>> for v in Parser('123 "hello" @x []'):
... print(repr(v))
123
'hello'
()
Supply include_annotations=True
to read annotations alongside the annotated values:
>>> d = Parser('123 "hello" @x []', include_annotations=True)
>>> list(d)
[123, 'hello', @#x ()]
If you are incrementally reading from, say, a socket, you can use extend to add new input as if comes available:
>>> d = Parser('123 "he')
>>> d.try_next()
123
>>> d.try_next() # returns None because the input is incomplete
>>> d.extend('llo"')
>>> d.try_next()
'hello'
>>> d.try_next()
Attributes:
Name | Type | Description |
---|---|---|
input_buffer |
str
|
buffered input waiting to be processed |
index |
int
|
read position within |
Source code in preserves/text.py
132 133 134 135 136 137 |
|
extend(text)
Appends text
to the remaining contents of self.input_buffer
, trimming already-processed
text from the front of self.input_buffer
and resetting self.index
to zero.
Source code in preserves/text.py
139 140 141 142 143 |
|
next()
Reads the next complete Value
from the internal buffer, raising
ShortPacket if too few bytes are available, or
DecodeError if the input is invalid somehow.
Source code in preserves/text.py
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
|
try_next()
Like next, but returns None
instead of raising
ShortPacket.
Source code in preserves/text.py
385 386 387 388 389 390 391 392 393 |
|
Record(key, fields)
Bases: object
Representation of Preserves Record
s, which are a pair of a label Value
and a sequence of field Value
s.
>>> r = Record(Symbol('label'), ['field1', ['field2item1', 'field2item2']])
>>> r
#label('field1', ['field2item1', 'field2item2'])
>>> r.key
#label
>>> r.fields
('field1', ['field2item1', 'field2item2'])
>>> import preserves
>>> preserves.stringify(r)
'<label "field1" ["field2item1" "field2item2"]>'
>>> r == preserves.parse('<label "field1" ["field2item1" "field2item2"]>')
True
Parameters:
Name | Type | Description | Default |
---|---|---|---|
key |
Value
|
the |
required |
fields |
iterable[Value]
|
the fields of the |
required |
Attributes:
Name | Type | Description |
---|---|---|
key |
Value
|
the |
fields |
tuple[Value]
|
the fields of the |
Source code in preserves/values.py
152 153 154 155 |
|
makeBasicConstructor(label, fieldNames)
staticmethod
Constructs and returns a "constructor" for Record
s having a certain label
and
number of fields.
Deprecated
Use preserves.schema definitions instead.
The "constructor" is a callable function that accepts len(fields)
arguments and
returns a Record with label
as its label and the arguments
to the constructor as field values.
In addition, the "constructor" has a constructorInfo
attribute holding a
RecordConstructorInfo object, an isClassOf
attribute holding a unary function that returns True
iff its argument is a
Record with label label
and arity len(fieldNames)
, and
an ensureClassOf
attribute that raises an Exception
if isClassOf
returns false on
its argument and returns the argument otherwise.
Finally, for each field name f
in fieldNames
, the "constructor" object has an
attribute _f
that is a unary function that retrieves the f
field from the passed in
argument.
>>> c = Record.makeBasicConstructor(Symbol('date'), 'year month day')
>>> c(1969, 7, 16)
#date(1969, 7, 16)
>>> c.constructorInfo
#date/3
>>> c.isClassOf(c(1969, 7, 16))
True
>>> c.isClassOf(Record(Symbol('date'), [1969, 7, 16]))
True
>>> c.isClassOf(Record(Symbol('date'), [1969]))
False
>>> c.ensureClassOf(c(1969, 7, 16))
#date(1969, 7, 16)
>>> c.ensureClassOf(Record(Symbol('date'), [1969]))
Traceback (most recent call last):
...
TypeError: Record: expected #date/3, got #date(1969)
>>> c._year(c(1969, 7, 16))
1969
>>> c._month(c(1969, 7, 16))
7
>>> c._day(c(1969, 7, 16))
16
Parameters:
Name | Type | Description | Default |
---|---|---|---|
label |
Value
|
Label to use for constructed/matched |
required |
fieldNames |
tuple[str] | list[str] | str
|
Names of the |
required |
Source code in preserves/values.py
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
|
makeConstructor(labelSymbolText, fieldNames)
staticmethod
Equivalent to Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)
.
Deprecated
Use preserves.schema definitions instead.
Source code in preserves/values.py
190 191 192 193 194 195 196 197 198 |
|
ShortPacket
Bases: DecodeError
Raised whenever preserves.binary.Decoder or preserves.text.Parser discover that they want to read beyond the end of the currently-available input buffer in order to completely read an encoded value.
Symbol(name)
Bases: object
Representation of Preserves Symbol
s.
>>> Symbol('xyz')
#xyz
>>> Symbol('xyz').name
'xyz'
>>> repr(Symbol('xyz'))
'#xyz'
>>> str(Symbol('xyz'))
'xyz'
>>> import preserves
>>> preserves.stringify(Symbol('xyz'))
'xyz'
>>> preserves.stringify(Symbol('hello world'))
"'hello world'"
>>> preserves.parse('xyz')
#xyz
>>> preserves.parse("'hello world'")
#hello world
Attributes:
Name | Type | Description |
---|---|---|
name |
str | Symbol
|
The symbol's text label. If an existing Symbol is passed
in, the existing Symbol's |
Source code in preserves/values.py
78 79 |
|
annotate(v, *anns)
Wraps v
in an Annotated object, if it isn't already
wrapped, and appends each of the anns
to the Annotated's
annotations
sequence. NOTE: Does not recursively ensure that any parts of the argument
v
are themselves wrapped in Annotated objects!
>>> import preserves
>>> print(preserves.stringify(annotate(123, "A comment", "Another comment")))
@"A comment" @"Another comment" 123
Source code in preserves/values.py
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 |
|
canonicalize(v, **kwargs)
As encode, but sets canonicalize=True
in the
Encoder constructor.
Source code in preserves/binary.py
436 437 438 439 440 441 |
|
cmp(a, b)
Returns -1
if a
< b
, or 0
if a
= b
, or 1
if a
> b
according to the
Preserves total order.
Source code in preserves/compare.py
82 83 84 85 |
|
decode(bs, **kwargs)
Yields the first complete encoded value from bs
, passing kwargs
through to the
Decoder constructor. Raises exceptions as per
next.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
bs |
bytes
|
encoded data to decode |
required |
Source code in preserves/binary.py
247 248 249 250 251 252 253 254 255 256 |
|
decode_with_annotations(bs, **kwargs)
Like decode, but supplying include_annotations=True
to the
Decoder constructor.
Source code in preserves/binary.py
258 259 260 261 |
|
encode(v, **kwargs)
Encode a single Value
v
to a byte string. Any supplied kwargs
are passed on to the
underlying Encoder constructor.
Source code in preserves/binary.py
429 430 431 432 433 434 |
|
exec(self, v)
WARNING: This is not a function: it is a method on Selector, Predicate, and so on.
>>> sel = parse('/ [.length gt 1]')
>>> sel.exec(['', 'a', 'ab', 'abc', 'abcd', 'bcd', 'cd', 'd', ''])
('ab', 'abc', 'abcd', 'bcd', 'cd')
Source code in preserves/path.py
516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
|
is_annotated(v)
True
iff v
is an instance of Annotated.
Source code in preserves/values.py
495 496 497 |
|
parse(s)
Parse s
as a Preserves Path path expression, yielding a
Selector object. Selectors (and Predicates etc.) have an
exec method defined on them.
Raises ValueError
if s
is not a valid path expression.
Source code in preserves/path.py
134 135 136 137 138 139 140 141 142 |
|
parse_with_annotations(bs, **kwargs)
Like parse, but supplying include_annotations=True
to the
Parser constructor.
Source code in preserves/text.py
415 416 417 418 |
|
preserve(v)
Converts v
to a representation of a Preserves Value
by (repeatedly) setting
v = v.__preserve__()
while v
has a __preserve__
method. Parsed Schema
values are able to render themselves to their serialized representations this way.
Source code in preserves/values.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
stringify(v, **kwargs)
Convert a single Value
v
to a string. Any supplied kwargs
are passed on to the
underlying Formatter constructor.
Source code in preserves/text.py
602 603 604 605 606 607 |
|
strip_annotations(v, depth=inf)
Exposes depth
layers of raw structure of
potentially-Annotated Value
s. If depth==0
or v
is not
Annotated, just returns v
. Otherwise, descends recursively
into the structure of v.item
.
>>> import preserves
>>> a = preserves.parse('@"A comment" [@a 1 @b 2 @c 3]', include_annotations=True)
>>> is_annotated(a)
True
>>> print(preserves.stringify(a))
@"A comment" [@a 1 @b 2 @c 3]
>>> print(preserves.stringify(strip_annotations(a)))
[1 2 3]
>>> print(preserves.stringify(strip_annotations(a, depth=1)))
[@a 1 @b 2 @c 3]
Source code in preserves/values.py
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 |
|