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."
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.
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
523 524 525 526 527 528 529 530 531 532 533 534 535 536 |
|
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 |
|
Created: March 16, 2023