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 rootValue` 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:
.annotationsselects each annotation on the root document^ Documentationretains only those values (each an annotation of the root) that areRecords with label equal to the symbolDocumentation. 0moves into the first child (the first field) of each suchRecord, which in our case is a list of otherValues/selects all immediate children of these listsstringretains 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."
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,
= Testselects values (remember, record labels) equal to the symbolTest - the right-hand argument
= NondeterministicTestselects values equal toNondeterministicTest
- the infix
The result is thus all
Records anywhere insidetestdatathat have eitherTestorNondeterministicTestas their labels. -
[. 1 rec]filters theseRecords by another path expression:. 1selects their second field (fields are numbered from 0)recretains only values that areRecords
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 Steps.
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.
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
513 514 515 516 517 518 519 520 521 522 523 524 525 526 | |
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
131 132 133 134 135 136 137 138 139 | |