From 031575ad826d2db716e88d9b087376b193c1cfe1 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 16 Mar 2023 17:51:19 +0100 Subject: [PATCH 01/68] Python preserves doctest runner, and mkdocs documentation stubs --- _includes/value-grammar.md | 17 +++++++ _includes/what-is-preserves.md | 12 +++++ implementations/python/.envrc | 3 +- implementations/python/.gitignore | 1 + implementations/python/docs/api.md | 3 ++ implementations/python/docs/binary.md | 3 ++ implementations/python/docs/compare.md | 3 ++ implementations/python/docs/error.md | 3 ++ implementations/python/docs/fold.md | 3 ++ implementations/python/docs/index.md | 30 +++++++++++++ implementations/python/docs/merge.md | 3 ++ implementations/python/docs/path.md | 3 ++ implementations/python/docs/schema.md | 3 ++ implementations/python/docs/text.md | 3 ++ implementations/python/docs/values.md | 3 ++ implementations/python/mkdocs.yml | 16 +++++++ implementations/python/preserves/__init__.py | 34 ++++++++++++++ implementations/python/preserves/binary.py | 45 ++++++++++++++++++- implementations/python/preserves/compare.py | 8 ++++ implementations/python/preserves/error.py | 16 +++++-- implementations/python/preserves/fold.py | 3 ++ implementations/python/preserves/merge.py | 4 ++ implementations/python/preserves/path.py | 7 +++ implementations/python/preserves/schema.py | 25 +++++++++-- implementations/python/preserves/text.py | 20 ++++++++- implementations/python/preserves/values.py | 28 ++++++++++++ implementations/python/tests/test_doctests.py | 17 +++++++ preserves.md | 31 +------------ 28 files changed, 308 insertions(+), 39 deletions(-) create mode 100644 _includes/value-grammar.md create mode 100644 _includes/what-is-preserves.md create mode 100644 implementations/python/docs/api.md create mode 100644 implementations/python/docs/binary.md create mode 100644 implementations/python/docs/compare.md create mode 100644 implementations/python/docs/error.md create mode 100644 implementations/python/docs/fold.md create mode 100644 implementations/python/docs/index.md create mode 100644 implementations/python/docs/merge.md create mode 100644 implementations/python/docs/path.md create mode 100644 implementations/python/docs/schema.md create mode 100644 implementations/python/docs/text.md create mode 100644 implementations/python/docs/values.md create mode 100644 implementations/python/mkdocs.yml create mode 100644 implementations/python/tests/test_doctests.py diff --git a/_includes/value-grammar.md b/_includes/value-grammar.md new file mode 100644 index 0000000..c42f773 --- /dev/null +++ b/_includes/value-grammar.md @@ -0,0 +1,17 @@ + Value = Atom + | Compound + | Embedded + + Atom = Boolean + | Float + | Double + | SignedInteger + | String + | ByteString + | Symbol + + Compound = Record + | Sequence + | Set + | Dictionary + diff --git a/_includes/what-is-preserves.md b/_includes/what-is-preserves.md new file mode 100644 index 0000000..3c8d1bc --- /dev/null +++ b/_includes/what-is-preserves.md @@ -0,0 +1,12 @@ +*Preserves* is a data model, with associated serialization formats. + +It supports *records* with user-defined *labels*, embedded +*references*, and the usual suite of atomic and compound data types, +including *binary* data as a distinct type from text strings. Its +*annotations* allow separation of data from metadata such as comments, +trace information, and provenance information. + +Preserves departs from many other data languages in defining how to +*compare* two values. Comparison is based on the data model, not on +syntax or on data structures of any particular implementation +language. diff --git a/implementations/python/.envrc b/implementations/python/.envrc index 9ae9c9c..9150c90 100644 --- a/implementations/python/.envrc +++ b/implementations/python/.envrc @@ -2,7 +2,8 @@ if ! [ -d .venv ] then python -m venv .venv . .venv/bin/activate - pip install -U coverage setuptools setuptools_scm wheel + pip install -U coverage setuptools setuptools_scm wheel \ + mkdocs 'mkdocstrings[python]' mkdocs-material mkdocs-macros-plugin pip install -e . else . .venv/bin/activate diff --git a/implementations/python/.gitignore b/implementations/python/.gitignore index b214146..63ac3c7 100644 --- a/implementations/python/.gitignore +++ b/implementations/python/.gitignore @@ -4,3 +4,4 @@ htmlcov/ build/ dist/ *.egg-info/ +/.venv/ diff --git a/implementations/python/docs/api.md b/implementations/python/docs/api.md new file mode 100644 index 0000000..b855a9e --- /dev/null +++ b/implementations/python/docs/api.md @@ -0,0 +1,3 @@ +# The top-level preserves package + +::: preserves diff --git a/implementations/python/docs/binary.md b/implementations/python/docs/binary.md new file mode 100644 index 0000000..e70482b --- /dev/null +++ b/implementations/python/docs/binary.md @@ -0,0 +1,3 @@ +# Machine-oriented binary syntax + +::: preserves.binary diff --git a/implementations/python/docs/compare.md b/implementations/python/docs/compare.md new file mode 100644 index 0000000..720b1d9 --- /dev/null +++ b/implementations/python/docs/compare.md @@ -0,0 +1,3 @@ +# Comparing Values + +::: preserves.compare diff --git a/implementations/python/docs/error.md b/implementations/python/docs/error.md new file mode 100644 index 0000000..5731c34 --- /dev/null +++ b/implementations/python/docs/error.md @@ -0,0 +1,3 @@ +# Codec errors + +::: preserves.error diff --git a/implementations/python/docs/fold.md b/implementations/python/docs/fold.md new file mode 100644 index 0000000..3534854 --- /dev/null +++ b/implementations/python/docs/fold.md @@ -0,0 +1,3 @@ +# Traversing values + +::: preserves.fold diff --git a/implementations/python/docs/index.md b/implementations/python/docs/index.md new file mode 100644 index 0000000..fa643c7 --- /dev/null +++ b/implementations/python/docs/index.md @@ -0,0 +1,30 @@ +# Overview + +This package implements [Preserves](https://preserves.dev/) for Python 3.x. It provides the +core [semantics][] as well as both the [human-readable text +syntax](https://preserves.dev/preserves-text.html) (a superset of JSON) and [machine-oriented +binary format](https://preserves.dev/preserves-binary.html) (including canonicalization) for +Preserves. It also implements [Preserves Schema](https://preserves.dev/preserves-schema.html) +and [Preserves Path](https://preserves.dev/preserves-path.html). + + - Main package API: [preserves](/api) + +## What is Preserves? + +{% include "what-is-preserves.md" %} + +## Mapping between Preserves values and Python values + +Preserves `Value`s are categorized in the following way: + +{% include "value-grammar.md" %} + +Python's strings, byte strings, integers, booleans, and double-precision floats stand directly +for their Preserves counterparts. Small wrapper classes for `Float` and `Symbol` complete the +suite of atomic types. + +Python's lists and tuples correspond to Preserves `Sequence`s, and dicts and sets to +`Dictionary` and `Set` values, respectively. Preserves `Record`s are represented by a `Record` +class. Finally, embedded values are represented by a small `Embedded` wrapper class. + +[semantics]: https://preserves.dev/preserves.html#semantics diff --git a/implementations/python/docs/merge.md b/implementations/python/docs/merge.md new file mode 100644 index 0000000..629109a --- /dev/null +++ b/implementations/python/docs/merge.md @@ -0,0 +1,3 @@ +# Merging values + +::: preserves.merge diff --git a/implementations/python/docs/path.md b/implementations/python/docs/path.md new file mode 100644 index 0000000..ec40d98 --- /dev/null +++ b/implementations/python/docs/path.md @@ -0,0 +1,3 @@ +# Preserves Path + +::: preserves.path diff --git a/implementations/python/docs/schema.md b/implementations/python/docs/schema.md new file mode 100644 index 0000000..4b9f11f --- /dev/null +++ b/implementations/python/docs/schema.md @@ -0,0 +1,3 @@ +# Preserves Schema + +::: preserves.schema diff --git a/implementations/python/docs/text.md b/implementations/python/docs/text.md new file mode 100644 index 0000000..bd505d8 --- /dev/null +++ b/implementations/python/docs/text.md @@ -0,0 +1,3 @@ +# Human-readable text syntax + +::: preserves.text diff --git a/implementations/python/docs/values.md b/implementations/python/docs/values.md new file mode 100644 index 0000000..ef2837e --- /dev/null +++ b/implementations/python/docs/values.md @@ -0,0 +1,3 @@ +# Representations of Values + +::: preserves.values diff --git a/implementations/python/mkdocs.yml b/implementations/python/mkdocs.yml new file mode 100644 index 0000000..d0fa910 --- /dev/null +++ b/implementations/python/mkdocs.yml @@ -0,0 +1,16 @@ +site_name: Preserves +theme: + name: material +plugins: + - search + - mkdocstrings + - macros: + include_dir: ../../_includes +markdown_extensions: + - admonition + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences +watch: + - preserves diff --git a/implementations/python/preserves/__init__.py b/implementations/python/preserves/__init__.py index 9e2b391..83500b3 100644 --- a/implementations/python/preserves/__init__.py +++ b/implementations/python/preserves/__init__.py @@ -1,4 +1,31 @@ +''' +``` +import preserves +``` + +The main package re-exports a subset of the exports of its constituent modules: + +(TODO: improve the presentation of this list) + +- From [`values`](/values): `Float, Symbol, Record, ImmutableDict, Embedded, preserve, Annotated, is_annotated, strip_annotations, annotate` + +- From [`compare`](/compare): `cmp` + +- From [`error`](/error): `DecodeError, EncodeError, ShortPacket` + +- From [`binary`](/binary): `Decoder, Encoder, decode, decode_with_annotations, encode, canonicalize` + +- From [`text`](/text): `Parser, Formatter, parse, parse_with_annotations, stringify` + +- From [`merge`](/merge): `merge` + +- and submodules [`fold`](/fold) and [`compare`](/compare). + +In addition, it provides a few utility aliases for common tasks: +''' + from .values import Float, Symbol, Record, ImmutableDict, Embedded, preserve + from .values import Annotated, is_annotated, strip_annotations, annotate from .compare import cmp @@ -13,4 +40,11 @@ from .merge import merge from . import fold, compare loads = parse +''' +This alias for `parse` provides a familiar pythonesque name for converting a string to a Preserves `Value`. +''' + dumps = stringify +''' +This alias for `stringify` provides a familiar pythonesque name for converting a Preserves `Value` to a string. +''' diff --git a/implementations/python/preserves/binary.py b/implementations/python/preserves/binary.py index 8b246f4..7f2904c 100644 --- a/implementations/python/preserves/binary.py +++ b/implementations/python/preserves/binary.py @@ -1,3 +1,20 @@ +"""The [preserves.binary][] module implements the [Preserves machine-oriented binary +syntax](https://preserves.dev/preserves-binary.html). + +The main entry points are functions [encode][preserves.binary.encode], +[canonicalize][preserves.binary.canonicalize], [decode][preserves.binary.decode], and +[decode_with_annotations][preserves.binary.decode_with_annotations]. + +```python +>>> encode(Record(Symbol('hi'), [])) +b'\\xb4\\xb3\\x02hi\\x84' +>>> decode(b'\\xb4\\xb3\\x02hi\\x84') +#hi() + +``` + +""" + import numbers import struct @@ -5,10 +22,15 @@ from .values import * from .error import * from .compat import basestring_, ord_ -class BinaryCodec(object): pass +class BinaryCodec(object): + """TODO""" + pass class Decoder(BinaryCodec): + """TODO""" + def __init__(self, packet=b'', include_annotations=False, decode_embedded=lambda x: x): + """TODO""" super(Decoder, self).__init__() self.packet = packet self.index = 0 @@ -16,6 +38,7 @@ class Decoder(BinaryCodec): self.decode_embedded = decode_embedded def extend(self, data): + """TODO""" self.packet = self.packet[self.index:] + data self.index = 0 @@ -69,6 +92,7 @@ class Decoder(BinaryCodec): return v def next(self): + """TODO""" tag = self.nextbyte() if tag == 0x80: return self.wrap(False) if tag == 0x81: return self.wrap(True) @@ -99,6 +123,7 @@ class Decoder(BinaryCodec): raise DecodeError('Invalid tag: ' + hex(tag)) def try_next(self): + """TODO""" start = self.index try: return self.next() @@ -107,6 +132,7 @@ class Decoder(BinaryCodec): return None def __iter__(self): + """TODO""" return self def __next__(self): @@ -116,19 +142,26 @@ class Decoder(BinaryCodec): return v def decode(bs, **kwargs): + """TODO""" return Decoder(packet=bs, **kwargs).next() def decode_with_annotations(bs, **kwargs): + """TODO""" return Decoder(packet=bs, include_annotations=True, **kwargs).next() class Encoder(BinaryCodec): + """Implementation of an encoder for the machine-oriented binary Preserves syntax. + + """ def __init__(self, encode_embedded=lambda x: x, canonicalize=False): + """TODO""" super(Encoder, self).__init__() self.buffer = bytearray() self._encode_embedded = encode_embedded self._canonicalize = canonicalize def reset(self): + """TODO""" self.buffer = bytearray() def encode_embedded(self, v): @@ -137,6 +170,7 @@ class Encoder(BinaryCodec): return self._encode_embedded(v) def contents(self): + """TODO""" return bytes(self.buffer) def varint(self, v): @@ -187,6 +221,7 @@ class Encoder(BinaryCodec): c.emit_entries(self, 7) def append(self, v): + """TODO""" v = preserve(v) if hasattr(v, '__preserve_write_binary__'): v.__preserve_write_binary__(self) @@ -246,9 +281,17 @@ class Canonicalizer: outer_encoder.buffer.append(0x84) def encode(v, **kwargs): + """Encode a single `Value` v to a byte string. Any kwargs are passed on to the underlying + [Encoder][preserves.binary.Encoder] constructor. + + """ e = Encoder(**kwargs) e.append(v) return e.contents() def canonicalize(v, **kwargs): + """As [encode][preserves.binary.encode], but sets `canonicalize=True` in the + [Encoder][preserves.binary.Encoder] constructor. + + """ return encode(v, canonicalize=True, **kwargs) diff --git a/implementations/python/preserves/compare.py b/implementations/python/preserves/compare.py index 1572426..b8cc685 100644 --- a/implementations/python/preserves/compare.py +++ b/implementations/python/preserves/compare.py @@ -1,3 +1,5 @@ +"""TODO""" + import numbers from enum import Enum from functools import cmp_to_key @@ -50,15 +52,19 @@ def type_number(v): return TypeNumber.SEQUENCE def cmp(a, b): + """TODO""" return _cmp(preserve(a), preserve(b)) def lt(a, b): + """TODO""" return cmp(a, b) < 0 def le(a, b): + """TODO""" return cmp(a, b) <= 0 def eq(a, b): + """TODO""" return _eq(preserve(a), preserve(b)) key = cmp_to_key(cmp) @@ -66,9 +72,11 @@ _key = key _sorted = sorted def sorted(iterable, *, key=lambda x: x, reverse=False): + """TODO""" return _sorted(iterable, key=lambda x: _key(key(x)), reverse=reverse) def sorted_items(d): + """TODO""" return sorted(d.items(), key=_item_key) def _eq_sequences(aa, bb): diff --git a/implementations/python/preserves/error.py b/implementations/python/preserves/error.py index d0aee8a..39122c5 100644 --- a/implementations/python/preserves/error.py +++ b/implementations/python/preserves/error.py @@ -1,3 +1,13 @@ -class DecodeError(ValueError): pass -class EncodeError(ValueError): pass -class ShortPacket(DecodeError): pass +"""TODO""" + +class DecodeError(ValueError): + """TODO""" + pass + +class EncodeError(ValueError): + """TODO""" + pass + +class ShortPacket(DecodeError): + """TODO""" + pass diff --git a/implementations/python/preserves/fold.py b/implementations/python/preserves/fold.py index 90b1e43..0cdb57b 100644 --- a/implementations/python/preserves/fold.py +++ b/implementations/python/preserves/fold.py @@ -1,6 +1,9 @@ +"""TODO""" + from .values import ImmutableDict, dict_kvs, Embedded, Record def map_embeddeds(f, v): + """TODO""" def walk(v): if isinstance(v, Embedded): return f(v.embeddedValue) diff --git a/implementations/python/preserves/merge.py b/implementations/python/preserves/merge.py index c04fac1..072a649 100644 --- a/implementations/python/preserves/merge.py +++ b/implementations/python/preserves/merge.py @@ -1,9 +1,12 @@ +"""TODO""" + from .values import ImmutableDict, dict_kvs, Embedded, Record def merge_embedded_id(a, b): return a if a is b else None def merge(v0, *vs, merge_embedded=None): + """TODO""" v = v0 for vN in vs: v = merge2(v, vN, merge_embedded=merge_embedded) @@ -17,6 +20,7 @@ def merge_seq(aa, bb, merge_embedded=None): return [merge2(a, b, merge_embedded=merge_embedded) for (a, b) in zip(aa, bb)] def merge2(a, b, merge_embedded=None): + """TODO""" if a == b: return a if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)): diff --git a/implementations/python/preserves/path.py b/implementations/python/preserves/path.py index bf05a6c..4b0ad15 100644 --- a/implementations/python/preserves/path.py +++ b/implementations/python/preserves/path.py @@ -1,3 +1,5 @@ +"""TODO (document __main__ behaviour)""" + from . import * from .schema import load_schema_file, extend from .values import _unwrap @@ -6,11 +8,16 @@ import pathlib import re syntax = load_schema_file(pathlib.Path(__file__).parent / 'path.prb').path +"""TODO""" Selector = syntax.Selector +"""TODO""" + Predicate = syntax.Predicate +"""TODO""" def parse(s): + """TODO""" return parse_selector(Parser(s)) def parse_selector(tokens): diff --git a/implementations/python/preserves/schema.py b/implementations/python/preserves/schema.py index f887b15..04233fa 100644 --- a/implementations/python/preserves/schema.py +++ b/implementations/python/preserves/schema.py @@ -1,7 +1,8 @@ -# -# This is an implementation of [Preserves Schema](https://preserves.dev/preserves-schema.html) -# for Python 3. -# +"""This is an implementation of [Preserves Schema](https://preserves.dev/preserves-schema.html) +for Python 3. + +TODO +""" from . import * import pathlib @@ -38,6 +39,7 @@ def sequenceish(x): return isinstance(x, tuple) or isinstance(x, list) class SchemaDecodeFailed(ValueError): + """TODO""" def __init__(self, cls, p, v, failures=None): super().__init__() self.cls = cls @@ -81,6 +83,8 @@ class ExplanationBuilder: return '\n' + ' ' * self.indentLevel + self._node(failure) + ''.join(nested) class SchemaObject: + """TODO""" + ROOTNS = None SCHEMA = None MODULE_PATH = None @@ -89,10 +93,12 @@ class SchemaObject: @classmethod def decode(cls, v): + """TODO""" raise NotImplementedError('Subclass responsibility') @classmethod def try_decode(cls, v): + """TODO""" try: return cls.decode(v) except SchemaDecodeFailed: @@ -176,6 +182,7 @@ class SchemaObject: raise ValueError(f'Bad schema {p}') def __preserve__(self): + """TODO""" raise NotImplementedError('Subclass responsibility') def __repr__(self): @@ -192,6 +199,8 @@ class SchemaObject: raise NotImplementedError('Subclass responsibility') class Enumeration(SchemaObject): + """TODO""" + VARIANTS = None def __init__(self): @@ -239,6 +248,8 @@ def safehasattr(o, k): return hasattr(o, safeattrname(k)) class Definition(SchemaObject): + """TODO""" + EMPTY = False SIMPLE = False FIELD_NAMES = [] @@ -345,6 +356,7 @@ class escape: return self.escaped def encode(p, v): + """TODO""" if hasattr(v, '__escape_schema__'): return preserve(v.__escape_schema__()) if p == ANY: @@ -431,6 +443,7 @@ def definition_not_found(module_path, name): raise KeyError('Definition not found: ' + module_path_str(module_path + (name,))) class Namespace: + """TODO""" def __init__(self, prefix): self._prefix = prefix @@ -453,6 +466,7 @@ class Namespace: return repr(self._items()) class Compiler: + """TODO""" def __init__(self): self.root = Namespace(()) @@ -487,12 +501,14 @@ class Compiler: ns[n] = c def load_schema_file(filename): + """TODO""" c = Compiler() c.load(filename) return c.root # a decorator def extend(cls): + """TODO""" def extender(f): setattr(cls, f.__name__, f) return f @@ -500,6 +516,7 @@ def extend(cls): __metaschema_filename = pathlib.Path(__file__).parent / 'schema.prb' meta = load_schema_file(__metaschema_filename).schema +"""TODO""" if __name__ == '__main__': with open(__metaschema_filename, 'rb') as f: diff --git a/implementations/python/preserves/text.py b/implementations/python/preserves/text.py index 321f8b3..9924636 100644 --- a/implementations/python/preserves/text.py +++ b/implementations/python/preserves/text.py @@ -1,3 +1,5 @@ +"""TODO""" + import numbers import struct import base64 @@ -8,12 +10,17 @@ from .error import * from .compat import basestring_, unichr_ from .binary import Decoder -class TextCodec(object): pass +class TextCodec(object): + """TODO""" + pass NUMBER_RE = re.compile(r'^([-+]?\d+)(((\.\d+([eE][-+]?\d+)?)|([eE][-+]?\d+))([fF]?))?$') class Parser(TextCodec): + """TODO""" + def __init__(self, input_buffer=u'', include_annotations=False, parse_embedded=lambda x: x): + """TODO""" super(Parser, self).__init__() self.input_buffer = input_buffer self.index = 0 @@ -21,6 +28,7 @@ class Parser(TextCodec): self.parse_embedded = parse_embedded def extend(self, text): + """TODO""" self.input_buffer = self.input_buffer[self.index:] + text self.index = 0 @@ -200,6 +208,7 @@ class Parser(TextCodec): return Annotated(v) if self.include_annotations else v def next(self): + """TODO""" self.skip_whitespace() c = self.peek() if c == '"': @@ -264,6 +273,7 @@ class Parser(TextCodec): return self.wrap(self.read_raw_symbol_or_number([c])) def try_next(self): + """TODO""" start = self.index try: return self.next() @@ -272,6 +282,7 @@ class Parser(TextCodec): return None def __iter__(self): + """TODO""" return self def __next__(self): @@ -281,17 +292,21 @@ class Parser(TextCodec): return v def parse(bs, **kwargs): + """TODO""" return Parser(input_buffer=bs, **kwargs).next() def parse_with_annotations(bs, **kwargs): + """TODO""" return Parser(input_buffer=bs, include_annotations=True, **kwargs).next() class Formatter(TextCodec): + """TODO""" def __init__(self, format_embedded=lambda x: x, indent=None, with_commas=False, trailing_comma=False): + """TODO""" super(Formatter, self).__init__() self.indent_delta = 0 if indent is None else indent self.indent_distance = 0 @@ -306,6 +321,7 @@ class Formatter(TextCodec): return self._format_embedded(v) def contents(self): + """TODO""" return u''.join(self.chunks) def is_indenting(self): @@ -352,6 +368,7 @@ class Formatter(TextCodec): self.chunks.append(closer) def append(self, v): + """TODO""" v = preserve(v) if hasattr(v, '__preserve_write_text__'): v.__preserve_write_text__(self) @@ -402,6 +419,7 @@ class Formatter(TextCodec): raise TypeError('Cannot preserves-format: ' + repr(v)) def stringify(v, **kwargs): + """TODO""" e = Formatter(**kwargs) e.append(v) return e.contents() diff --git a/implementations/python/preserves/values.py b/implementations/python/preserves/values.py index cdb2fc0..d3e7225 100644 --- a/implementations/python/preserves/values.py +++ b/implementations/python/preserves/values.py @@ -1,3 +1,5 @@ +"""TODO""" + import re import sys import struct @@ -6,6 +8,7 @@ import math from .error import DecodeError def preserve(v): + """TODO""" while hasattr(v, '__preserve__'): v = v.__preserve__() return v @@ -14,6 +17,7 @@ def float_to_int(v): return struct.unpack('>Q', struct.pack('>d', v))[0] def cmp_floats(a, b): + """TODO""" a = float_to_int(a) b = float_to_int(b) if a & 0x8000000000000000: a = a ^ 0x7fffffffffffffff @@ -21,7 +25,9 @@ def cmp_floats(a, b): return a - b class Float(object): + """TODO""" def __init__(self, value): + """TODO""" self.value = value def __eq__(self, other): @@ -66,6 +72,7 @@ class Float(object): @staticmethod def from_bytes(bs): + """TODO""" vf = struct.unpack('>I', bs)[0] if (vf & 0x7f800000) == 0x7f800000: # NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision. @@ -80,7 +87,9 @@ class Float(object): RAW_SYMBOL_RE = re.compile(r'^[-a-zA-Z0-9~!$%^&*?_=+/.]+$') class Symbol(object): + """TODO""" def __init__(self, name): + """TODO""" self.name = name.name if isinstance(name, Symbol) else name def __eq__(self, other): @@ -125,7 +134,9 @@ class Symbol(object): formatter.chunks.append('|') class Record(object): + """TODO""" def __init__(self, key, fields): + """TODO""" self.key = key self.fields = tuple(fields) self.__hash = None @@ -165,10 +176,12 @@ class Record(object): @staticmethod def makeConstructor(labelSymbolText, fieldNames): + """TODO""" return Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames) @staticmethod def makeBasicConstructor(label, fieldNames): + """TODO""" if type(fieldNames) == str: fieldNames = fieldNames.split() arity = len(fieldNames) @@ -196,7 +209,9 @@ class Record(object): return ctor class RecordConstructorInfo(object): + """TODO""" def __init__(self, key, arity): + """TODO""" self.key = key self.arity = arity @@ -218,7 +233,9 @@ class RecordConstructorInfo(object): # Blub blub blub class ImmutableDict(dict): + """TODO""" def __init__(self, *args, **kwargs): + """TODO""" if hasattr(self, '__hash'): raise TypeError('Immutable') super(ImmutableDict, self).__init__(*args, **kwargs) self.__hash = None @@ -241,6 +258,7 @@ class ImmutableDict(dict): @staticmethod def from_kvs(kvs): + """TODO""" i = iter(kvs) result = ImmutableDict() result_proxy = super(ImmutableDict, result) @@ -257,6 +275,7 @@ class ImmutableDict(dict): return result def dict_kvs(d): + """TODO""" for k in d: yield k yield d[k] @@ -264,7 +283,9 @@ def dict_kvs(d): inf = float('inf') class Annotated(object): + """TODO""" def __init__(self, item): + """TODO""" self.annotations = [] self.item = item @@ -282,9 +303,11 @@ class Annotated(object): formatter.append(self.item) def strip(self, depth=inf): + """TODO""" return strip_annotations(self, depth) def peel(self): + """TODO""" return strip_annotations(self, 1) def __eq__(self, other): @@ -300,9 +323,11 @@ class Annotated(object): return ' '.join(list('@' + repr(a) for a in self.annotations) + [repr(self.item)]) def is_annotated(v): + """TODO""" return isinstance(v, Annotated) def strip_annotations(v, depth=inf): + """TODO""" if depth == 0: return v if not is_annotated(v): return v @@ -329,6 +354,7 @@ def strip_annotations(v, depth=inf): return v def annotate(v, *anns): + """TODO""" if not is_annotated(v): v = Annotated(v) for a in anns: @@ -342,7 +368,9 @@ def _unwrap(x): return x class Embedded: + """TODO""" def __init__(self, value): + """TODO""" self.embeddedValue = value def __eq__(self, other): diff --git a/implementations/python/tests/test_doctests.py b/implementations/python/tests/test_doctests.py new file mode 100644 index 0000000..a29fd4c --- /dev/null +++ b/implementations/python/tests/test_doctests.py @@ -0,0 +1,17 @@ +import doctest +import pkgutil +import importlib.util + +import preserves + +def load_tests(loader, tests, ignore): + def m(spec): + mod = importlib.util.module_from_spec(spec) + mod.__loader__.exec_module(mod) + tests.addTests(doctest.DocTestSuite(mod)) + spec = preserves.__spec__ + m(spec) + for mi in pkgutil.walk_packages(spec.submodule_search_locations, spec.name + '.'): + subspec = mi.module_finder.find_spec(mi.name) + m(subspec) + return tests diff --git a/preserves.md b/preserves.md index 02c89e9..98f9e08 100644 --- a/preserves.md +++ b/preserves.md @@ -6,19 +6,7 @@ title: "Preserves: an Expressive Data Language" Tony Garnock-Jones {{ site.version_date }}. Version {{ site.version }}. -*Preserves* is a data model, with associated serialization formats. - -It supports *records* with user-defined *labels*, embedded *references*, -and the usual suite of atomic and compound data types, including -*binary* data as a distinct type from text strings. Its *annotations* -allow separation of data from metadata such as -[comments](conventions.html#comments), trace information, and provenance -information. - -Preserves departs from many other data languages in defining how to -*compare* two values. Comparison is based on the data model, not on -syntax or on data structures of any particular implementation -language. +{% include what-is-preserves.md %} This document defines the core semantics and data model of Preserves and presents a handful of examples. Two other core documents define @@ -38,22 +26,7 @@ element of that set. data. Every `Value` is finite and non-cyclic. Embedded values, called `Embedded`s, are a third, special-case category. - Value = Atom - | Compound - | Embedded - - Atom = Boolean - | Float - | Double - | SignedInteger - | String - | ByteString - | Symbol - - Compound = Record - | Sequence - | Set - | Dictionary +{% include value-grammar.md %} **Total order.** As we go, we will incrementally specify a total order over `Value`s. Two values of the From 99258be1ef2f6ca524ebd2d2a357e527ea1c9c30 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 16 Mar 2023 20:12:17 +0100 Subject: [PATCH 02/68] Fix doctest module loading to avoid double-creation of modules --- implementations/python/tests/test_doctests.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/implementations/python/tests/test_doctests.py b/implementations/python/tests/test_doctests.py index a29fd4c..06d249c 100644 --- a/implementations/python/tests/test_doctests.py +++ b/implementations/python/tests/test_doctests.py @@ -1,17 +1,17 @@ import doctest import pkgutil -import importlib.util +import importlib import preserves def load_tests(loader, tests, ignore): - def m(spec): - mod = importlib.util.module_from_spec(spec) - mod.__loader__.exec_module(mod) + mods = [] + mods.append(preserves) + for mi in pkgutil.iter_modules(preserves.__path__, preserves.__name__ + '.'): + mod = importlib.import_module(mi.name) + mods.append(mod) + + for mod in mods: tests.addTests(doctest.DocTestSuite(mod)) - spec = preserves.__spec__ - m(spec) - for mi in pkgutil.walk_packages(spec.submodule_search_locations, spec.name + '.'): - subspec = mi.module_finder.find_spec(mi.name) - m(subspec) + return tests From 7cbd1a28137f09d5633d23d9159aae0b4450ec45 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 16 Mar 2023 20:55:49 +0100 Subject: [PATCH 03/68] Docs for preserves.binary; better encoding annotations with canonicalization --- implementations/python/mkdocs.yml | 6 +- implementations/python/preserves/binary.py | 186 +++++++++++++++--- implementations/python/preserves/text.py | 4 +- implementations/python/preserves/values.py | 16 +- .../python/tests/test_preserves.py | 8 +- 5 files changed, 185 insertions(+), 35 deletions(-) diff --git a/implementations/python/mkdocs.yml b/implementations/python/mkdocs.yml index d0fa910..8a7701b 100644 --- a/implementations/python/mkdocs.yml +++ b/implementations/python/mkdocs.yml @@ -3,7 +3,11 @@ theme: name: material plugins: - search - - mkdocstrings + - mkdocstrings: + handlers: + python: + options: + merge_init_into_class: true - macros: include_dir: ../../_includes markdown_extensions: diff --git a/implementations/python/preserves/binary.py b/implementations/python/preserves/binary.py index 7f2904c..ee0b267 100644 --- a/implementations/python/preserves/binary.py +++ b/implementations/python/preserves/binary.py @@ -23,14 +23,108 @@ from .error import * from .compat import basestring_, ord_ class BinaryCodec(object): - """TODO""" pass class Decoder(BinaryCodec): - """TODO""" + """Implementation of a decoder for the machine-oriented binary Preserves syntax. + + Args: + packet (bytes): + initial contents of the input buffer; may subsequently be extended by calling + [extend][preserves.binary.Decoder.extend]. + + include_annotations (bool): + if `True`, wrap each value and subvalue in an + [Annotated][preserves.values.Annotated] object. + + decode_embedded: + function accepting a `Value` and returning a possibly-decoded form of that value + suitable for placing into an [Embedded][preserves.values.Embedded] object. + + Normal usage is to supply a buffer, and keep calling [next][preserves.binary.Decoder.next] + until a [ShortPacket][preserves.error.ShortPacket] exception is raised: + + ```python + >>> d = Decoder(b'\\xa0{\\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][preserves.binary.Decoder.try_next] until it yields + `None`, which is not in the domain of Preserves `Value`s: + + ```python + >>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84') + >>> d.try_next() + 123 + >>> d.try_next() + 'hello' + >>> d.try_next() + () + >>> d.try_next() + + ``` + + For convenience, [Decoder][preserves.binary.Decoder] implements the iterator interface, + backing it with [try_next][preserves.binary.Decoder.try_next], so you can simply iterate + over all complete values in an input: + + ```python + >>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84') + >>> list(d) + [123, 'hello', ()] + + ``` + + ```python + >>> for v in Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84'): + ... print(repr(v)) + 123 + 'hello' + () + + ``` + + Supply `include_annotations=True` to read annotations alongside the annotated values: + + ```python + >>> d = Decoder(b'\\xa0{\\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][preserves.binary.Decoder.extend] to add new input as if comes available: + + ```python + >>> d = Decoder(b'\\xa0{\\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: + packet (bytes): buffered input waiting to be processed + index (int): read position within `packet` + + """ def __init__(self, packet=b'', include_annotations=False, decode_embedded=lambda x: x): - """TODO""" super(Decoder, self).__init__() self.packet = packet self.index = 0 @@ -38,7 +132,8 @@ class Decoder(BinaryCodec): self.decode_embedded = decode_embedded def extend(self, data): - """TODO""" + """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.""" self.packet = self.packet[self.index:] + data self.index = 0 @@ -92,7 +187,11 @@ class Decoder(BinaryCodec): return v def next(self): - """TODO""" + """Reads the next complete `Value` from the internal buffer, raising + [ShortPacket][preserves.error.ShortPacket] if too few bytes are available, or + [DecodeError][preserves.error.DecodeError] if the input is invalid somehow. + + """ tag = self.nextbyte() if tag == 0x80: return self.wrap(False) if tag == 0x81: return self.wrap(True) @@ -123,7 +222,8 @@ class Decoder(BinaryCodec): raise DecodeError('Invalid tag: ' + hex(tag)) def try_next(self): - """TODO""" + """Like [next][preserves.binary.Decoder.next], but returns `None` instead of raising + [ShortPacket][preserves.error.ShortPacket].""" start = self.index try: return self.next() @@ -132,7 +232,6 @@ class Decoder(BinaryCodec): return None def __iter__(self): - """TODO""" return self def __next__(self): @@ -142,26 +241,71 @@ class Decoder(BinaryCodec): return v def decode(bs, **kwargs): - """TODO""" + """Yields the first complete encoded value from `bs`, passing `kwargs` through to the + [Decoder][preserves.binary.Decoder] constructor. Raises exceptions as per + [next][preserves.binary.Decoder.next]. + + Args: + bs (bytes): encoded data to decode + + """ return Decoder(packet=bs, **kwargs).next() def decode_with_annotations(bs, **kwargs): - """TODO""" + """Like [decode][preserves.binary.decode], but supplying `include_annotations=True` to the + [Decoder][preserves.binary.Decoder] constructor.""" return Decoder(packet=bs, include_annotations=True, **kwargs).next() class Encoder(BinaryCodec): """Implementation of an encoder for the machine-oriented binary Preserves syntax. + ```python + >>> e = Encoder() + >>> e.append(123) + >>> e.append('hello') + >>> e.append(annotate([], Symbol('x'))) + >>> e.contents() + b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84' + + ``` + + Args: + encode_embedded: + function accepting an [Embedded][preserves.values.Embedded].embeddedValue and + returning a `Value` for serialization. + + canonicalize (bool): + if `True`, ensures the serialized data are in [canonical + form](https://preserves.dev/canonical-binary.html). This is slightly more work than + producing potentially-non-canonical output. + + include_annotations (bool | None): + if `None`, includes annotations in the output only when `canonicalize` is `False`, + because [canonical serialization of values demands omission of + annotations](https://preserves.dev/canonical-binary.html). If explicitly `True` or + `False`, however, annotations will be included resp. excluded no matter the + `canonicalize` setting. This can be used to get canonical ordering + (`canonicalize=True`) *and* annotations (`include_annotations=True`). + + Attributes: + buffer (bytearray): accumulator for the output of the encoder + """ - def __init__(self, encode_embedded=lambda x: x, canonicalize=False): - """TODO""" + def __init__(self, + encode_embedded=lambda x: x, + canonicalize=False, + include_annotations=None): super(Encoder, self).__init__() self.buffer = bytearray() self._encode_embedded = encode_embedded self._canonicalize = canonicalize + if include_annotations is None: + self.include_annotations = not self._canonicalize + else: + self.include_annotations = include_annotations def reset(self): - """TODO""" + """Clears `self.buffer` to a fresh empty `bytearray`.""" self.buffer = bytearray() def encode_embedded(self, v): @@ -170,7 +314,7 @@ class Encoder(BinaryCodec): return self._encode_embedded(v) def contents(self): - """TODO""" + """Returns a `bytes` constructed from the contents of `self.buffer`.""" return bytes(self.buffer) def varint(self, v): @@ -208,7 +352,7 @@ class Encoder(BinaryCodec): if not self._canonicalize: self.encodevalues(6, v) else: - c = Canonicalizer(self._encode_embedded) + c = Canonicalizer(self._encode_embedded, self.include_annotations) for i in v: c.entry([i]) c.emit_entries(self, 6) @@ -216,12 +360,12 @@ class Encoder(BinaryCodec): if not self._canonicalize: self.encodevalues(7, list(dict_kvs(v))) else: - c = Canonicalizer(self._encode_embedded) + c = Canonicalizer(self._encode_embedded, self.include_annotations) for (kk, vv) in v.items(): c.entry([kk, vv]) c.emit_entries(self, 7) def append(self, v): - """TODO""" + """Extend `self.buffer` with an encoding of `v`.""" v = preserve(v) if hasattr(v, '__preserve_write_binary__'): v.__preserve_write_binary__(self) @@ -265,8 +409,8 @@ class Encoder(BinaryCodec): raise TypeError('Cannot preserves-encode: ' + repr(v)) class Canonicalizer: - def __init__(self, encode_embedded): - self.encoder = Encoder(encode_embedded, canonicalize=True) + def __init__(self, encode_embedded, include_annotations): + self.encoder = Encoder(encode_embedded, canonicalize=True, include_annotations=include_annotations) self.entries = [] def entry(self, pieces): @@ -281,10 +425,8 @@ class Canonicalizer: outer_encoder.buffer.append(0x84) def encode(v, **kwargs): - """Encode a single `Value` v to a byte string. Any kwargs are passed on to the underlying - [Encoder][preserves.binary.Encoder] constructor. - - """ + """Encode a single `Value` `v` to a byte string. Any supplied `kwargs` are passed on to the + underlying [Encoder][preserves.binary.Encoder] constructor.""" e = Encoder(**kwargs) e.append(v) return e.contents() diff --git a/implementations/python/preserves/text.py b/implementations/python/preserves/text.py index 9924636..f30e281 100644 --- a/implementations/python/preserves/text.py +++ b/implementations/python/preserves/text.py @@ -305,7 +305,8 @@ class Formatter(TextCodec): format_embedded=lambda x: x, indent=None, with_commas=False, - trailing_comma=False): + trailing_comma=False, + include_annotations=True): """TODO""" super(Formatter, self).__init__() self.indent_delta = 0 if indent is None else indent @@ -314,6 +315,7 @@ class Formatter(TextCodec): self.trailing_comma = trailing_comma self.chunks = [] self._format_embedded = format_embedded + self.include_annotations = include_annotations def format_embedded(self, v): if self._format_embedded is None: diff --git a/implementations/python/preserves/values.py b/implementations/python/preserves/values.py index d3e7225..e731040 100644 --- a/implementations/python/preserves/values.py +++ b/implementations/python/preserves/values.py @@ -290,16 +290,18 @@ class Annotated(object): self.item = item def __preserve_write_binary__(self, encoder): - for a in self.annotations: - encoder.buffer.append(0x85) - encoder.append(a) + if encoder.include_annotations: + for a in self.annotations: + encoder.buffer.append(0x85) + encoder.append(a) encoder.append(self.item) def __preserve_write_text__(self, formatter): - for a in self.annotations: - formatter.chunks.append('@') - formatter.append(a) - formatter.chunks.append(' ') + if formatter.include_annotations: + for a in self.annotations: + formatter.chunks.append('@') + formatter.append(a) + formatter.chunks.append(' ') formatter.append(self.item) def strip(self, depth=inf): diff --git a/implementations/python/tests/test_preserves.py b/implementations/python/tests/test_preserves.py index ecd2b6c..856caba 100644 --- a/implementations/python/tests/test_preserves.py +++ b/implementations/python/tests/test_preserves.py @@ -261,7 +261,7 @@ def install_test(d, variant, tName, binaryForm, annotatedTextForm): def test_back(self): self.assertPreservesEqual(self.DS(binaryForm), back) def test_back_ann(self): self.assertPreservesEqual(self.D(self.E(annotatedTextForm)), annotatedTextForm) def test_encode(self): self.assertPreservesEqual(self.E(forward), binaryForm) - def test_encode_canonical(self): self.assertPreservesEqual(self.EC(annotatedTextForm), binaryForm) + def test_encode_nondet(self): self.assertPreservesEqual(self.ENONDET(annotatedTextForm), binaryForm) def test_encode_ann(self): self.assertPreservesEqual(self.E(annotatedTextForm), binaryForm) add_method(d, tName, test_match_expected) add_method(d, tName, test_roundtrip) @@ -271,7 +271,7 @@ def install_test(d, variant, tName, binaryForm, annotatedTextForm): if variant in ['normal']: add_method(d, tName, test_encode) if variant in ['nondeterministic']: - add_method(d, tName, test_encode_canonical) + add_method(d, tName, test_encode_nondet) if variant in ['normal', 'nondeterministic']: add_method(d, tName, test_encode_ann) @@ -323,8 +323,8 @@ class CommonTestSuite(PreservesTestCase): def E(self, v): return encode(v, encode_embedded=lambda x: x) - def EC(self, v): - return encode(v, encode_embedded=lambda x: x, canonicalize=True) + def ENONDET(self, v): + return encode(v, encode_embedded=lambda x: x, canonicalize=True, include_annotations=True) class RecordTests(PreservesTestCase): def test_getters(self): From e3e1dd4944628a01afda01bc40a8780d19b21393 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 16 Mar 2023 21:04:37 +0100 Subject: [PATCH 04/68] Better __init__.py docs --- implementations/python/preserves/__init__.py | 54 ++++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/implementations/python/preserves/__init__.py b/implementations/python/preserves/__init__.py index 83500b3..a7e6439 100644 --- a/implementations/python/preserves/__init__.py +++ b/implementations/python/preserves/__init__.py @@ -1,27 +1,59 @@ -''' -``` +'''``` import preserves ``` The main package re-exports a subset of the exports of its constituent modules: -(TODO: improve the presentation of this list) +- From [preserves.values][]: + - [Annotated][preserves.values.Annotated] + - [Embedded][preserves.values.Embedded] + - [Float][preserves.values.Float] + - [ImmutableDict][preserves.values.ImmutableDict] + - [Record][preserves.values.Record] + - [Symbol][preserves.values.Symbol] + - [annotate][preserves.values.annotate] + - [is_annotated][preserves.values.is_annotated] + - [preserve][preserves.values.preserve] + - [strip_annotations][preserves.values.strip_annotations] -- From [`values`](/values): `Float, Symbol, Record, ImmutableDict, Embedded, preserve, Annotated, is_annotated, strip_annotations, annotate` +- From [preserves.error][]: + - [DecodeError][preserves.error.DecodeError] + - [EncodeError][preserves.error.EncodeError] + - [ShortPacket][preserves.error.ShortPacket] -- From [`compare`](/compare): `cmp` +- From [preserves.binary][]: + - [Decoder][preserves.binary.Decoder] + - [Encoder][preserves.binary.Encoder] + - [canonicalize][preserves.binary.canonicalize] + - [decode][preserves.binary.decode] + - [decode_with_annotations][preserves.binary.decode_with_annotations] + - [encode][preserves.binary.encode] -- From [`error`](/error): `DecodeError, EncodeError, ShortPacket` +- From [preserves.text][]: + - [Formatter][preserves.text.Formatter] + - [Parser][preserves.text.Parser] + - [parse][preserves.text.parse] + - [parse_with_annotations][preserves.text.parse_with_annotations] + - [stringify][preserves.text.stringify] -- From [`binary`](/binary): `Decoder, Encoder, decode, decode_with_annotations, encode, canonicalize` +- From [preserves.compare][]: + - [cmp][preserves.compare.cmp] -- From [`text`](/text): `Parser, Formatter, parse, parse_with_annotations, stringify` +- From [preserves.merge][]: + - [merge][preserves.merge.merge] -- From [`merge`](/merge): `merge` +It also exports the [compare][preserves.compare] and [fold][preserves.fold] modules themselves, +permitting patterns like -- and submodules [`fold`](/fold) and [`compare`](/compare). +```python +>>> from preserves import * +>>> compare.cmp(123, 234) +-1 + +``` + +Finally, it provides a few utility aliases for common tasks: -In addition, it provides a few utility aliases for common tasks: ''' from .values import Float, Symbol, Record, ImmutableDict, Embedded, preserve From 2ab814333c0d5c93c1c2c3152a35c2ec790338d0 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 16 Mar 2023 21:19:42 +0100 Subject: [PATCH 05/68] Deploy docs --- implementations/python/Makefile | 5 + implementations/python/mkdocs.yml | 1 + python/404.html | 409 + python/api/index.html | 650 ++ python/assets/_mkdocstrings.css | 36 + python/assets/images/favicon.png | Bin 0 -> 1870 bytes .../assets/javascripts/bundle.efa0ade1.min.js | 29 + .../javascripts/bundle.efa0ade1.min.js.map | 8 + .../javascripts/lunr/min/lunr.ar.min.js | 1 + .../javascripts/lunr/min/lunr.da.min.js | 18 + .../javascripts/lunr/min/lunr.de.min.js | 18 + .../javascripts/lunr/min/lunr.du.min.js | 18 + .../javascripts/lunr/min/lunr.es.min.js | 18 + .../javascripts/lunr/min/lunr.fi.min.js | 18 + .../javascripts/lunr/min/lunr.fr.min.js | 18 + .../javascripts/lunr/min/lunr.hi.min.js | 1 + .../javascripts/lunr/min/lunr.hu.min.js | 18 + .../javascripts/lunr/min/lunr.it.min.js | 18 + .../javascripts/lunr/min/lunr.ja.min.js | 1 + .../javascripts/lunr/min/lunr.jp.min.js | 1 + .../javascripts/lunr/min/lunr.ko.min.js | 1 + .../javascripts/lunr/min/lunr.multi.min.js | 1 + .../javascripts/lunr/min/lunr.nl.min.js | 18 + .../javascripts/lunr/min/lunr.no.min.js | 18 + .../javascripts/lunr/min/lunr.pt.min.js | 18 + .../javascripts/lunr/min/lunr.ro.min.js | 18 + .../javascripts/lunr/min/lunr.ru.min.js | 18 + .../lunr/min/lunr.stemmer.support.min.js | 1 + .../javascripts/lunr/min/lunr.sv.min.js | 18 + .../javascripts/lunr/min/lunr.ta.min.js | 1 + .../javascripts/lunr/min/lunr.th.min.js | 1 + .../javascripts/lunr/min/lunr.tr.min.js | 18 + .../javascripts/lunr/min/lunr.vi.min.js | 1 + .../javascripts/lunr/min/lunr.zh.min.js | 1 + python/assets/javascripts/lunr/tinyseg.js | 206 + python/assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.208ed371.min.js | 42 + .../workers/search.208ed371.min.js.map | 8 + .../assets/stylesheets/main.c4a75a56.min.css | 1 + .../stylesheets/main.c4a75a56.min.css.map | 1 + .../stylesheets/palette.a0c5b2b5.min.css | 1 + .../stylesheets/palette.a0c5b2b5.min.css.map | 1 + python/binary/index.html | 1522 ++++ python/compare/index.html | 759 ++ python/error/index.html | 627 ++ python/fold/index.html | 580 ++ python/index.html | 541 ++ python/merge/index.html | 645 ++ python/objects.inv | Bin 0 -> 797 bytes python/path/index.html | 659 ++ python/schema/index.html | 1300 ++++ python/search/search_index.json | 1 + python/sitemap.xml | 58 + python/sitemap.xml.gz | Bin 0 -> 204 bytes python/text/index.html | 1330 ++++ python/values/index.html | 1693 +++++ 56 files changed, 18102 insertions(+) create mode 100644 python/404.html create mode 100644 python/api/index.html create mode 100644 python/assets/_mkdocstrings.css create mode 100644 python/assets/images/favicon.png create mode 100644 python/assets/javascripts/bundle.efa0ade1.min.js create mode 100644 python/assets/javascripts/bundle.efa0ade1.min.js.map create mode 100644 python/assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 python/assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 python/assets/javascripts/lunr/tinyseg.js create mode 100644 python/assets/javascripts/lunr/wordcut.js create mode 100644 python/assets/javascripts/workers/search.208ed371.min.js create mode 100644 python/assets/javascripts/workers/search.208ed371.min.js.map create mode 100644 python/assets/stylesheets/main.c4a75a56.min.css create mode 100644 python/assets/stylesheets/main.c4a75a56.min.css.map create mode 100644 python/assets/stylesheets/palette.a0c5b2b5.min.css create mode 100644 python/assets/stylesheets/palette.a0c5b2b5.min.css.map create mode 100644 python/binary/index.html create mode 100644 python/compare/index.html create mode 100644 python/error/index.html create mode 100644 python/fold/index.html create mode 100644 python/index.html create mode 100644 python/merge/index.html create mode 100644 python/objects.inv create mode 100644 python/path/index.html create mode 100644 python/schema/index.html create mode 100644 python/search/search_index.json create mode 100644 python/sitemap.xml create mode 100644 python/sitemap.xml.gz create mode 100644 python/text/index.html create mode 100644 python/values/index.html diff --git a/implementations/python/Makefile b/implementations/python/Makefile index 8015e72..10bb011 100644 --- a/implementations/python/Makefile +++ b/implementations/python/Makefile @@ -1,6 +1,11 @@ +all: test build-docs + test: update-test-data python3 -m unittest discover -s tests +build-docs: + mkdocs build + coverage: update-test-data python3-coverage run --branch -m unittest discover -s tests python3-coverage html diff --git a/implementations/python/mkdocs.yml b/implementations/python/mkdocs.yml index 8a7701b..c394d05 100644 --- a/implementations/python/mkdocs.yml +++ b/implementations/python/mkdocs.yml @@ -1,4 +1,5 @@ site_name: Preserves +site_dir: ../../python theme: name: material plugins: diff --git a/python/404.html b/python/404.html new file mode 100644 index 0000000..3933572 --- /dev/null +++ b/python/404.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/api/index.html b/python/api/index.html new file mode 100644 index 0000000..3481043 --- /dev/null +++ b/python/api/index.html @@ -0,0 +1,650 @@ + + + + + + + + + + + + + + + + + + + + + + The top-level preserves package - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

The top-level preserves package

+ + +
+ + + +
+ +
import preserves
+
+

The main package re-exports a subset of the exports of its constituent modules:

+ +

It also exports the compare and fold modules themselves, +permitting patterns like

+
>>> from preserves import *
+>>> compare.cmp(123, 234)
+-1
+
+

Finally, it provides a few utility aliases for common tasks:

+ + + +
+ + + + + + + +
+ + + +

+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.

+
+ +
+ + + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/assets/_mkdocstrings.css b/python/assets/_mkdocstrings.css new file mode 100644 index 0000000..a65078d --- /dev/null +++ b/python/assets/_mkdocstrings.css @@ -0,0 +1,36 @@ + +/* Don't capitalize names. */ +h5.doc-heading { + text-transform: none !important; +} + +/* Avoid breaking parameters name, etc. in table cells. */ +.doc-contents td code { + word-break: normal !important; +} + +/* For pieces of Markdown rendered in table cells. */ +.doc-contents td p { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +/* Max width for docstring sections tables. */ +.doc .md-typeset__table, +.doc .md-typeset__table table { + display: table !important; + width: 100%; +} +.doc .md-typeset__table tr { + display: table-row; +} + +/* Avoid line breaks in rendered fields. */ +.field-body p { + display: inline; +} + +/* Defaults in Spacy table style. */ +.doc-param-default { + float: right; +} diff --git a/python/assets/images/favicon.png b/python/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/python/assets/javascripts/bundle.efa0ade1.min.js b/python/assets/javascripts/bundle.efa0ade1.min.js new file mode 100644 index 0000000..215c901 --- /dev/null +++ b/python/assets/javascripts/bundle.efa0ade1.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Ri=Object.create;var gr=Object.defineProperty;var ki=Object.getOwnPropertyDescriptor;var Hi=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Pi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,on=Object.prototype.propertyIsEnumerable;var nn=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&nn(e,r,t[r]);if(kt)for(var r of kt(t))on.call(t,r)&&nn(e,r,t[r]);return e};var an=(e,t)=>{var r={};for(var n in e)yr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&on.call(e,n)&&(r[n]=e[n]);return r};var Ht=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var $i=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Hi(t))!yr.call(e,o)&&o!==r&&gr(e,o,{get:()=>t[o],enumerable:!(n=ki(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Ri(Pi(e)):{},$i(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var cn=Ht((xr,sn)=>{(function(e,t){typeof xr=="object"&&typeof sn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function c(T){var Qe=T.type,De=T.tagName;return!!(De==="INPUT"&&s[Qe]&&!T.readOnly||De==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function f(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(a(r.activeElement)&&f(r.activeElement),n=!0)}function m(T){n=!1}function d(T){a(T.target)&&(n||c(T.target))&&f(T.target)}function h(T){a(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),G())}function G(){document.addEventListener("mousemove",N),document.addEventListener("mousedown",N),document.addEventListener("mouseup",N),document.addEventListener("pointermove",N),document.addEventListener("pointerdown",N),document.addEventListener("pointerup",N),document.addEventListener("touchmove",N),document.addEventListener("touchstart",N),document.addEventListener("touchend",N)}function oe(){document.removeEventListener("mousemove",N),document.removeEventListener("mousedown",N),document.removeEventListener("mouseup",N),document.removeEventListener("pointermove",N),document.removeEventListener("pointerdown",N),document.removeEventListener("pointerup",N),document.removeEventListener("touchmove",N),document.removeEventListener("touchstart",N),document.removeEventListener("touchend",N)}function N(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,oe())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),G(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var fn=Ht(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(f){return!1}},r=t(),n=function(f){var u={next:function(){var p=f.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(f){return encodeURIComponent(f).replace(/%20/g,"+")},i=function(f){return decodeURIComponent(String(f).replace(/\+/g," "))},s=function(){var f=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof f){var d=this;p.forEach(function(oe,N){d.append(N,oe)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),f._entries&&(f._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(c,f){typeof c!="string"&&(c=String(c)),f&&typeof f!="string"&&(f=String(f));var u=document,p;if(f&&(e.location===void 0||f!==e.location.href)){f=f.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=f,u.head.appendChild(p);try{if(p.href.indexOf(f)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+f+" due to "+T)}}var m=u.createElement("a");m.href=c,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=c,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!f)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,G=!0,oe=this;["append","delete","set"].forEach(function(T){var Qe=h[T];h[T]=function(){Qe.apply(h,arguments),v&&(G=!1,oe.search=h.toString(),G=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var N=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==N&&(N=this.search,G&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},s=i.prototype,a=function(c){Object.defineProperty(s,c,{get:function(){return this._anchorElement[c]},set:function(f){this._anchorElement[c]=f},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(c){a(c)}),Object.defineProperty(s,"search",{get:function(){return this._anchorElement.search},set:function(c){this._anchorElement.search=c,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(s,{toString:{get:function(){var c=this;return function(){return c.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(c){this._anchorElement.href=c,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(c){this._anchorElement.pathname=c},enumerable:!0},origin:{get:function(){var c={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],f=this._anchorElement.port!=c&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(f?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(c){},enumerable:!0},username:{get:function(){return""},set:function(c){},enumerable:!0}}),i.createObjectURL=function(c){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(c){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var Kr=Ht((Mt,qr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Mt=="object"&&typeof qr=="object"?qr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Mt=="object"?Mt.ClipboardJS=r():t.ClipboardJS=r()})(Mt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return Ci}});var s=i(279),a=i.n(s),c=i(370),f=i.n(c),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var E=p()(O);return m("cut"),E},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",E=document.createElement("textarea");E.style.fontSize="12pt",E.style.border="0",E.style.padding="0",E.style.margin="0",E.style.position="absolute",E.style[O?"right":"left"]="-9999px";var H=window.pageYOffset||document.documentElement.scrollTop;return E.style.top="".concat(H,"px"),E.setAttribute("readonly",""),E.value=j,E}var G=function(O,E){var H=v(O);E.container.appendChild(H);var I=p()(H);return m("copy"),H.remove(),I},oe=function(O){var E=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},H="";return typeof O=="string"?H=G(O,E):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?H=G(O.value,E):(H=p()(O),m("copy")),H},N=oe;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(E){return typeof E}:T=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},T(j)}var Qe=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=O.action,H=E===void 0?"copy":E,I=O.container,q=O.target,Me=O.text;if(H!=="copy"&&H!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(H==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(H==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Me)return N(Me,{container:I});if(q)return H==="cut"?h(q):N(q,{container:I})},De=Qe;function $e(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?$e=function(E){return typeof E}:$e=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},$e(j)}function wi(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function rn(j,O){for(var E=0;E0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=$e(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var q=this;this.listener=f()(I,"click",function(Me){return q.onClick(Me)})}},{key:"onClick",value:function(I){var q=I.delegateTarget||I.currentTarget,Me=this.action(q)||"copy",Rt=De({action:Me,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Rt?"success":"error",{action:Me,text:Rt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(I){return vr("action",I)}},{key:"defaultTarget",value:function(I){var q=vr("target",I);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(I){return vr("text",I)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(I){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return N(I,q)}},{key:"cut",value:function(I){return h(I)}},{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof I=="string"?[I]:I,Me=!!document.queryCommandSupported;return q.forEach(function(Rt){Me=Me&&!!document.queryCommandSupported(Rt)}),Me}}]),E}(a()),Ci=Ai},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==o;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}n.exports=s},438:function(n,o,i){var s=i(828);function a(u,p,m,d,h){var v=f.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function c(u,p,m,d,h){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof m=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return a(v,p,m,d,h)}))}function f(u,p,m,d){return function(h){h.delegateTarget=s(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=c},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(n,o,i){var s=i(879),a=i(438);function c(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(h))throw new TypeError("Third argument must be a Function");if(s.node(m))return f(m,d,h);if(s.nodeList(m))return u(m,d,h);if(s.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function f(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return a(document.body,m,d,h)}n.exports=c},817:function(n){function o(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),f=document.createRange();f.selectNodeContents(i),c.removeAllRanges(),c.addRange(f),s=c.toString()}return s}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function f(){c.off(i,f),s.apply(a,arguments)}return f._=s,this.on(i,f,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,f=a.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var ns=/["'&<>]/;Go.exports=os;function os(e){var t=""+e,r=ns.exec(t);if(!r)return t;var n,o="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(f[0]===6||f[0]===2)){r=0;continue}if(f[0]===3&&(!i||f[1]>i[0]&&f[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],s;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(a){s={error:a}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||a(m,d)})})}function a(m,d){try{c(n[m](d))}catch(h){p(i[0][3],h)}}function c(m){m.value instanceof et?Promise.resolve(m.value.v).then(f,u):p(i[0][2],m)}function f(m){a("next",m)}function u(m){a("throw",m)}function p(m,d){m(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function ln(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Ee=="function"?Ee(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),o(a,c,s.done,s.value)})}}function o(i,s,a,c){Promise.resolve(c).then(function(f){i({value:f,done:a})},s)}}function A(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var $t=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Ee(s),c=a.next();!c.done;c=a.next()){var f=c.value;f.remove(this)}}catch(v){t={error:v}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var u=this.initialTeardown;if(A(u))try{u()}catch(v){i=v instanceof $t?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=Ee(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{mn(h)}catch(v){i=i!=null?i:[],v instanceof $t?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new $t(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)mn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Sr=Ie.EMPTY;function It(e){return e instanceof Ie||e&&"closed"in e&&A(e.remove)&&A(e.add)&&A(e.unsubscribe)}function mn(e){A(e)?e():e.unsubscribe()}var Le={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,s=o.isStopped,a=o.observers;return i||s?Sr:(this.currentObservers=null,a.push(r),new Ie(function(){n.currentObservers=null,Ve(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,s=n.isStopped;o?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,n){return new En(r,n)},t}(F);var En=function(e){ie(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Sr},t}(x);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ie(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,s=n._infiniteTimeWindow,a=n._timestampProvider,c=n._windowTime;o||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,s=o._buffer,a=s.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var s=r.actions;n!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Ut);var Tn=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Wt);var Te=new Tn(Sn);var _=new F(function(e){return e.complete()});function Dt(e){return e&&A(e.schedule)}function Cr(e){return e[e.length-1]}function Ye(e){return A(Cr(e))?e.pop():void 0}function Oe(e){return Dt(Cr(e))?e.pop():void 0}function Vt(e,t){return typeof Cr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function zt(e){return A(e==null?void 0:e.then)}function Nt(e){return A(e[ft])}function qt(e){return Symbol.asyncIterator&&A(e==null?void 0:e[Symbol.asyncIterator])}function Kt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ni(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qt=Ni();function Yt(e){return A(e==null?void 0:e[Qt])}function Gt(e){return pn(this,arguments,function(){var r,n,o,i;return Pt(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,et(r.read())];case 3:return n=s.sent(),o=n.value,i=n.done,i?[4,et(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,et(o)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return A(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(Nt(e))return qi(e);if(pt(e))return Ki(e);if(zt(e))return Qi(e);if(qt(e))return On(e);if(Yt(e))return Yi(e);if(Bt(e))return Gi(e)}throw Kt(e)}function qi(e){return new F(function(t){var r=e[ft]();if(A(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Ki(e){return new F(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?L(function(o,i){return e(o,i,n)}):de,ge(1),r?He(t):Vn(function(){return new Xt}))}}function zn(){for(var e=[],t=0;t=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(f){var u,p,m,d=0,h=!1,v=!1,G=function(){p==null||p.unsubscribe(),p=void 0},oe=function(){G(),u=m=void 0,h=v=!1},N=function(){var T=u;oe(),T==null||T.unsubscribe()};return y(function(T,Qe){d++,!v&&!h&&G();var De=m=m!=null?m:r();Qe.add(function(){d--,d===0&&!v&&!h&&(p=$r(N,c))}),De.subscribe(Qe),!u&&d>0&&(u=new rt({next:function($e){return De.next($e)},error:function($e){v=!0,G(),p=$r(oe,o,$e),De.error($e)},complete:function(){h=!0,G(),p=$r(oe,s),De.complete()}}),U(T).subscribe(u))})(f)}}function $r(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function z(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function er(e){return C(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),V(e===_e()),B())}function Xe(e){return{x:e.offsetLeft,y:e.offsetTop}}function Qn(e){return C(b(window,"load"),b(window,"resize")).pipe(Ce(0,Te),l(()=>Xe(e)),V(Xe(e)))}function tr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return C(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,Te),l(()=>tr(e)),V(tr(e)))}var Gn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!Dr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),ga?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!Dr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=va.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Bn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Xn=typeof WeakMap!="undefined"?new WeakMap:new Gn,Zn=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=ya.getInstance(),n=new Aa(t,r,this);Xn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Zn.prototype[e]=function(){var t;return(t=Xn.get(this))[e].apply(t,arguments)}});var Ca=function(){return typeof rr.ResizeObserver!="undefined"?rr.ResizeObserver:Zn}(),eo=Ca;var to=new x,Ra=$(()=>k(new eo(e=>{for(let t of e)to.next(t)}))).pipe(g(e=>C(ze,k(e)).pipe(R(()=>e.disconnect()))),J(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return Ra.pipe(S(t=>t.observe(e)),g(t=>to.pipe(L(({target:r})=>r===e),R(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ir(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var ro=new x,ka=$(()=>k(new IntersectionObserver(e=>{for(let t of e)ro.next(t)},{threshold:0}))).pipe(g(e=>C(ze,k(e)).pipe(R(()=>e.disconnect()))),J(1));function ar(e){return ka.pipe(S(t=>t.observe(e)),g(t=>ro.pipe(L(({target:r})=>r===e),R(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function no(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),B())}var sr={drawer:z("[data-md-toggle=drawer]"),search:z("[data-md-toggle=search]")};function oo(e){return sr[e].checked}function Ke(e,t){sr[e].checked!==t&&sr[e].click()}function Ue(e){let t=sr[e];return b(t,"change").pipe(l(()=>t.checked),V(t.checked))}function Ha(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Pa(){return C(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function io(){let e=b(window,"keydown").pipe(L(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:oo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),L(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!Ha(n,r)}return!0}),pe());return Pa().pipe(g(t=>t?_:e))}function le(){return new URL(location.href)}function ot(e){location.href=e.href}function ao(){return new x}function so(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)so(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)so(n,o);return n}function cr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function co(){return location.hash.substring(1)}function Vr(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function $a(){return b(window,"hashchange").pipe(l(co),V(co()),L(e=>e.length>0),J(1))}function fo(){return $a().pipe(l(e=>ce(`[id="${e}"]`)),L(e=>typeof e!="undefined"))}function zr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function uo(){let e=matchMedia("print");return C(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function Nr(e,t){return e.pipe(g(r=>r?t():_))}function fr(e,t={credentials:"same-origin"}){return ue(fetch(`${e}`,t)).pipe(fe(()=>_),g(r=>r.status!==200?Tt(()=>new Error(r.statusText)):k(r)))}function We(e,t){return fr(e,t).pipe(g(r=>r.json()),J(1))}function po(e,t){let r=new DOMParser;return fr(e,t).pipe(g(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function ur(e){let t=M("script",{src:e});return $(()=>(document.head.appendChild(t),C(b(t,"load"),b(t,"error").pipe(g(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),R(()=>document.head.removeChild(t)),ge(1))))}function lo(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function mo(){return C(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(lo),V(lo()))}function ho(){return{width:innerWidth,height:innerHeight}}function bo(){return b(window,"resize",{passive:!0}).pipe(l(ho),V(ho()))}function vo(){return Q([mo(),bo()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function pr(e,{viewport$:t,header$:r}){let n=t.pipe(Z("size")),o=Q([n,r]).pipe(l(()=>Xe(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:s,size:a},{x:c,y:f}])=>({offset:{x:s.x-c,y:s.y-f+i},size:a})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(s=>{let a=document.createElement("script");a.src=i,a.onload=s,document.body.appendChild(a)})),Promise.resolve())}var r=class extends EventTarget{constructor(n){super(),this.url=n,this.m=i=>{i.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:i.data})),this.onmessage&&this.onmessage(i))},this.e=(i,s,a,c,f)=>{if(s===`${this.url}`){let u=new ErrorEvent("error",{message:i,filename:s,lineno:a,colno:c,error:f});this.dispatchEvent(u),this.onerror&&this.onerror(u)}};let o=document.createElement("iframe");o.hidden=!0,document.body.appendChild(this.iframe=o),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Machine-oriented binary syntax

+ + +
+ + + +
+ +

The preserves.binary module implements the Preserves machine-oriented binary +syntax.

+

The main entry points are functions encode, +canonicalize, decode, and +decode_with_annotations.

+
>>> encode(Record(Symbol('hi'), []))
+b'\xb4\xb3\x02hi\x84'
+>>> decode(b'\xb4\xb3\x02hi\x84')
+#hi()
+
+ + + +
+ + + + + + + + +
+ + + +

+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:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
packet + bytes +

initial contents of the input buffer; may subsequently be extended by calling +extend.

+ b'' +
include_annotations + bool +

if True, wrap each value and subvalue in an +Annotated object.

+ False +
decode_embedded +

function accepting a Value and returning a possibly-decoded form of that value +suitable for placing into an Embedded object.

+ lambda x: x +
+

Normal usage is to supply a buffer, and keep calling next +until a ShortPacket exception is raised:

+
>>> d = Decoder(b'\xa0{\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 Values:

+
>>> d = Decoder(b'\xa0{\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'\xa0{\xb1\x05hello\x85\xb3\x01x\xb5\x84')
+>>> list(d)
+[123, 'hello', ()]
+
+
>>> for v in Decoder(b'\xa0{\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'\xa0{\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'\xa0{\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:

+ + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
packet + bytes +

buffered input waiting to be processed

index + int +

read position within packet

+ + +
+ Source code in preserves/binary.py +
127
+128
+129
+130
+131
+132
def __init__(self, packet=b'', include_annotations=False, decode_embedded=lambda x: x):
+    super(Decoder, self).__init__()
+    self.packet = packet
+    self.index = 0
+    self.include_annotations = include_annotations
+    self.decode_embedded = decode_embedded
+
+
+ + + +
+ + + + + + + + + +
+ + + +

+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
def extend(self, 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."""
+    self.packet = self.packet[self.index:] + data
+    self.index = 0
+
+
+
+ +
+ +
+ + + +

+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
def next(self):
+    """Reads the next complete `Value` from the internal buffer, raising
+    [ShortPacket][preserves.error.ShortPacket] if too few bytes are available, or
+    [DecodeError][preserves.error.DecodeError] if the input is invalid somehow.
+
+    """
+    tag = self.nextbyte()
+    if tag == 0x80: return self.wrap(False)
+    if tag == 0x81: return self.wrap(True)
+    if tag == 0x82: return self.wrap(Float.from_bytes(self.nextbytes(4)))
+    if tag == 0x83: return self.wrap(struct.unpack('>d', self.nextbytes(8))[0])
+    if tag == 0x84: raise DecodeError('Unexpected end-of-stream marker')
+    if tag == 0x85:
+        a = self.next()
+        v = self.next()
+        return self.unshift_annotation(a, v)
+    if tag == 0x86:
+        if self.decode_embedded is None:
+            raise DecodeError('No decode_embedded function supplied')
+        return self.wrap(Embedded(self.decode_embedded(self.next())))
+    if tag >= 0x90 and tag <= 0x9f: return self.wrap(tag - (0xa0 if tag > 0x9c else 0x90))
+    if tag >= 0xa0 and tag <= 0xaf: return self.wrap(self.nextint(tag - 0xa0 + 1))
+    if tag == 0xb0: return self.wrap(self.nextint(self.varint()))
+    if tag == 0xb1: return self.wrap(self.nextbytes(self.varint()).decode('utf-8'))
+    if tag == 0xb2: return self.wrap(self.nextbytes(self.varint()))
+    if tag == 0xb3: return self.wrap(Symbol(self.nextbytes(self.varint()).decode('utf-8')))
+    if tag == 0xb4:
+        vs = self.nextvalues()
+        if not vs: raise DecodeError('Too few elements in encoded record')
+        return self.wrap(Record(vs[0], vs[1:]))
+    if tag == 0xb5: return self.wrap(tuple(self.nextvalues()))
+    if tag == 0xb6: return self.wrap(frozenset(self.nextvalues()))
+    if tag == 0xb7: return self.wrap(ImmutableDict.from_kvs(self.nextvalues()))
+    raise DecodeError('Invalid tag: ' + hex(tag))
+
+
+
+ +
+ +
+ + + +

+try_next() + +

+ + +
+ +

Like next, but returns None instead of raising +ShortPacket.

+ +
+ Source code in preserves/binary.py +
224
+225
+226
+227
+228
+229
+230
+231
+232
def try_next(self):
+    """Like [next][preserves.binary.Decoder.next], but returns `None` instead of raising
+    [ShortPacket][preserves.error.ShortPacket]."""
+    start = self.index
+    try:
+        return self.next()
+    except ShortPacket:
+        self.index = start
+        return None
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+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'\xa0{\xb1\x05hello\x85\xb3\x01x\xb5\x84'
+
+ +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
encode_embedded +

function accepting an Embedded.embeddedValue and +returning a Value for serialization.

+ lambda x: x +
canonicalize + bool +

if True, ensures the serialized data are in canonical +form. This is slightly more work than +producing potentially-non-canonical output.

+ False +
include_annotations + bool | None +

if None, includes annotations in the output only when canonicalize is False, +because canonical serialization of values demands omission of +annotations. If explicitly True or +False, however, annotations will be included resp. excluded no matter the +canonicalize setting. This can be used to get canonical ordering +(canonicalize=True) and annotations (include_annotations=True).

+ None +
+ +

Attributes:

+ + + + + + + + + + + + + + + +
NameTypeDescription
buffer + bytearray +

accumulator for the output of the encoder

+ + +
+ Source code in preserves/binary.py +
294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
def __init__(self,
+             encode_embedded=lambda x: x,
+             canonicalize=False,
+             include_annotations=None):
+    super(Encoder, self).__init__()
+    self.buffer = bytearray()
+    self._encode_embedded = encode_embedded
+    self._canonicalize = canonicalize
+    if include_annotations is None:
+        self.include_annotations = not self._canonicalize
+    else:
+        self.include_annotations = include_annotations
+
+
+ + + +
+ + + + + + + + + +
+ + + +

+append(v) + +

+ + +
+ +

Extend self.buffer with an encoding of v.

+ +
+ Source code in preserves/binary.py +
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
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
def append(self, v):
+    """Extend `self.buffer` with an encoding of `v`."""
+    v = preserve(v)
+    if hasattr(v, '__preserve_write_binary__'):
+        v.__preserve_write_binary__(self)
+    elif v is False:
+        self.buffer.append(0x80)
+    elif v is True:
+        self.buffer.append(0x81)
+    elif isinstance(v, float):
+        self.buffer.append(0x83)
+        self.buffer.extend(struct.pack('>d', v))
+    elif isinstance(v, numbers.Number):
+        if v >= -3 and v <= 12:
+            self.buffer.append(0x90 + (v if v >= 0 else v + 16))
+        else:
+            self.encodeint(v)
+    elif isinstance(v, bytes):
+        self.encodebytes(2, v)
+    elif isinstance(v, basestring_):
+        self.encodebytes(1, v.encode('utf-8'))
+    elif isinstance(v, list):
+        self.encodevalues(5, v)
+    elif isinstance(v, tuple):
+        self.encodevalues(5, v)
+    elif isinstance(v, set):
+        self.encodeset(v)
+    elif isinstance(v, frozenset):
+        self.encodeset(v)
+    elif isinstance(v, dict):
+        self.encodedict(v)
+    else:
+        try:
+            i = iter(v)
+        except TypeError:
+            i = None
+        if i is None:
+            self.cannot_encode(v)
+        else:
+            self.encodevalues(5, i)
+
+
+
+ +
+ +
+ + + +

+contents() + +

+ + +
+ +

Returns a bytes constructed from the contents of self.buffer.

+ +
+ Source code in preserves/binary.py +
316
+317
+318
def contents(self):
+    """Returns a `bytes` constructed from the contents of `self.buffer`."""
+    return bytes(self.buffer)
+
+
+
+ +
+ +
+ + + +

+reset() + +

+ + +
+ +

Clears self.buffer to a fresh empty bytearray.

+ +
+ Source code in preserves/binary.py +
307
+308
+309
def reset(self):
+    """Clears `self.buffer` to a fresh empty `bytearray`."""
+    self.buffer = bytearray()
+
+
+
+ +
+ + + +
+ +
+ +
+ + +
+ + + +

+canonicalize(v, **kwargs) + +

+ + +
+ +

As encode, but sets canonicalize=True in the +Encoder constructor.

+ +
+ Source code in preserves/binary.py +
434
+435
+436
+437
+438
+439
def canonicalize(v, **kwargs):
+    """As [encode][preserves.binary.encode], but sets `canonicalize=True` in the
+    [Encoder][preserves.binary.Encoder] constructor.
+
+    """
+    return encode(v, canonicalize=True, **kwargs)
+
+
+
+ +
+ +
+ + + +

+decode(bs, **kwargs) + +

+ + +
+ +

Yields the first complete encoded value from bs, passing kwargs through to the +Decoder constructor. Raises exceptions as per +next.

+ +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
bs + bytes +

encoded data to decode

+ required +
+ +
+ Source code in preserves/binary.py +
243
+244
+245
+246
+247
+248
+249
+250
+251
+252
def decode(bs, **kwargs):
+    """Yields the first complete encoded value from `bs`, passing `kwargs` through to the
+    [Decoder][preserves.binary.Decoder] constructor. Raises exceptions as per
+    [next][preserves.binary.Decoder.next].
+
+    Args:
+        bs (bytes): encoded data to decode
+
+    """
+    return Decoder(packet=bs, **kwargs).next()
+
+
+
+ +
+ +
+ + + +

+decode_with_annotations(bs, **kwargs) + +

+ + +
+ +

Like decode, but supplying include_annotations=True to the +Decoder constructor.

+ +
+ Source code in preserves/binary.py +
254
+255
+256
+257
def decode_with_annotations(bs, **kwargs):
+    """Like [decode][preserves.binary.decode], but supplying `include_annotations=True` to the
+    [Decoder][preserves.binary.Decoder] constructor."""
+    return Decoder(packet=bs, include_annotations=True, **kwargs).next()
+
+
+
+ +
+ +
+ + + +

+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 +
427
+428
+429
+430
+431
+432
def encode(v, **kwargs):
+    """Encode a single `Value` `v` to a byte string. Any supplied `kwargs` are passed on to the
+    underlying [Encoder][preserves.binary.Encoder] constructor."""
+    e = Encoder(**kwargs)
+    e.append(v)
+    return e.contents()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/compare/index.html b/python/compare/index.html new file mode 100644 index 0000000..bf4fbbd --- /dev/null +++ b/python/compare/index.html @@ -0,0 +1,759 @@ + + + + + + + + + + + + + + + + + + + + + + Comparing Values - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Comparing Values

+ + +
+ + + +
+ +

TODO

+ + + +
+ + + + + + + + + +
+ + + +

+cmp(a, b) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/compare.py +
54
+55
+56
def cmp(a, b):
+    """TODO"""
+    return _cmp(preserve(a), preserve(b))
+
+
+
+ +
+ +
+ + + +

+eq(a, b) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/compare.py +
66
+67
+68
def eq(a, b):
+    """TODO"""
+    return _eq(preserve(a), preserve(b))
+
+
+
+ +
+ +
+ + + +

+le(a, b) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/compare.py +
62
+63
+64
def le(a, b):
+    """TODO"""
+    return cmp(a, b) <= 0
+
+
+
+ +
+ +
+ + + +

+lt(a, b) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/compare.py +
58
+59
+60
def lt(a, b):
+    """TODO"""
+    return cmp(a, b) < 0
+
+
+
+ +
+ +
+ + + +

+sorted(iterable, *, key=lambda x: x, reverse=False) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/compare.py +
74
+75
+76
def sorted(iterable, *, key=lambda x: x, reverse=False):
+    """TODO"""
+    return _sorted(iterable, key=lambda x: _key(key(x)), reverse=reverse)
+
+
+
+ +
+ +
+ + + +

+sorted_items(d) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/compare.py +
78
+79
+80
def sorted_items(d):
+    """TODO"""
+    return sorted(d.items(), key=_item_key)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/error/index.html b/python/error/index.html new file mode 100644 index 0000000..7c3887b --- /dev/null +++ b/python/error/index.html @@ -0,0 +1,627 @@ + + + + + + + + + + + + + + + + + + + + + + Codec errors - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Codec errors

+ + +
+ + + +
+ +

TODO

+ + + +
+ + + + + + + + +
+ + + +

+ DecodeError + + +

+ + +
+

+ Bases: ValueError

+ + +

TODO

+ + + +
+ +
+ +
+ + + +

+ EncodeError + + +

+ + +
+

+ Bases: ValueError

+ + +

TODO

+ + + +
+ +
+ +
+ + + +

+ ShortPacket + + +

+ + +
+

+ Bases: DecodeError

+ + +

TODO

+ + + +
+ +
+ + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/fold/index.html b/python/fold/index.html new file mode 100644 index 0000000..0873f51 --- /dev/null +++ b/python/fold/index.html @@ -0,0 +1,580 @@ + + + + + + + + + + + + + + + + + + + + + + Traversing values - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Traversing values

+ + +
+ + + +
+ +

TODO

+ + + +
+ + + + + + + + + +
+ + + +

+map_embeddeds(f, v) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/fold.py +
 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
def map_embeddeds(f, v):
+    """TODO"""
+    def walk(v):
+        if isinstance(v, Embedded):
+            return f(v.embeddedValue)
+        elif isinstance(v, (list, tuple)):
+            return tuple(walk(w) for w in v)
+        elif isinstance(v, (set, frozenset)):
+            return frozenset(walk(w) for w in v)
+        elif isinstance(v, dict):
+            return ImmutableDict.from_kvs(walk(w) for w in dict_kvs(v))
+        elif isinstance(v, Record):
+            return Record(walk(v.key), walk(v.fields))
+        else:
+            return v
+    return walk(v)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/index.html b/python/index.html new file mode 100644 index 0000000..0f91863 --- /dev/null +++ b/python/index.html @@ -0,0 +1,541 @@ + + + + + + + + + + + + + + + + + + + + Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Overview

+

This package implements Preserves for Python 3.x. It provides the +core semantics as well as both the human-readable text +syntax (a superset of JSON) and machine-oriented +binary format (including canonicalization) for +Preserves. It also implements Preserves Schema +and Preserves Path.

+ +

What is Preserves?

+

Preserves is a data model, with associated serialization formats.

+

It supports records with user-defined labels, embedded +references, and the usual suite of atomic and compound data types, +including binary data as a distinct type from text strings. Its +annotations allow separation of data from metadata such as comments, +trace information, and provenance information.

+

Preserves departs from many other data languages in defining how to +compare two values. Comparison is based on the data model, not on +syntax or on data structures of any particular implementation +language.

+

Mapping between Preserves values and Python values

+

Preserves Values are categorized in the following way:

+
                      Value = Atom
+                            | Compound
+                            | Embedded
+
+                       Atom = Boolean
+                            | Float
+                            | Double
+                            | SignedInteger
+                            | String
+                            | ByteString
+                            | Symbol
+
+                   Compound = Record
+                            | Sequence
+                            | Set
+                            | Dictionary
+
+

Python's strings, byte strings, integers, booleans, and double-precision floats stand directly +for their Preserves counterparts. Small wrapper classes for Float and Symbol complete the +suite of atomic types.

+

Python's lists and tuples correspond to Preserves Sequences, and dicts and sets to +Dictionary and Set values, respectively. Preserves Records are represented by a Record +class. Finally, embedded values are represented by a small Embedded wrapper class.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/merge/index.html b/python/merge/index.html new file mode 100644 index 0000000..0eb6914 --- /dev/null +++ b/python/merge/index.html @@ -0,0 +1,645 @@ + + + + + + + + + + + + + + + + + + + + + + Merging values - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Merging values

+ + +
+ + + +
+ +

TODO

+ + + +
+ + + + + + + + + +
+ + + +

+merge(v0, *vs, merge_embedded=None) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/merge.py +
 8
+ 9
+10
+11
+12
+13
def merge(v0, *vs, merge_embedded=None):
+    """TODO"""
+    v = v0
+    for vN in vs:
+        v = merge2(v, vN, merge_embedded=merge_embedded)
+    return v
+
+
+
+ +
+ +
+ + + +

+merge2(a, b, merge_embedded=None) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/merge.py +
22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
def merge2(a, b, merge_embedded=None):
+    """TODO"""
+    if a == b:
+        return a
+    if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
+        return merge_seq(a, b)
+    if isinstance(a, (set, frozenset)) and isinstance(b, (set, frozenset)):
+        _die()
+    if isinstance(a, dict) and isinstance(b, dict):
+        r = {}
+        for (ak, av) in a.items():
+            bv = b.get(ak, None)
+            r[ak] = av if bv is None else merge2(av, bv, merge_embedded=merge_embedded)
+        for (bk, bv) in b.items():
+            if bk not in r:
+                r[bk] = bv
+        return r
+    if isinstance(a, Record) and isinstance(b, Record):
+        return Record(merge2(a.key, b.key, merge_embedded=merge_embedded),
+                      merge_seq(a.fields, b.fields, merge_embedded=merge_embedded))
+    if isinstance(a, Embedded) and isinstance(b, Embedded):
+        m = (merge_embedded or merge_embedded_id)(a.embeddedValue, b.embeddedValue)
+        if m is None: _die()
+        return Embedded(m)
+    _die()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/objects.inv b/python/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..3e5feca08994719ce2cb59b818f2ed4ec787c67b GIT binary patch literal 797 zcmV+&1LFK6AX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkVa%FR6 za&~2N3L_v^WpZMd?av*PJAarPHb0B7EY-J#6 zb0A}HZE$jBb8}^6Aa!$TZf78RY-wUH3V7P3mraw?Fbsz8{EE)Nl`wGR2K|`X3v?)V zk76rZv;M+%%7$N$lQhs|oy0DerlY)%CE2pA7>f?=1>MFpAEjosz|H#&82;|{o6Xx- zW_Vq&TAEJ8z(ZUjG_Vy($radU z9?si_2Z z%E}T|vQw(10gIz5zS?G_1(jwp^a_AK^Yj8U^*&GUwDrjDVF>pqT^fnjYCD?O&JvYf zciw(Lh8?s(^p%`LoX#PKBhaV5@)wCp3pR?qHN-@P1Vd1k$QZKe%)}sx_HkTpoh2&! zC%zeUP5vQ8Z@`~NL9~XLs5n|=Vc&baxeR8-ClpA1zanc|*C-FqDaue?ZaNDwv7={{ zFiNX8#f4$IJlyzS)NVtOb1^>O7l9pCT=6d2RAjH}uvn1Ad;y8h0L5hD)IItQcYu%+ z%S6AHKf)s41r>x0?+KZ#X!;=0J0%@&Z}<@`8Fu8y}T3EWJBGD{xYeV!IvFAe1sE+qS+l-985%B(?=hHwp;=_6;c z@8M?~s*EPhLF`yu7Oyo`v$XS;t4Cfn3BUzJJ^2LbO>trIsSjs-45bl;K@^ja63I3r zlY{QBcYJb`hp?M{p1P>4mZWSvx}4A+q^v!Z0(YFIdmK@{=ch}S-*4d%wdoVm%R|HT zgrC82x(x;BZ9Vi0d0x9={%eFA_(4@VIgL{zOD&S+*WHFW0fsLOQndwM^SMjN b-EbVd_#jy1215?hZMlWh=NA40AiV1cV_0|H literal 0 HcmV?d00001 diff --git a/python/path/index.html b/python/path/index.html new file mode 100644 index 0000000..0a3f60e --- /dev/null +++ b/python/path/index.html @@ -0,0 +1,659 @@ + + + + + + + + + + + + + + + + + + + + + + Preserves Path - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Preserves Path

+ + +
+ + + +
+ +

TODO (document main behaviour)

+ + + +
+ + + + + + + +
+ + + +

+Predicate = syntax.Predicate + + + module-attribute + + +

+ + +
+ +

TODO

+
+ +
+ +
+ + + +

+Selector = syntax.Selector + + + module-attribute + + +

+ + +
+ +

TODO

+
+ +
+ +
+ + + +

+syntax = load_schema_file(pathlib.Path(__file__).parent / 'path.prb').path + + + module-attribute + + +

+ + +
+ +

TODO

+
+ +
+ + + +
+ + + +

+parse(s) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/path.py +
19
+20
+21
def parse(s):
+    """TODO"""
+    return parse_selector(Parser(s))
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/schema/index.html b/python/schema/index.html new file mode 100644 index 0000000..91b5303 --- /dev/null +++ b/python/schema/index.html @@ -0,0 +1,1300 @@ + + + + + + + + + + + + + + + + + + + + + + Preserves Schema - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Preserves Schema

+ + +
+ + + +
+ +

This is an implementation of Preserves Schema +for Python 3.

+

TODO

+ + + +
+ + + + + + + +
+ + + +

+meta = load_schema_file(__metaschema_filename).schema + + + module-attribute + + +

+ + +
+ +

TODO

+
+ +
+ + +
+ + + +

+Compiler() + +

+ + +
+ + +

TODO

+ + +
+ Source code in preserves/schema.py +
470
+471
def __init__(self):
+    self.root = Namespace(())
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+Definition(*args, **kwargs) + +

+ + +
+

+ Bases: SchemaObject

+ + +

TODO

+ + +
+ Source code in preserves/schema.py +
265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
def __init__(self, *args, **kwargs):
+    self._fields = args
+    if self.SIMPLE:
+        if self.EMPTY:
+            if len(args) != 0:
+                raise TypeError('%s takes no arguments' % (self._constructor_name(),))
+        else:
+            if len(args) != 1:
+                raise TypeError('%s needs exactly one argument' % (self._constructor_name(),))
+            self.value = args[0]
+    else:
+        i = 0
+        for arg in args:
+            if i >= len(self.FIELD_NAMES):
+                raise TypeError('%s given too many positional arguments' % (self._constructor_name(),))
+            setattr(self, self.SAFE_FIELD_NAMES[i], arg)
+            i = i + 1
+        for (argname, arg) in kwargs.items():
+            if hasattr(self, argname):
+                raise TypeError('%s given duplicate attribute: %r' % (self._constructor_name, argname))
+            if argname not in self.SAFE_FIELD_NAMES:
+                raise TypeError('%s given unknown attribute: %r' % (self._constructor_name, argname))
+            setattr(self, argname, arg)
+            i = i + 1
+        if i != len(self.FIELD_NAMES):
+            raise TypeError('%s needs argument(s) %r' % (self._constructor_name(), self.FIELD_NAMES))
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+Enumeration() + +

+ + +
+

+ Bases: SchemaObject

+ + +

TODO

+ + +
+ Source code in preserves/schema.py +
206
+207
def __init__(self):
+    raise TypeError('Cannot create instance of Enumeration')
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+Namespace(prefix) + +

+ + +
+ + +

TODO

+ + +
+ Source code in preserves/schema.py +
447
+448
def __init__(self, prefix):
+    self._prefix = prefix
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+SchemaDecodeFailed(cls, p, v, failures=None) + +

+ + +
+

+ Bases: ValueError

+ + +

TODO

+ + +
+ Source code in preserves/schema.py +
43
+44
+45
+46
+47
+48
def __init__(self, cls, p, v, failures=None):
+    super().__init__()
+    self.cls = cls
+    self.pattern = p
+    self.value = v
+    self.failures = [] if failures is None else failures
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ SchemaObject + + +

+ + +
+ + +

TODO

+ + + + + +
+ + + + + + + + + +
+ + + +

+__preserve__() + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/schema.py +
184
+185
+186
def __preserve__(self):
+    """TODO"""
+    raise NotImplementedError('Subclass responsibility')
+
+
+
+ +
+ +
+ + + +

+decode(v) + + + classmethod + + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/schema.py +
94
+95
+96
+97
@classmethod
+def decode(cls, v):
+    """TODO"""
+    raise NotImplementedError('Subclass responsibility')
+
+
+
+ +
+ +
+ + + +

+try_decode(v) + + + classmethod + + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/schema.py +
 99
+100
+101
+102
+103
+104
+105
@classmethod
+def try_decode(cls, v):
+    """TODO"""
+    try:
+        return cls.decode(v)
+    except SchemaDecodeFailed:
+        return None
+
+
+
+ +
+ + + +
+ +
+ +
+ + +
+ + + +

+encode(p, v) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/schema.py +
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
+384
+385
+386
+387
+388
+389
+390
def encode(p, v):
+    """TODO"""
+    if hasattr(v, '__escape_schema__'):
+        return preserve(v.__escape_schema__())
+    if p == ANY:
+        return v
+    if p.key == NAMED:
+        return encode(p[1], safegetattr(v, p[0].name))
+    if p.key == ATOM:
+        return v
+    if p.key == EMBEDDED:
+        return Embedded(v)
+    if p.key == LIT:
+        return p[0]
+    if p.key == SEQOF:
+        return tuple(encode(p[0], w) for w in v)
+    if p.key == SETOF:
+        return set(encode(p[0], w) for w in v)
+    if p.key == DICTOF:
+        return dict((encode(p[0], k), encode(p[1], w)) for (k, w) in v.items())
+    if p.key == REF:
+        return preserve(v)
+    if p.key == REC:
+        return Record(encode(p[0], v), encode(p[1], v))
+    if p.key == TUPLE:
+        return tuple(encode(pp, v) for pp in p[0])
+    if p.key == TUPLE_PREFIX:
+        return tuple(encode(pp, v) for pp in p[0]) + encode(p[1], v)
+    if p.key == DICT:
+        return dict((k, encode(pp, v)) for (k, pp) in p[0].items())
+    if p.key == AND:
+        return merge(*[encode(pp, v) for pp in p[0]])
+    raise ValueError(f'Bad schema {p}')
+
+
+
+ +
+ +
+ + + +

+extend(cls) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/schema.py +
510
+511
+512
+513
+514
+515
def extend(cls):
+    """TODO"""
+    def extender(f):
+        setattr(cls, f.__name__, f)
+        return f
+    return extender
+
+
+
+ +
+ +
+ + + +

+load_schema_file(filename) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/schema.py +
503
+504
+505
+506
+507
def load_schema_file(filename):
+    """TODO"""
+    c = Compiler()
+    c.load(filename)
+    return c.root
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/search/search_index.json b/python/search/search_index.json new file mode 100644 index 0000000..ca0bfbe --- /dev/null +++ b/python/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Overview","text":"

This package implements Preserves for Python 3.x. It provides the core semantics as well as both the human-readable text syntax (a superset of JSON) and machine-oriented binary format (including canonicalization) for Preserves. It also implements Preserves Schema and Preserves Path.

  • Main package API: preserves
"},{"location":"#what-is-preserves","title":"What is Preserves?","text":"

Preserves is a data model, with associated serialization formats.

It supports records with user-defined labels, embedded references, and the usual suite of atomic and compound data types, including binary data as a distinct type from text strings. Its annotations allow separation of data from metadata such as comments, trace information, and provenance information.

Preserves departs from many other data languages in defining how to compare two values. Comparison is based on the data model, not on syntax or on data structures of any particular implementation language.

"},{"location":"#mapping-between-preserves-values-and-python-values","title":"Mapping between Preserves values and Python values","text":"

Preserves Values are categorized in the following way:

                      Value = Atom\n                            | Compound\n                            | Embedded\n\n                       Atom = Boolean\n                            | Float\n                            | Double\n                            | SignedInteger\n                            | String\n                            | ByteString\n                            | Symbol\n\n                   Compound = Record\n                            | Sequence\n                            | Set\n                            | Dictionary\n

Python's strings, byte strings, integers, booleans, and double-precision floats stand directly for their Preserves counterparts. Small wrapper classes for Float and Symbol complete the suite of atomic types.

Python's lists and tuples correspond to Preserves Sequences, and dicts and sets to Dictionary and Set values, respectively. Preserves Records are represented by a Record class. Finally, embedded values are represented by a small Embedded wrapper class.

"},{"location":"api/","title":"The top-level preserves package","text":"
import preserves\n

The main package re-exports a subset of the exports of its constituent modules:

  • From preserves.values:

    • Annotated
    • Embedded
    • Float
    • ImmutableDict
    • Record
    • Symbol
    • annotate
    • is_annotated
    • preserve
    • strip_annotations
  • From preserves.error:

    • DecodeError
    • EncodeError
    • ShortPacket
  • From preserves.binary:

    • Decoder
    • Encoder
    • canonicalize
    • decode
    • decode_with_annotations
    • encode
  • From preserves.text:

    • Formatter
    • Parser
    • parse
    • parse_with_annotations
    • stringify
  • From preserves.compare:

    • cmp
  • From preserves.merge:

    • merge

It also exports the compare and fold modules themselves, permitting patterns like

>>> from preserves import *\n>>> compare.cmp(123, 234)\n-1\n

Finally, it provides a few utility aliases for common tasks:

"},{"location":"api/#preserves.dumps","title":"dumps = stringify module-attribute","text":"

This alias for stringify provides a familiar pythonesque name for converting a Preserves Value to a string.

"},{"location":"api/#preserves.loads","title":"loads = parse module-attribute","text":"

This alias for parse provides a familiar pythonesque name for converting a string to a Preserves Value.

"},{"location":"binary/","title":"Machine-oriented binary syntax","text":"

The preserves.binary module implements the Preserves machine-oriented binary syntax.

The main entry points are functions encode, canonicalize, decode, and decode_with_annotations.

>>> encode(Record(Symbol('hi'), []))\nb'\\xb4\\xb3\\x02hi\\x84'\n>>> decode(b'\\xb4\\xb3\\x02hi\\x84')\n#hi()\n
"},{"location":"binary/#preserves.binary.Decoder","title":"Decoder(packet=b'', include_annotations=False, decode_embedded=lambda x: x)","text":"

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 True, wrap each value and subvalue in an Annotated object.

False decode_embedded

function accepting a Value and returning a possibly-decoded form of that value suitable for placing into an Embedded object.

lambda x: x

Normal usage is to supply a buffer, and keep calling next until a ShortPacket exception is raised:

>>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84')\n>>> d.next()\n123\n>>> d.next()\n'hello'\n>>> d.next()\n()\n>>> d.next()\nTraceback (most recent call last):\n  ...\npreserves.error.ShortPacket: Short packet\n

Alternatively, keep calling try_next until it yields None, which is not in the domain of Preserves Values:

>>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84')\n>>> d.try_next()\n123\n>>> d.try_next()\n'hello'\n>>> d.try_next()\n()\n>>> d.try_next()\n

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'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84')\n>>> list(d)\n[123, 'hello', ()]\n
>>> for v in Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84'):\n...     print(repr(v))\n123\n'hello'\n()\n

Supply include_annotations=True to read annotations alongside the annotated values:

>>> d = Decoder(b'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84', include_annotations=True)\n>>> list(d)\n[123, 'hello', @#x ()]\n

If you are incrementally reading from, say, a socket, you can use extend to add new input as if comes available:

>>> d = Decoder(b'\\xa0{\\xb1\\x05he')\n>>> d.try_next()\n123\n>>> d.try_next() # returns None because the input is incomplete\n>>> d.extend(b'llo')\n>>> d.try_next()\n'hello'\n>>> d.try_next()\n

Attributes:

Name Type Description packet bytes

buffered input waiting to be processed

index int

read position within packet

Source code in preserves/binary.py
def __init__(self, packet=b'', include_annotations=False, decode_embedded=lambda x: x):\n    super(Decoder, self).__init__()\n    self.packet = packet\n    self.index = 0\n    self.include_annotations = include_annotations\n    self.decode_embedded = decode_embedded\n
"},{"location":"binary/#preserves.binary.Decoder.extend","title":"extend(data)","text":"

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
def extend(self, data):\n\"\"\"Appends `data` to the remaining bytes in `self.packet`, trimming already-processed\n    bytes from the front of `self.packet` and resetting `self.index` to zero.\"\"\"\n    self.packet = self.packet[self.index:] + data\n    self.index = 0\n
"},{"location":"binary/#preserves.binary.Decoder.next","title":"next()","text":"

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
def next(self):\n\"\"\"Reads the next complete `Value` from the internal buffer, raising\n    [ShortPacket][preserves.error.ShortPacket] if too few bytes are available, or\n    [DecodeError][preserves.error.DecodeError] if the input is invalid somehow.\n\n    \"\"\"\n    tag = self.nextbyte()\n    if tag == 0x80: return self.wrap(False)\n    if tag == 0x81: return self.wrap(True)\n    if tag == 0x82: return self.wrap(Float.from_bytes(self.nextbytes(4)))\n    if tag == 0x83: return self.wrap(struct.unpack('>d', self.nextbytes(8))[0])\n    if tag == 0x84: raise DecodeError('Unexpected end-of-stream marker')\n    if tag == 0x85:\n        a = self.next()\n        v = self.next()\n        return self.unshift_annotation(a, v)\n    if tag == 0x86:\n        if self.decode_embedded is None:\n            raise DecodeError('No decode_embedded function supplied')\n        return self.wrap(Embedded(self.decode_embedded(self.next())))\n    if tag >= 0x90 and tag <= 0x9f: return self.wrap(tag - (0xa0 if tag > 0x9c else 0x90))\n    if tag >= 0xa0 and tag <= 0xaf: return self.wrap(self.nextint(tag - 0xa0 + 1))\n    if tag == 0xb0: return self.wrap(self.nextint(self.varint()))\n    if tag == 0xb1: return self.wrap(self.nextbytes(self.varint()).decode('utf-8'))\n    if tag == 0xb2: return self.wrap(self.nextbytes(self.varint()))\n    if tag == 0xb3: return self.wrap(Symbol(self.nextbytes(self.varint()).decode('utf-8')))\n    if tag == 0xb4:\n        vs = self.nextvalues()\n        if not vs: raise DecodeError('Too few elements in encoded record')\n        return self.wrap(Record(vs[0], vs[1:]))\n    if tag == 0xb5: return self.wrap(tuple(self.nextvalues()))\n    if tag == 0xb6: return self.wrap(frozenset(self.nextvalues()))\n    if tag == 0xb7: return self.wrap(ImmutableDict.from_kvs(self.nextvalues()))\n    raise DecodeError('Invalid tag: ' + hex(tag))\n
"},{"location":"binary/#preserves.binary.Decoder.try_next","title":"try_next()","text":"

Like next, but returns None instead of raising ShortPacket.

Source code in preserves/binary.py
def try_next(self):\n\"\"\"Like [next][preserves.binary.Decoder.next], but returns `None` instead of raising\n    [ShortPacket][preserves.error.ShortPacket].\"\"\"\n    start = self.index\n    try:\n        return self.next()\n    except ShortPacket:\n        self.index = start\n        return None\n
"},{"location":"binary/#preserves.binary.Encoder","title":"Encoder(encode_embedded=lambda x: x, canonicalize=False, include_annotations=None)","text":"

Bases: BinaryCodec

Implementation of an encoder for the machine-oriented binary Preserves syntax.

>>> e = Encoder()\n>>> e.append(123)\n>>> e.append('hello')\n>>> e.append(annotate([], Symbol('x')))\n>>> e.contents()\nb'\\xa0{\\xb1\\x05hello\\x85\\xb3\\x01x\\xb5\\x84'\n

Parameters:

Name Type Description Default encode_embedded

function accepting an Embedded.embeddedValue and returning a Value for serialization.

lambda x: x canonicalize bool

if True, ensures the serialized data are in canonical form. This is slightly more work than producing potentially-non-canonical output.

False include_annotations bool | None

if None, includes annotations in the output only when canonicalize is False, because canonical serialization of values demands omission of annotations. If explicitly True or False, however, annotations will be included resp. excluded no matter the canonicalize setting. This can be used to get canonical ordering (canonicalize=True) and annotations (include_annotations=True).

None

Attributes:

Name Type Description buffer bytearray

accumulator for the output of the encoder

Source code in preserves/binary.py
def __init__(self,\n             encode_embedded=lambda x: x,\n             canonicalize=False,\n             include_annotations=None):\n    super(Encoder, self).__init__()\n    self.buffer = bytearray()\n    self._encode_embedded = encode_embedded\n    self._canonicalize = canonicalize\n    if include_annotations is None:\n        self.include_annotations = not self._canonicalize\n    else:\n        self.include_annotations = include_annotations\n
"},{"location":"binary/#preserves.binary.Encoder.append","title":"append(v)","text":"

Extend self.buffer with an encoding of v.

Source code in preserves/binary.py
def append(self, v):\n\"\"\"Extend `self.buffer` with an encoding of `v`.\"\"\"\n    v = preserve(v)\n    if hasattr(v, '__preserve_write_binary__'):\n        v.__preserve_write_binary__(self)\n    elif v is False:\n        self.buffer.append(0x80)\n    elif v is True:\n        self.buffer.append(0x81)\n    elif isinstance(v, float):\n        self.buffer.append(0x83)\n        self.buffer.extend(struct.pack('>d', v))\n    elif isinstance(v, numbers.Number):\n        if v >= -3 and v <= 12:\n            self.buffer.append(0x90 + (v if v >= 0 else v + 16))\n        else:\n            self.encodeint(v)\n    elif isinstance(v, bytes):\n        self.encodebytes(2, v)\n    elif isinstance(v, basestring_):\n        self.encodebytes(1, v.encode('utf-8'))\n    elif isinstance(v, list):\n        self.encodevalues(5, v)\n    elif isinstance(v, tuple):\n        self.encodevalues(5, v)\n    elif isinstance(v, set):\n        self.encodeset(v)\n    elif isinstance(v, frozenset):\n        self.encodeset(v)\n    elif isinstance(v, dict):\n        self.encodedict(v)\n    else:\n        try:\n            i = iter(v)\n        except TypeError:\n            i = None\n        if i is None:\n            self.cannot_encode(v)\n        else:\n            self.encodevalues(5, i)\n
"},{"location":"binary/#preserves.binary.Encoder.contents","title":"contents()","text":"

Returns a bytes constructed from the contents of self.buffer.

Source code in preserves/binary.py
def contents(self):\n\"\"\"Returns a `bytes` constructed from the contents of `self.buffer`.\"\"\"\n    return bytes(self.buffer)\n
"},{"location":"binary/#preserves.binary.Encoder.reset","title":"reset()","text":"

Clears self.buffer to a fresh empty bytearray.

Source code in preserves/binary.py
def reset(self):\n\"\"\"Clears `self.buffer` to a fresh empty `bytearray`.\"\"\"\n    self.buffer = bytearray()\n
"},{"location":"binary/#preserves.binary.canonicalize","title":"canonicalize(v, **kwargs)","text":"

As encode, but sets canonicalize=True in the Encoder constructor.

Source code in preserves/binary.py
def canonicalize(v, **kwargs):\n\"\"\"As [encode][preserves.binary.encode], but sets `canonicalize=True` in the\n    [Encoder][preserves.binary.Encoder] constructor.\n\n    \"\"\"\n    return encode(v, canonicalize=True, **kwargs)\n
"},{"location":"binary/#preserves.binary.decode","title":"decode(bs, **kwargs)","text":"

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
def decode(bs, **kwargs):\n\"\"\"Yields the first complete encoded value from `bs`, passing `kwargs` through to the\n    [Decoder][preserves.binary.Decoder] constructor. Raises exceptions as per\n    [next][preserves.binary.Decoder.next].\n\n    Args:\n        bs (bytes): encoded data to decode\n\n    \"\"\"\n    return Decoder(packet=bs, **kwargs).next()\n
"},{"location":"binary/#preserves.binary.decode_with_annotations","title":"decode_with_annotations(bs, **kwargs)","text":"

Like decode, but supplying include_annotations=True to the Decoder constructor.

Source code in preserves/binary.py
def decode_with_annotations(bs, **kwargs):\n\"\"\"Like [decode][preserves.binary.decode], but supplying `include_annotations=True` to the\n    [Decoder][preserves.binary.Decoder] constructor.\"\"\"\n    return Decoder(packet=bs, include_annotations=True, **kwargs).next()\n
"},{"location":"binary/#preserves.binary.encode","title":"encode(v, **kwargs)","text":"

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
def encode(v, **kwargs):\n\"\"\"Encode a single `Value` `v` to a byte string. Any supplied `kwargs` are passed on to the\n    underlying [Encoder][preserves.binary.Encoder] constructor.\"\"\"\n    e = Encoder(**kwargs)\n    e.append(v)\n    return e.contents()\n
"},{"location":"compare/","title":"Comparing Values","text":"

TODO

"},{"location":"compare/#preserves.compare.cmp","title":"cmp(a, b)","text":"

TODO

Source code in preserves/compare.py
def cmp(a, b):\n\"\"\"TODO\"\"\"\n    return _cmp(preserve(a), preserve(b))\n
"},{"location":"compare/#preserves.compare.eq","title":"eq(a, b)","text":"

TODO

Source code in preserves/compare.py
def eq(a, b):\n\"\"\"TODO\"\"\"\n    return _eq(preserve(a), preserve(b))\n
"},{"location":"compare/#preserves.compare.le","title":"le(a, b)","text":"

TODO

Source code in preserves/compare.py
def le(a, b):\n\"\"\"TODO\"\"\"\n    return cmp(a, b) <= 0\n
"},{"location":"compare/#preserves.compare.lt","title":"lt(a, b)","text":"

TODO

Source code in preserves/compare.py
def lt(a, b):\n\"\"\"TODO\"\"\"\n    return cmp(a, b) < 0\n
"},{"location":"compare/#preserves.compare.sorted","title":"sorted(iterable, *, key=lambda x: x, reverse=False)","text":"

TODO

Source code in preserves/compare.py
def sorted(iterable, *, key=lambda x: x, reverse=False):\n\"\"\"TODO\"\"\"\n    return _sorted(iterable, key=lambda x: _key(key(x)), reverse=reverse)\n
"},{"location":"compare/#preserves.compare.sorted_items","title":"sorted_items(d)","text":"

TODO

Source code in preserves/compare.py
def sorted_items(d):\n\"\"\"TODO\"\"\"\n    return sorted(d.items(), key=_item_key)\n
"},{"location":"error/","title":"Codec errors","text":"

TODO

"},{"location":"error/#preserves.error.DecodeError","title":"DecodeError","text":"

Bases: ValueError

TODO

"},{"location":"error/#preserves.error.EncodeError","title":"EncodeError","text":"

Bases: ValueError

TODO

"},{"location":"error/#preserves.error.ShortPacket","title":"ShortPacket","text":"

Bases: DecodeError

TODO

"},{"location":"fold/","title":"Traversing values","text":"

TODO

"},{"location":"fold/#preserves.fold.map_embeddeds","title":"map_embeddeds(f, v)","text":"

TODO

Source code in preserves/fold.py
def map_embeddeds(f, v):\n\"\"\"TODO\"\"\"\n    def walk(v):\n        if isinstance(v, Embedded):\n            return f(v.embeddedValue)\n        elif isinstance(v, (list, tuple)):\n            return tuple(walk(w) for w in v)\n        elif isinstance(v, (set, frozenset)):\n            return frozenset(walk(w) for w in v)\n        elif isinstance(v, dict):\n            return ImmutableDict.from_kvs(walk(w) for w in dict_kvs(v))\n        elif isinstance(v, Record):\n            return Record(walk(v.key), walk(v.fields))\n        else:\n            return v\n    return walk(v)\n
"},{"location":"merge/","title":"Merging values","text":"

TODO

"},{"location":"merge/#preserves.merge.merge","title":"merge(v0, *vs, merge_embedded=None)","text":"

TODO

Source code in preserves/merge.py
def merge(v0, *vs, merge_embedded=None):\n\"\"\"TODO\"\"\"\n    v = v0\n    for vN in vs:\n        v = merge2(v, vN, merge_embedded=merge_embedded)\n    return v\n
"},{"location":"merge/#preserves.merge.merge2","title":"merge2(a, b, merge_embedded=None)","text":"

TODO

Source code in preserves/merge.py
def merge2(a, b, merge_embedded=None):\n\"\"\"TODO\"\"\"\n    if a == b:\n        return a\n    if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):\n        return merge_seq(a, b)\n    if isinstance(a, (set, frozenset)) and isinstance(b, (set, frozenset)):\n        _die()\n    if isinstance(a, dict) and isinstance(b, dict):\n        r = {}\n        for (ak, av) in a.items():\n            bv = b.get(ak, None)\n            r[ak] = av if bv is None else merge2(av, bv, merge_embedded=merge_embedded)\n        for (bk, bv) in b.items():\n            if bk not in r:\n                r[bk] = bv\n        return r\n    if isinstance(a, Record) and isinstance(b, Record):\n        return Record(merge2(a.key, b.key, merge_embedded=merge_embedded),\n                      merge_seq(a.fields, b.fields, merge_embedded=merge_embedded))\n    if isinstance(a, Embedded) and isinstance(b, Embedded):\n        m = (merge_embedded or merge_embedded_id)(a.embeddedValue, b.embeddedValue)\n        if m is None: _die()\n        return Embedded(m)\n    _die()\n
"},{"location":"path/","title":"Preserves Path","text":"

TODO (document main behaviour)

"},{"location":"path/#preserves.path.Predicate","title":"Predicate = syntax.Predicate module-attribute","text":"

TODO

"},{"location":"path/#preserves.path.Selector","title":"Selector = syntax.Selector module-attribute","text":"

TODO

"},{"location":"path/#preserves.path.syntax","title":"syntax = load_schema_file(pathlib.Path(__file__).parent / 'path.prb').path module-attribute","text":"

TODO

"},{"location":"path/#preserves.path.parse","title":"parse(s)","text":"

TODO

Source code in preserves/path.py
def parse(s):\n\"\"\"TODO\"\"\"\n    return parse_selector(Parser(s))\n
"},{"location":"schema/","title":"Preserves Schema","text":"

This is an implementation of Preserves Schema for Python 3.

TODO

"},{"location":"schema/#preserves.schema.meta","title":"meta = load_schema_file(__metaschema_filename).schema module-attribute","text":"

TODO

"},{"location":"schema/#preserves.schema.Compiler","title":"Compiler()","text":"

TODO

Source code in preserves/schema.py
def __init__(self):\n    self.root = Namespace(())\n
"},{"location":"schema/#preserves.schema.Definition","title":"Definition(*args, **kwargs)","text":"

Bases: SchemaObject

TODO

Source code in preserves/schema.py
def __init__(self, *args, **kwargs):\n    self._fields = args\n    if self.SIMPLE:\n        if self.EMPTY:\n            if len(args) != 0:\n                raise TypeError('%s takes no arguments' % (self._constructor_name(),))\n        else:\n            if len(args) != 1:\n                raise TypeError('%s needs exactly one argument' % (self._constructor_name(),))\n            self.value = args[0]\n    else:\n        i = 0\n        for arg in args:\n            if i >= len(self.FIELD_NAMES):\n                raise TypeError('%s given too many positional arguments' % (self._constructor_name(),))\n            setattr(self, self.SAFE_FIELD_NAMES[i], arg)\n            i = i + 1\n        for (argname, arg) in kwargs.items():\n            if hasattr(self, argname):\n                raise TypeError('%s given duplicate attribute: %r' % (self._constructor_name, argname))\n            if argname not in self.SAFE_FIELD_NAMES:\n                raise TypeError('%s given unknown attribute: %r' % (self._constructor_name, argname))\n            setattr(self, argname, arg)\n            i = i + 1\n        if i != len(self.FIELD_NAMES):\n            raise TypeError('%s needs argument(s) %r' % (self._constructor_name(), self.FIELD_NAMES))\n
"},{"location":"schema/#preserves.schema.Enumeration","title":"Enumeration()","text":"

Bases: SchemaObject

TODO

Source code in preserves/schema.py
def __init__(self):\n    raise TypeError('Cannot create instance of Enumeration')\n
"},{"location":"schema/#preserves.schema.Namespace","title":"Namespace(prefix)","text":"

TODO

Source code in preserves/schema.py
def __init__(self, prefix):\n    self._prefix = prefix\n
"},{"location":"schema/#preserves.schema.SchemaDecodeFailed","title":"SchemaDecodeFailed(cls, p, v, failures=None)","text":"

Bases: ValueError

TODO

Source code in preserves/schema.py
def __init__(self, cls, p, v, failures=None):\n    super().__init__()\n    self.cls = cls\n    self.pattern = p\n    self.value = v\n    self.failures = [] if failures is None else failures\n
"},{"location":"schema/#preserves.schema.SchemaObject","title":"SchemaObject","text":"

TODO

"},{"location":"schema/#preserves.schema.SchemaObject.__preserve__","title":"__preserve__()","text":"

TODO

Source code in preserves/schema.py
def __preserve__(self):\n\"\"\"TODO\"\"\"\n    raise NotImplementedError('Subclass responsibility')\n
"},{"location":"schema/#preserves.schema.SchemaObject.decode","title":"decode(v) classmethod","text":"

TODO

Source code in preserves/schema.py
@classmethod\ndef decode(cls, v):\n\"\"\"TODO\"\"\"\n    raise NotImplementedError('Subclass responsibility')\n
"},{"location":"schema/#preserves.schema.SchemaObject.try_decode","title":"try_decode(v) classmethod","text":"

TODO

Source code in preserves/schema.py
@classmethod\ndef try_decode(cls, v):\n\"\"\"TODO\"\"\"\n    try:\n        return cls.decode(v)\n    except SchemaDecodeFailed:\n        return None\n
"},{"location":"schema/#preserves.schema.encode","title":"encode(p, v)","text":"

TODO

Source code in preserves/schema.py
def encode(p, v):\n\"\"\"TODO\"\"\"\n    if hasattr(v, '__escape_schema__'):\n        return preserve(v.__escape_schema__())\n    if p == ANY:\n        return v\n    if p.key == NAMED:\n        return encode(p[1], safegetattr(v, p[0].name))\n    if p.key == ATOM:\n        return v\n    if p.key == EMBEDDED:\n        return Embedded(v)\n    if p.key == LIT:\n        return p[0]\n    if p.key == SEQOF:\n        return tuple(encode(p[0], w) for w in v)\n    if p.key == SETOF:\n        return set(encode(p[0], w) for w in v)\n    if p.key == DICTOF:\n        return dict((encode(p[0], k), encode(p[1], w)) for (k, w) in v.items())\n    if p.key == REF:\n        return preserve(v)\n    if p.key == REC:\n        return Record(encode(p[0], v), encode(p[1], v))\n    if p.key == TUPLE:\n        return tuple(encode(pp, v) for pp in p[0])\n    if p.key == TUPLE_PREFIX:\n        return tuple(encode(pp, v) for pp in p[0]) + encode(p[1], v)\n    if p.key == DICT:\n        return dict((k, encode(pp, v)) for (k, pp) in p[0].items())\n    if p.key == AND:\n        return merge(*[encode(pp, v) for pp in p[0]])\n    raise ValueError(f'Bad schema {p}')\n
"},{"location":"schema/#preserves.schema.extend","title":"extend(cls)","text":"

TODO

Source code in preserves/schema.py
def extend(cls):\n\"\"\"TODO\"\"\"\n    def extender(f):\n        setattr(cls, f.__name__, f)\n        return f\n    return extender\n
"},{"location":"schema/#preserves.schema.load_schema_file","title":"load_schema_file(filename)","text":"

TODO

Source code in preserves/schema.py
def load_schema_file(filename):\n\"\"\"TODO\"\"\"\n    c = Compiler()\n    c.load(filename)\n    return c.root\n
"},{"location":"text/","title":"Human-readable text syntax","text":"

TODO

"},{"location":"text/#preserves.text.Formatter","title":"Formatter(format_embedded=lambda x: x, indent=None, with_commas=False, trailing_comma=False, include_annotations=True)","text":"

Bases: TextCodec

TODO

TODO

Source code in preserves/text.py
def __init__(self,\n             format_embedded=lambda x: x,\n             indent=None,\n             with_commas=False,\n             trailing_comma=False,\n             include_annotations=True):\n\"\"\"TODO\"\"\"\n    super(Formatter, self).__init__()\n    self.indent_delta = 0 if indent is None else indent\n    self.indent_distance = 0\n    self.with_commas = with_commas\n    self.trailing_comma = trailing_comma\n    self.chunks = []\n    self._format_embedded = format_embedded\n    self.include_annotations = include_annotations\n
"},{"location":"text/#preserves.text.Formatter.append","title":"append(v)","text":"

TODO

Source code in preserves/text.py
def append(self, v):\n\"\"\"TODO\"\"\"\n    v = preserve(v)\n    if hasattr(v, '__preserve_write_text__'):\n        v.__preserve_write_text__(self)\n    elif v is False:\n        self.chunks.append('#f')\n    elif v is True:\n        self.chunks.append('#t')\n    elif isinstance(v, float):\n        if math.isnan(v) or math.isinf(v):\n            self.chunks.append('#xd\"' + struct.pack('>d', v).hex() + '\"')\n        else:\n            self.chunks.append(repr(v))\n    elif isinstance(v, numbers.Number):\n        self.chunks.append('%d' % (v,))\n    elif isinstance(v, bytes):\n        self.chunks.append('#[%s]' % (base64.b64encode(v).decode('ascii'),))\n    elif isinstance(v, basestring_):\n        self.chunks.append('\"')\n        for c in v:\n            if c == '\"': self.chunks.append('\\\\\"')\n            else: self.write_stringlike_char(c)\n        self.chunks.append('\"')\n    elif isinstance(v, list):\n        self.write_seq('[', ']', v, self.append)\n    elif isinstance(v, tuple):\n        self.write_seq('[', ']', v, self.append)\n    elif isinstance(v, set):\n        self.write_seq('#{', '}', v, self.append)\n    elif isinstance(v, frozenset):\n        self.write_seq('#{', '}', v, self.append)\n    elif isinstance(v, dict):\n        def append_kv(kv):\n            self.append(kv[0])\n            self.chunks.append(': ')\n            self.append(kv[1])\n        self.write_seq('{', '}', v.items(), append_kv)\n    else:\n        try:\n            i = iter(v)\n        except TypeError:\n            i = None\n        if i is None:\n            self.cannot_format(v)\n        else:\n            self.write_seq('[', ']', i, self.append)\n
"},{"location":"text/#preserves.text.Formatter.contents","title":"contents()","text":"

TODO

Source code in preserves/text.py
def contents(self):\n\"\"\"TODO\"\"\"\n    return u''.join(self.chunks)\n
"},{"location":"text/#preserves.text.Parser","title":"Parser(input_buffer='', include_annotations=False, parse_embedded=lambda x: x)","text":"

Bases: TextCodec

TODO

TODO

Source code in preserves/text.py
def __init__(self, input_buffer=u'', include_annotations=False, parse_embedded=lambda x: x):\n\"\"\"TODO\"\"\"\n    super(Parser, self).__init__()\n    self.input_buffer = input_buffer\n    self.index = 0\n    self.include_annotations = include_annotations\n    self.parse_embedded = parse_embedded\n
"},{"location":"text/#preserves.text.Parser.__iter__","title":"__iter__()","text":"

TODO

Source code in preserves/text.py
def __iter__(self):\n\"\"\"TODO\"\"\"\n    return self\n
"},{"location":"text/#preserves.text.Parser.extend","title":"extend(text)","text":"

TODO

Source code in preserves/text.py
def extend(self, text):\n\"\"\"TODO\"\"\"\n    self.input_buffer = self.input_buffer[self.index:] + text\n    self.index = 0\n
"},{"location":"text/#preserves.text.Parser.next","title":"next()","text":"

TODO

Source code in preserves/text.py
def next(self):\n\"\"\"TODO\"\"\"\n    self.skip_whitespace()\n    c = self.peek()\n    if c == '\"':\n        self.skip()\n        return self.wrap(self.read_string('\"'))\n    if c == '|':\n        self.skip()\n        return self.wrap(Symbol(self.read_string('|')))\n    if c in ';@':\n        annotations = self.gather_annotations()\n        v = self.next()\n        if self.include_annotations:\n            v.annotations = annotations + v.annotations\n        return v\n    if c == ':':\n        raise DecodeError('Unexpected key/value separator between items')\n    if c == '#':\n        self.skip()\n        c = self.nextchar()\n        if c == 'f': return self.wrap(False)\n        if c == 't': return self.wrap(True)\n        if c == '{': return self.wrap(frozenset(self.upto('}')))\n        if c == '\"': return self.wrap(self.read_literal_binary())\n        if c == 'x':\n            c = self.nextchar()\n            if c == '\"': return self.wrap(self.read_hex_binary())\n            if c == 'f': return self.wrap(self.read_hex_float(4))\n            if c == 'd': return self.wrap(self.read_hex_float(8))\n            raise DecodeError('Invalid #x syntax')\n        if c == '[': return self.wrap(self.read_base64_binary())\n        if c == '=':\n            old_ann = self.include_annotations\n            self.include_annotations = True\n            bs_val = self.next()\n            self.include_annotations = old_ann\n            if len(bs_val.annotations) > 0:\n                raise DecodeError('Annotations not permitted after #=')\n            bs_val = bs_val.item\n            if not isinstance(bs_val, bytes):\n                raise DecodeError('ByteString must follow #=')\n            return self.wrap(Decoder(bs_val, include_annotations = self.include_annotations).next())\n        if c == '!':\n            if self.parse_embedded is None:\n                raise DecodeError('No parse_embedded function supplied')\n            return self.wrap(Embedded(self.parse_embedded(self.next())))\n        raise DecodeError('Invalid # syntax')\n    if c == '<':\n        self.skip()\n        vs = self.upto('>')\n        if len(vs) == 0:\n            raise DecodeError('Missing record label')\n        return self.wrap(Record(vs[0], vs[1:]))\n    if c == '[':\n        self.skip()\n        return self.wrap(self.upto(']'))\n    if c == '{':\n        self.skip()\n        return self.wrap(self.read_dictionary())\n    if c in '>]}':\n        raise DecodeError('Unexpected ' + c)\n    self.skip()\n    return self.wrap(self.read_raw_symbol_or_number([c]))\n
"},{"location":"text/#preserves.text.Parser.try_next","title":"try_next()","text":"

TODO

Source code in preserves/text.py
def try_next(self):\n\"\"\"TODO\"\"\"\n    start = self.index\n    try:\n        return self.next()\n    except ShortPacket:\n        self.index = start\n        return None\n
"},{"location":"text/#preserves.text.TextCodec","title":"TextCodec","text":"

Bases: object

TODO

"},{"location":"text/#preserves.text.parse","title":"parse(bs, **kwargs)","text":"

TODO

Source code in preserves/text.py
def parse(bs, **kwargs):\n\"\"\"TODO\"\"\"\n    return Parser(input_buffer=bs, **kwargs).next()\n
"},{"location":"text/#preserves.text.parse_with_annotations","title":"parse_with_annotations(bs, **kwargs)","text":"

TODO

Source code in preserves/text.py
def parse_with_annotations(bs, **kwargs):\n\"\"\"TODO\"\"\"\n    return Parser(input_buffer=bs, include_annotations=True, **kwargs).next()\n
"},{"location":"text/#preserves.text.stringify","title":"stringify(v, **kwargs)","text":"

TODO

Source code in preserves/text.py
def stringify(v, **kwargs):\n\"\"\"TODO\"\"\"\n    e = Formatter(**kwargs)\n    e.append(v)\n    return e.contents()\n
"},{"location":"values/","title":"Representations of Values","text":"

TODO

"},{"location":"values/#preserves.values.Annotated","title":"Annotated(item)","text":"

Bases: object

TODO

TODO

Source code in preserves/values.py
def __init__(self, item):\n\"\"\"TODO\"\"\"\n    self.annotations = []\n    self.item = item\n
"},{"location":"values/#preserves.values.Annotated.peel","title":"peel()","text":"

TODO

Source code in preserves/values.py
def peel(self):\n\"\"\"TODO\"\"\"\n    return strip_annotations(self, 1)\n
"},{"location":"values/#preserves.values.Annotated.strip","title":"strip(depth=inf)","text":"

TODO

Source code in preserves/values.py
def strip(self, depth=inf):\n\"\"\"TODO\"\"\"\n    return strip_annotations(self, depth)\n
"},{"location":"values/#preserves.values.Embedded","title":"Embedded(value)","text":"

TODO

TODO

Source code in preserves/values.py
def __init__(self, value):\n\"\"\"TODO\"\"\"\n    self.embeddedValue = value\n
"},{"location":"values/#preserves.values.Float","title":"Float(value)","text":"

Bases: object

TODO

TODO

Source code in preserves/values.py
def __init__(self, value):\n\"\"\"TODO\"\"\"\n    self.value = value\n
"},{"location":"values/#preserves.values.Float.from_bytes","title":"from_bytes(bs) staticmethod","text":"

TODO

Source code in preserves/values.py
@staticmethod\ndef from_bytes(bs):\n\"\"\"TODO\"\"\"\n    vf = struct.unpack('>I', bs)[0]\n    if (vf & 0x7f800000) == 0x7f800000:\n        # NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.\n        sign = vf >> 31\n        payload = vf & 0x007fffff\n        dbs = struct.pack('>Q', (sign << 63) | 0x7ff0000000000000 | (payload << 29))\n        return Float(struct.unpack('>d', dbs)[0])\n    else:\n        return Float(struct.unpack('>f', bs)[0])\n
"},{"location":"values/#preserves.values.ImmutableDict","title":"ImmutableDict(*args, **kwargs)","text":"

Bases: dict

TODO

TODO

Source code in preserves/values.py
def __init__(self, *args, **kwargs):\n\"\"\"TODO\"\"\"\n    if hasattr(self, '__hash'): raise TypeError('Immutable')\n    super(ImmutableDict, self).__init__(*args, **kwargs)\n    self.__hash = None\n
"},{"location":"values/#preserves.values.ImmutableDict.from_kvs","title":"from_kvs(kvs) staticmethod","text":"

TODO

Source code in preserves/values.py
@staticmethod\ndef from_kvs(kvs):\n\"\"\"TODO\"\"\"\n    i = iter(kvs)\n    result = ImmutableDict()\n    result_proxy = super(ImmutableDict, result)\n    try:\n        while True:\n            k = next(i)\n            try:\n                v = next(i)\n            except StopIteration:\n                raise DecodeError(\"Missing dictionary value\")\n            result_proxy.__setitem__(k, v)\n    except StopIteration:\n        pass\n    return result\n
"},{"location":"values/#preserves.values.Record","title":"Record(key, fields)","text":"

Bases: object

TODO

TODO

Source code in preserves/values.py
def __init__(self, key, fields):\n\"\"\"TODO\"\"\"\n    self.key = key\n    self.fields = tuple(fields)\n    self.__hash = None\n
"},{"location":"values/#preserves.values.Record.makeBasicConstructor","title":"makeBasicConstructor(label, fieldNames) staticmethod","text":"

TODO

Source code in preserves/values.py
@staticmethod\ndef makeBasicConstructor(label, fieldNames):\n\"\"\"TODO\"\"\"\n    if type(fieldNames) == str:\n        fieldNames = fieldNames.split()\n    arity = len(fieldNames)\n    def ctor(*fields):\n        if len(fields) != arity:\n            raise Exception(\"Record: cannot instantiate %r expecting %d fields with %d fields\"%(\n                label,\n                arity,\n                len(fields)))\n        return Record(label, fields)\n    ctor.constructorInfo = RecordConstructorInfo(label, arity)\n    ctor.isClassOf = lambda v: \\\n                     isinstance(v, Record) and v.key == label and len(v.fields) == arity\n    def ensureClassOf(v):\n        if not ctor.isClassOf(v):\n            raise TypeError(\"Record: expected %r/%d, got %r\" % (label, arity, v))\n        return v\n    ctor.ensureClassOf = ensureClassOf\n    for fieldIndex in range(len(fieldNames)):\n        fieldName = fieldNames[fieldIndex]\n        # Stupid python scoping bites again\n        def getter(fieldIndex):\n            return lambda v: ensureClassOf(v)[fieldIndex]\n        setattr(ctor, '_' + fieldName, getter(fieldIndex))\n    return ctor\n
"},{"location":"values/#preserves.values.Record.makeConstructor","title":"makeConstructor(labelSymbolText, fieldNames) staticmethod","text":"

TODO

Source code in preserves/values.py
@staticmethod\ndef makeConstructor(labelSymbolText, fieldNames):\n\"\"\"TODO\"\"\"\n    return Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)\n
"},{"location":"values/#preserves.values.RecordConstructorInfo","title":"RecordConstructorInfo(key, arity)","text":"

Bases: object

TODO

TODO

Source code in preserves/values.py
def __init__(self, key, arity):\n\"\"\"TODO\"\"\"\n    self.key = key\n    self.arity = arity\n
"},{"location":"values/#preserves.values.Symbol","title":"Symbol(name)","text":"

Bases: object

TODO

TODO

Source code in preserves/values.py
def __init__(self, name):\n\"\"\"TODO\"\"\"\n    self.name = name.name if isinstance(name, Symbol) else name\n
"},{"location":"values/#preserves.values.annotate","title":"annotate(v, *anns)","text":"

TODO

Source code in preserves/values.py
def annotate(v, *anns):\n\"\"\"TODO\"\"\"\n    if not is_annotated(v):\n        v = Annotated(v)\n    for a in anns:\n        v.annotations.append(a)\n    return v\n
"},{"location":"values/#preserves.values.cmp_floats","title":"cmp_floats(a, b)","text":"

TODO

Source code in preserves/values.py
def cmp_floats(a, b):\n\"\"\"TODO\"\"\"\n    a = float_to_int(a)\n    b = float_to_int(b)\n    if a & 0x8000000000000000: a = a ^ 0x7fffffffffffffff\n    if b & 0x8000000000000000: b = b ^ 0x7fffffffffffffff\n    return a - b\n
"},{"location":"values/#preserves.values.dict_kvs","title":"dict_kvs(d)","text":"

TODO

Source code in preserves/values.py
def dict_kvs(d):\n\"\"\"TODO\"\"\"\n    for k in d:\n        yield k\n        yield d[k]\n
"},{"location":"values/#preserves.values.is_annotated","title":"is_annotated(v)","text":"

TODO

Source code in preserves/values.py
def is_annotated(v):\n\"\"\"TODO\"\"\"\n    return isinstance(v, Annotated)\n
"},{"location":"values/#preserves.values.preserve","title":"preserve(v)","text":"

TODO

Source code in preserves/values.py
def preserve(v):\n\"\"\"TODO\"\"\"\n    while hasattr(v, '__preserve__'):\n        v = v.__preserve__()\n    return v\n
"},{"location":"values/#preserves.values.strip_annotations","title":"strip_annotations(v, depth=inf)","text":"

TODO

Source code in preserves/values.py
def strip_annotations(v, depth=inf):\n\"\"\"TODO\"\"\"\n    if depth == 0: return v\n    if not is_annotated(v): return v\n\n    next_depth = depth - 1\n    def walk(v):\n        return strip_annotations(v, next_depth)\n\n    v = v.item\n    if isinstance(v, Record):\n        return Record(strip_annotations(v.key, depth), tuple(walk(f) for f in v.fields))\n    elif isinstance(v, list):\n        return tuple(walk(f) for f in v)\n    elif isinstance(v, tuple):\n        return tuple(walk(f) for f in v)\n    elif isinstance(v, set):\n        return frozenset(walk(f) for f in v)\n    elif isinstance(v, frozenset):\n        return frozenset(walk(f) for f in v)\n    elif isinstance(v, dict):\n        return ImmutableDict.from_kvs(walk(f) for f in dict_kvs(v))\n    elif is_annotated(v):\n        raise ValueError('Improper annotation structure')\n    else:\n        return v\n
"}]} \ No newline at end of file diff --git a/python/sitemap.xml b/python/sitemap.xml new file mode 100644 index 0000000..885f760 --- /dev/null +++ b/python/sitemap.xml @@ -0,0 +1,58 @@ + + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + + None + 2023-03-16 + daily + + \ No newline at end of file diff --git a/python/sitemap.xml.gz b/python/sitemap.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..bbb483d3252640ce1c4c7e2ce419122e724bdba9 GIT binary patch literal 204 zcmV;-05ks|iwFqAc@ty;|8r?{Wo=<_E_iKh0PU1b4#FT1h4-FLeX@qT*X)AcUp0fk2*^WM) z_A-DNHok&g2*U~EaU79XLH2y`$hsDL(Z#{z)x-oHA=`mi(}6|JMYR>|VvVGDQriXh zp|=eyNKs~_%<_GVtllOjI&NBdck;m+uJniEjk6H(51HTzp5O`opYXZG=ghv>KfAWf G1polx6=Zz? literal 0 HcmV?d00001 diff --git a/python/text/index.html b/python/text/index.html new file mode 100644 index 0000000..dd0b5d9 --- /dev/null +++ b/python/text/index.html @@ -0,0 +1,1330 @@ + + + + + + + + + + + + + + + + + + + + + + Human-readable text syntax - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Human-readable text syntax

+ + +
+ + + +
+ +

TODO

+ + + +
+ + + + + + + + +
+ + + +

+Formatter(format_embedded=lambda x: x, indent=None, with_commas=False, trailing_comma=False, include_annotations=True) + +

+ + +
+

+ Bases: TextCodec

+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/text.py +
304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
def __init__(self,
+             format_embedded=lambda x: x,
+             indent=None,
+             with_commas=False,
+             trailing_comma=False,
+             include_annotations=True):
+    """TODO"""
+    super(Formatter, self).__init__()
+    self.indent_delta = 0 if indent is None else indent
+    self.indent_distance = 0
+    self.with_commas = with_commas
+    self.trailing_comma = trailing_comma
+    self.chunks = []
+    self._format_embedded = format_embedded
+    self.include_annotations = include_annotations
+
+
+ + + +
+ + + + + + + + + +
+ + + +

+append(v) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
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
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
def append(self, v):
+    """TODO"""
+    v = preserve(v)
+    if hasattr(v, '__preserve_write_text__'):
+        v.__preserve_write_text__(self)
+    elif v is False:
+        self.chunks.append('#f')
+    elif v is True:
+        self.chunks.append('#t')
+    elif isinstance(v, float):
+        if math.isnan(v) or math.isinf(v):
+            self.chunks.append('#xd"' + struct.pack('>d', v).hex() + '"')
+        else:
+            self.chunks.append(repr(v))
+    elif isinstance(v, numbers.Number):
+        self.chunks.append('%d' % (v,))
+    elif isinstance(v, bytes):
+        self.chunks.append('#[%s]' % (base64.b64encode(v).decode('ascii'),))
+    elif isinstance(v, basestring_):
+        self.chunks.append('"')
+        for c in v:
+            if c == '"': self.chunks.append('\\"')
+            else: self.write_stringlike_char(c)
+        self.chunks.append('"')
+    elif isinstance(v, list):
+        self.write_seq('[', ']', v, self.append)
+    elif isinstance(v, tuple):
+        self.write_seq('[', ']', v, self.append)
+    elif isinstance(v, set):
+        self.write_seq('#{', '}', v, self.append)
+    elif isinstance(v, frozenset):
+        self.write_seq('#{', '}', v, self.append)
+    elif isinstance(v, dict):
+        def append_kv(kv):
+            self.append(kv[0])
+            self.chunks.append(': ')
+            self.append(kv[1])
+        self.write_seq('{', '}', v.items(), append_kv)
+    else:
+        try:
+            i = iter(v)
+        except TypeError:
+            i = None
+        if i is None:
+            self.cannot_format(v)
+        else:
+            self.write_seq('[', ']', i, self.append)
+
+
+
+ +
+ +
+ + + +

+contents() + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
325
+326
+327
def contents(self):
+    """TODO"""
+    return u''.join(self.chunks)
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+Parser(input_buffer='', include_annotations=False, parse_embedded=lambda x: x) + +

+ + +
+

+ Bases: TextCodec

+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/text.py +
22
+23
+24
+25
+26
+27
+28
def __init__(self, input_buffer=u'', include_annotations=False, parse_embedded=lambda x: x):
+    """TODO"""
+    super(Parser, self).__init__()
+    self.input_buffer = input_buffer
+    self.index = 0
+    self.include_annotations = include_annotations
+    self.parse_embedded = parse_embedded
+
+
+ + + +
+ + + + + + + + + +
+ + + +

+__iter__() + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
284
+285
+286
def __iter__(self):
+    """TODO"""
+    return self
+
+
+
+ +
+ +
+ + + +

+extend(text) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
30
+31
+32
+33
def extend(self, text):
+    """TODO"""
+    self.input_buffer = self.input_buffer[self.index:] + text
+    self.index = 0
+
+
+
+ +
+ +
+ + + +

+next() + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
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
def next(self):
+    """TODO"""
+    self.skip_whitespace()
+    c = self.peek()
+    if c == '"':
+        self.skip()
+        return self.wrap(self.read_string('"'))
+    if c == '|':
+        self.skip()
+        return self.wrap(Symbol(self.read_string('|')))
+    if c in ';@':
+        annotations = self.gather_annotations()
+        v = self.next()
+        if self.include_annotations:
+            v.annotations = annotations + v.annotations
+        return v
+    if c == ':':
+        raise DecodeError('Unexpected key/value separator between items')
+    if c == '#':
+        self.skip()
+        c = self.nextchar()
+        if c == 'f': return self.wrap(False)
+        if c == 't': return self.wrap(True)
+        if c == '{': return self.wrap(frozenset(self.upto('}')))
+        if c == '"': return self.wrap(self.read_literal_binary())
+        if c == 'x':
+            c = self.nextchar()
+            if c == '"': return self.wrap(self.read_hex_binary())
+            if c == 'f': return self.wrap(self.read_hex_float(4))
+            if c == 'd': return self.wrap(self.read_hex_float(8))
+            raise DecodeError('Invalid #x syntax')
+        if c == '[': return self.wrap(self.read_base64_binary())
+        if c == '=':
+            old_ann = self.include_annotations
+            self.include_annotations = True
+            bs_val = self.next()
+            self.include_annotations = old_ann
+            if len(bs_val.annotations) > 0:
+                raise DecodeError('Annotations not permitted after #=')
+            bs_val = bs_val.item
+            if not isinstance(bs_val, bytes):
+                raise DecodeError('ByteString must follow #=')
+            return self.wrap(Decoder(bs_val, include_annotations = self.include_annotations).next())
+        if c == '!':
+            if self.parse_embedded is None:
+                raise DecodeError('No parse_embedded function supplied')
+            return self.wrap(Embedded(self.parse_embedded(self.next())))
+        raise DecodeError('Invalid # syntax')
+    if c == '<':
+        self.skip()
+        vs = self.upto('>')
+        if len(vs) == 0:
+            raise DecodeError('Missing record label')
+        return self.wrap(Record(vs[0], vs[1:]))
+    if c == '[':
+        self.skip()
+        return self.wrap(self.upto(']'))
+    if c == '{':
+        self.skip()
+        return self.wrap(self.read_dictionary())
+    if c in '>]}':
+        raise DecodeError('Unexpected ' + c)
+    self.skip()
+    return self.wrap(self.read_raw_symbol_or_number([c]))
+
+
+
+ +
+ +
+ + + +

+try_next() + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
275
+276
+277
+278
+279
+280
+281
+282
def try_next(self):
+    """TODO"""
+    start = self.index
+    try:
+        return self.next()
+    except ShortPacket:
+        self.index = start
+        return None
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ TextCodec + + +

+ + +
+

+ Bases: object

+ + +

TODO

+ + + +
+ +
+ + +
+ + + +

+parse(bs, **kwargs) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
294
+295
+296
def parse(bs, **kwargs):
+    """TODO"""
+    return Parser(input_buffer=bs, **kwargs).next()
+
+
+
+ +
+ +
+ + + +

+parse_with_annotations(bs, **kwargs) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
298
+299
+300
def parse_with_annotations(bs, **kwargs):
+    """TODO"""
+    return Parser(input_buffer=bs, include_annotations=True, **kwargs).next()
+
+
+
+ +
+ +
+ + + +

+stringify(v, **kwargs) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/text.py +
423
+424
+425
+426
+427
def stringify(v, **kwargs):
+    """TODO"""
+    e = Formatter(**kwargs)
+    e.append(v)
+    return e.contents()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/python/values/index.html b/python/values/index.html new file mode 100644 index 0000000..abeb100 --- /dev/null +++ b/python/values/index.html @@ -0,0 +1,1693 @@ + + + + + + + + + + + + + + + + + + + + Representations of Values - Preserves + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Representations of Values

+ + +
+ + + +
+ +

TODO

+ + + +
+ + + + + + + + +
+ + + +

+Annotated(item) + +

+ + +
+

+ Bases: object

+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/values.py +
287
+288
+289
+290
def __init__(self, item):
+    """TODO"""
+    self.annotations = []
+    self.item = item
+
+
+ + + +
+ + + + + + + + + +
+ + + +

+peel() + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
311
+312
+313
def peel(self):
+    """TODO"""
+    return strip_annotations(self, 1)
+
+
+
+ +
+ +
+ + + +

+strip(depth=inf) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
307
+308
+309
def strip(self, depth=inf):
+    """TODO"""
+    return strip_annotations(self, depth)
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+Embedded(value) + +

+ + +
+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/values.py +
374
+375
+376
def __init__(self, value):
+    """TODO"""
+    self.embeddedValue = value
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+Float(value) + +

+ + +
+

+ Bases: object

+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/values.py +
29
+30
+31
def __init__(self, value):
+    """TODO"""
+    self.value = value
+
+
+ + + +
+ + + + + + + + + +
+ + + +

+from_bytes(bs) + + + staticmethod + + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
@staticmethod
+def from_bytes(bs):
+    """TODO"""
+    vf = struct.unpack('>I', bs)[0]
+    if (vf & 0x7f800000) == 0x7f800000:
+        # NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.
+        sign = vf >> 31
+        payload = vf & 0x007fffff
+        dbs = struct.pack('>Q', (sign << 63) | 0x7ff0000000000000 | (payload << 29))
+        return Float(struct.unpack('>d', dbs)[0])
+    else:
+        return Float(struct.unpack('>f', bs)[0])
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ImmutableDict(*args, **kwargs) + +

+ + +
+

+ Bases: dict

+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/values.py +
237
+238
+239
+240
+241
def __init__(self, *args, **kwargs):
+    """TODO"""
+    if hasattr(self, '__hash'): raise TypeError('Immutable')
+    super(ImmutableDict, self).__init__(*args, **kwargs)
+    self.__hash = None
+
+
+ + + +
+ + + + + + + + + +
+ + + +

+from_kvs(kvs) + + + staticmethod + + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
@staticmethod
+def from_kvs(kvs):
+    """TODO"""
+    i = iter(kvs)
+    result = ImmutableDict()
+    result_proxy = super(ImmutableDict, result)
+    try:
+        while True:
+            k = next(i)
+            try:
+                v = next(i)
+            except StopIteration:
+                raise DecodeError("Missing dictionary value")
+            result_proxy.__setitem__(k, v)
+    except StopIteration:
+        pass
+    return result
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+Record(key, fields) + +

+ + +
+

+ Bases: object

+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/values.py +
138
+139
+140
+141
+142
def __init__(self, key, fields):
+    """TODO"""
+    self.key = key
+    self.fields = tuple(fields)
+    self.__hash = None
+
+
+ + + +
+ + + + + + + + + +
+ + + +

+makeBasicConstructor(label, fieldNames) + + + staticmethod + + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
@staticmethod
+def makeBasicConstructor(label, fieldNames):
+    """TODO"""
+    if type(fieldNames) == str:
+        fieldNames = fieldNames.split()
+    arity = len(fieldNames)
+    def ctor(*fields):
+        if len(fields) != arity:
+            raise Exception("Record: cannot instantiate %r expecting %d fields with %d fields"%(
+                label,
+                arity,
+                len(fields)))
+        return Record(label, fields)
+    ctor.constructorInfo = RecordConstructorInfo(label, arity)
+    ctor.isClassOf = lambda v: \
+                     isinstance(v, Record) and v.key == label and len(v.fields) == arity
+    def ensureClassOf(v):
+        if not ctor.isClassOf(v):
+            raise TypeError("Record: expected %r/%d, got %r" % (label, arity, v))
+        return v
+    ctor.ensureClassOf = ensureClassOf
+    for fieldIndex in range(len(fieldNames)):
+        fieldName = fieldNames[fieldIndex]
+        # Stupid python scoping bites again
+        def getter(fieldIndex):
+            return lambda v: ensureClassOf(v)[fieldIndex]
+        setattr(ctor, '_' + fieldName, getter(fieldIndex))
+    return ctor
+
+
+
+ +
+ +
+ + + +

+makeConstructor(labelSymbolText, fieldNames) + + + staticmethod + + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
177
+178
+179
+180
@staticmethod
+def makeConstructor(labelSymbolText, fieldNames):
+    """TODO"""
+    return Record.makeBasicConstructor(Symbol(labelSymbolText), fieldNames)
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+RecordConstructorInfo(key, arity) + +

+ + +
+

+ Bases: object

+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/values.py +
213
+214
+215
+216
def __init__(self, key, arity):
+    """TODO"""
+    self.key = key
+    self.arity = arity
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+Symbol(name) + +

+ + +
+

+ Bases: object

+ + +

TODO

+ + +

TODO

+ +
+ Source code in preserves/values.py +
91
+92
+93
def __init__(self, name):
+    """TODO"""
+    self.name = name.name if isinstance(name, Symbol) else name
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + +
+ + + +

+annotate(v, *anns) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
358
+359
+360
+361
+362
+363
+364
def annotate(v, *anns):
+    """TODO"""
+    if not is_annotated(v):
+        v = Annotated(v)
+    for a in anns:
+        v.annotations.append(a)
+    return v
+
+
+
+ +
+ +
+ + + +

+cmp_floats(a, b) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
19
+20
+21
+22
+23
+24
+25
def cmp_floats(a, b):
+    """TODO"""
+    a = float_to_int(a)
+    b = float_to_int(b)
+    if a & 0x8000000000000000: a = a ^ 0x7fffffffffffffff
+    if b & 0x8000000000000000: b = b ^ 0x7fffffffffffffff
+    return a - b
+
+
+
+ +
+ +
+ + + +

+dict_kvs(d) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
277
+278
+279
+280
+281
def dict_kvs(d):
+    """TODO"""
+    for k in d:
+        yield k
+        yield d[k]
+
+
+
+ +
+ +
+ + + +

+is_annotated(v) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
327
+328
+329
def is_annotated(v):
+    """TODO"""
+    return isinstance(v, Annotated)
+
+
+
+ +
+ +
+ + + +

+preserve(v) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
10
+11
+12
+13
+14
def preserve(v):
+    """TODO"""
+    while hasattr(v, '__preserve__'):
+        v = v.__preserve__()
+    return v
+
+
+
+ +
+ +
+ + + +

+strip_annotations(v, depth=inf) + +

+ + +
+ +

TODO

+ +
+ Source code in preserves/values.py +
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
def strip_annotations(v, depth=inf):
+    """TODO"""
+    if depth == 0: return v
+    if not is_annotated(v): return v
+
+    next_depth = depth - 1
+    def walk(v):
+        return strip_annotations(v, next_depth)
+
+    v = v.item
+    if isinstance(v, Record):
+        return Record(strip_annotations(v.key, depth), tuple(walk(f) for f in v.fields))
+    elif isinstance(v, list):
+        return tuple(walk(f) for f in v)
+    elif isinstance(v, tuple):
+        return tuple(walk(f) for f in v)
+    elif isinstance(v, set):
+        return frozenset(walk(f) for f in v)
+    elif isinstance(v, frozenset):
+        return frozenset(walk(f) for f in v)
+    elif isinstance(v, dict):
+        return ImmutableDict.from_kvs(walk(f) for f in dict_kvs(v))
+    elif is_annotated(v):
+        raise ValueError('Improper annotation structure')
+    else:
+        return v
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file From 2bad9a6bca81db3f3589461866d3154c2e76bc30 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 16 Mar 2023 21:31:24 +0100 Subject: [PATCH 06/68] README.md for python --- implementations/python/README.md | 23 +++++++++++++++++++++++ implementations/python/docs/index.md | 17 +++++++++++------ implementations/python/mkdocs.yml | 2 +- implementations/python/setup.py | 3 ++- python/404.html | 10 +++++----- python/api/index.html | 10 +++++----- python/binary/index.html | 10 +++++----- python/compare/index.html | 10 +++++----- python/error/index.html | 10 +++++----- python/fold/index.html | 10 +++++----- python/index.html | 25 ++++++++++++++----------- python/merge/index.html | 10 +++++----- python/objects.inv | Bin 797 -> 804 bytes python/path/index.html | 10 +++++----- python/schema/index.html | 10 +++++----- python/search/search_index.json | 2 +- python/sitemap.xml.gz | Bin 204 -> 204 bytes python/text/index.html | 10 +++++----- python/values/index.html | 10 +++++----- 19 files changed, 107 insertions(+), 75 deletions(-) create mode 100644 implementations/python/README.md diff --git a/implementations/python/README.md b/implementations/python/README.md new file mode 100644 index 0000000..7edc968 --- /dev/null +++ b/implementations/python/README.md @@ -0,0 +1,23 @@ +# Python Preserves + +This package ([`preserves` on pypi.org](https://pypi.org/project/preserves/)) implements +[Preserves](https://preserves.dev/) for Python 3.x. It provides the core [semantics][] as well +as both the [human-readable text syntax](https://preserves.dev/preserves-text.html) (a superset +of JSON) and [machine-oriented binary format](https://preserves.dev/preserves-binary.html) +(including canonicalization) for Preserves. It also implements [Preserves +Schema](https://preserves.dev/preserves-schema.html) and [Preserves +Path](https://preserves.dev/preserves-path.html). + +# Git repository + +The project is [hosted on Gitlab](https://gitlab.com/preserves/preserves). + +# Documentation + +Documentation for the package is available at . + +# License + +The package is licensed under the Apache License v2.0. + +[semantics]: https://preserves.dev/preserves.html#semantics diff --git a/implementations/python/docs/index.md b/implementations/python/docs/index.md index fa643c7..6f2f1be 100644 --- a/implementations/python/docs/index.md +++ b/implementations/python/docs/index.md @@ -1,11 +1,16 @@ # Overview -This package implements [Preserves](https://preserves.dev/) for Python 3.x. It provides the -core [semantics][] as well as both the [human-readable text -syntax](https://preserves.dev/preserves-text.html) (a superset of JSON) and [machine-oriented -binary format](https://preserves.dev/preserves-binary.html) (including canonicalization) for -Preserves. It also implements [Preserves Schema](https://preserves.dev/preserves-schema.html) -and [Preserves Path](https://preserves.dev/preserves-path.html). +```shell +pip install preserves +``` + +This package ([`preserves` on pypi.org](https://pypi.org/project/preserves/)) implements +[Preserves](https://preserves.dev/) for Python 3.x. It provides the core [semantics][] as well +as both the [human-readable text syntax](https://preserves.dev/preserves-text.html) (a superset +of JSON) and [machine-oriented binary format](https://preserves.dev/preserves-binary.html) +(including canonicalization) for Preserves. It also implements [Preserves +Schema](https://preserves.dev/preserves-schema.html) and [Preserves +Path](https://preserves.dev/preserves-path.html). - Main package API: [preserves](/api) diff --git a/implementations/python/mkdocs.yml b/implementations/python/mkdocs.yml index c394d05..43cb7db 100644 --- a/implementations/python/mkdocs.yml +++ b/implementations/python/mkdocs.yml @@ -1,4 +1,4 @@ -site_name: Preserves +site_name: Python Preserves site_dir: ../../python theme: name: material diff --git a/implementations/python/setup.py b/implementations/python/setup.py index 120431b..9a3050a 100644 --- a/implementations/python/setup.py +++ b/implementations/python/setup.py @@ -14,8 +14,9 @@ setup( "Programming Language :: Python :: 3", ], packages=["preserves"], - url="https://gitlab.com/preserves/preserves", + url="https://preserves.dev/", description="Experimental data serialization format", + readme="README.md", install_requires=[], python_requires=">=3.6, <4", setup_requires=['setuptools_scm'], diff --git a/python/404.html b/python/404.html index 3933572..8603b48 100644 --- a/python/404.html +++ b/python/404.html @@ -15,7 +15,7 @@ - Preserves + Python Preserves @@ -76,7 +76,7 @@