├── .github └── workflows │ ├── build.yml │ ├── pypi_upload.yml │ └── testing.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── build-wheels.sh ├── docs-src ├── .gitignore ├── Makefile ├── make.bat └── source │ ├── _static │ └── custom.css │ ├── _templates │ └── layout.html │ ├── conf.py │ ├── data_types.rst │ ├── index.rst │ ├── term.atom.rst │ ├── term.bitstring.rst │ ├── term.erl_typing.rst │ ├── term.fun.rst │ ├── term.list.rst │ ├── term.pid.rst │ ├── term.py_codec_impl.rst │ └── term.reference.rst ├── docs ├── .buildinfo ├── .nojekyll ├── _sources │ ├── data_types.rst.txt │ ├── index.rst.txt │ ├── term.atom.rst.txt │ ├── term.bitstring.rst.txt │ ├── term.erl_typing.rst.txt │ ├── term.fun.rst.txt │ ├── term.list.rst.txt │ ├── term.pid.rst.txt │ ├── term.py_codec_impl.rst.txt │ └── term.reference.rst.txt ├── _static │ ├── ajax-loader.gif │ ├── alabaster.css │ ├── basic.css │ ├── comment-bright.png │ ├── comment-close.png │ ├── comment.png │ ├── custom.css │ ├── doctools.js │ ├── down-pressed.png │ ├── down.png │ ├── file.png │ ├── jquery-3.1.0.js │ ├── jquery.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── underscore-1.3.1.js │ ├── underscore.js │ ├── up-pressed.png │ ├── up.png │ └── websupport.js ├── data_types.html ├── genindex.html ├── index.html ├── objects.inv ├── py-modindex.html ├── search.html ├── searchindex.js ├── term.atom.html ├── term.bitstring.html ├── term.erl_typing.html ├── term.fun.html ├── term.list.html ├── term.pid.html ├── term.py_codec_impl.html └── term.reference.html ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── setup.py ├── src ├── consts.rs ├── decoder.rs ├── encoder.rs ├── errors.rs ├── helpers.rs ├── lib.rs └── reader.rs ├── term ├── __init__.py ├── atom.py ├── basetypes.py ├── bitstring.py ├── codec.py ├── fun.py ├── list.py ├── pid.py ├── py_codec_impl.py ├── reference.py └── util.py └── test ├── test_etf_decode.py ├── test_etf_encode.py └── test_rust_extension.py /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 11 | jobs: 12 | build_wheels: 13 | name: Build wheels for ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-10.15] 18 | 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 22 | - uses: actions/checkout@v2 23 | 24 | - name: Build wheels 25 | uses: joerick/cibuildwheel@v1.10.0 26 | env: 27 | CIBW_BEFORE_BUILD: pip install setuptools-rust 28 | CIBW_BEFORE_ALL_LINUX: "curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y" 29 | 30 | CIBW_ENVIRONMENT: 'PATH="$PATH:$HOME/.cargo/bin"' 31 | 32 | CIBW_SKIP: cp2* pp* cp35* *win* 33 | CIBW_BUILD_VERBOSITY: 1 34 | 35 | #CIBW_TEST_REQUIRES: pytest 36 | #CIBW_TEST_COMMAND: pytest 37 | 38 | - name: Upload wheel artifacts 39 | uses: actions/upload-artifact@v2 40 | with: 41 | path: ./wheelhouse/*.whl 42 | 43 | build_sdist: 44 | name: Build source distribution 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v2 48 | 49 | - uses: actions/setup-python@v2 50 | name: Install Python 51 | with: 52 | python-version: '3.7' 53 | - name: Build sdist 54 | run: python setup.py sdist 55 | - name: Upload sdist artifact 56 | uses: actions/upload-artifact@v2 57 | with: 58 | path: ./dist/* 59 | -------------------------------------------------------------------------------- /.github/workflows/pypi_upload.yml: -------------------------------------------------------------------------------- 1 | name: Upload to Pypi 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | # version: 7 | # description: 'Version upload to pypi' 8 | # required: true 9 | pypi_repo: 10 | description: 'Repo to upload to (testpypi or pypi)' 11 | default: 'testpypi' 12 | required: true 13 | 14 | jobs: 15 | upload: 16 | runs-on: ubuntu-18.04 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-python@v2 20 | with: 21 | python-version: '3.8' 22 | - uses: dawidd6/action-download-artifact@v2 23 | with: 24 | workflow: build.yml 25 | - name: show all data we've got to work with 26 | run: ls -R 27 | - name: Publish package to Test Pypi 28 | if: ${{ github.event.inputs.pypi_repo == 'testpypi' }} 29 | uses: pypa/gh-action-pypi-publish@release/v1 30 | with: 31 | user: __token__ 32 | password: ${{ secrets.PYPI_TEST_TOKEN }} 33 | packages_dir: artifact/ 34 | repository_url: https://test.pypi.org/legacy/ 35 | - name: Publish package to Pypi 36 | if: ${{ github.event.inputs.pypi_repo == 'pypi' }} 37 | uses: pypa/gh-action-pypi-publish@release/v1 38 | with: 39 | user: __token__ 40 | password: ${{ secrets.PYPI_TEST_TOKEN }} 41 | packages_dir: artifact/ -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Testing 5 | 6 | on: 7 | #push: 8 | #branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | python-version: [3.7, 3.8, 3.9] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install flake8 pytest setuptools_rust 31 | - name: Lint with flake8 32 | run: | 33 | # stop the build if there are Python syntax errors or undefined names 34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 37 | - name: Build rust native module 38 | run: | 39 | python setup.py build 40 | - name: Install dev link for package 41 | run: | 42 | pip install -e . 43 | - name: Test with pytest 44 | run: | 45 | pytest 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /venv 3 | /target 4 | *.egg-info 5 | *.pyc 6 | *.pyo 7 | 8 | *.o 9 | *.a 10 | *.so 11 | 12 | /.idea 13 | *.iml 14 | 15 | # Pyre Typecheck 16 | /.pyre 17 | /.pyre_configuration 18 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.19" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.1.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 25 | 26 | [[package]] 27 | name = "cfg-if" 28 | version = "1.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 31 | 32 | [[package]] 33 | name = "cpython" 34 | version = "0.7.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "43b398a2c65baaf5892f10bb69b52508bf7a993380cc4ecd3785aaebb5c79389" 37 | dependencies = [ 38 | "libc", 39 | "num-traits", 40 | "paste", 41 | "python3-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "crc32fast" 46 | version = "1.3.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 49 | dependencies = [ 50 | "cfg-if", 51 | ] 52 | 53 | [[package]] 54 | name = "flate2" 55 | version = "1.0.22" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" 58 | dependencies = [ 59 | "cfg-if", 60 | "crc32fast", 61 | "libc", 62 | "miniz_oxide", 63 | ] 64 | 65 | [[package]] 66 | name = "libc" 67 | version = "0.2.118" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" 70 | 71 | [[package]] 72 | name = "memchr" 73 | version = "2.5.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 76 | 77 | [[package]] 78 | name = "miniz_oxide" 79 | version = "0.4.4" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 82 | dependencies = [ 83 | "adler", 84 | "autocfg", 85 | ] 86 | 87 | [[package]] 88 | name = "native_codec_impl" 89 | version = "1.4.0" 90 | dependencies = [ 91 | "cpython", 92 | "flate2", 93 | "thiserror", 94 | ] 95 | 96 | [[package]] 97 | name = "num-traits" 98 | version = "0.2.14" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 101 | dependencies = [ 102 | "autocfg", 103 | ] 104 | 105 | [[package]] 106 | name = "paste" 107 | version = "1.0.6" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" 110 | 111 | [[package]] 112 | name = "proc-macro2" 113 | version = "1.0.36" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 116 | dependencies = [ 117 | "unicode-xid", 118 | ] 119 | 120 | [[package]] 121 | name = "python3-sys" 122 | version = "0.7.2" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "0f53ef6740367a09718d2cd21ba15b0d7972342a38e554736bcee7773e45c9f5" 125 | dependencies = [ 126 | "libc", 127 | "regex", 128 | ] 129 | 130 | [[package]] 131 | name = "quote" 132 | version = "1.0.15" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 135 | dependencies = [ 136 | "proc-macro2", 137 | ] 138 | 139 | [[package]] 140 | name = "regex" 141 | version = "1.6.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 144 | dependencies = [ 145 | "aho-corasick", 146 | "memchr", 147 | "regex-syntax", 148 | ] 149 | 150 | [[package]] 151 | name = "regex-syntax" 152 | version = "0.6.27" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 155 | 156 | [[package]] 157 | name = "syn" 158 | version = "1.0.86" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 161 | dependencies = [ 162 | "proc-macro2", 163 | "quote", 164 | "unicode-xid", 165 | ] 166 | 167 | [[package]] 168 | name = "thiserror" 169 | version = "1.0.30" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 172 | dependencies = [ 173 | "thiserror-impl", 174 | ] 175 | 176 | [[package]] 177 | name = "thiserror-impl" 178 | version = "1.0.30" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 181 | dependencies = [ 182 | "proc-macro2", 183 | "quote", 184 | "syn", 185 | ] 186 | 187 | [[package]] 188 | name = "unicode-xid" 189 | version = "0.2.2" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 192 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "native_codec_impl" 3 | version = "1.4.1" 4 | authors = ["Dmytro Lytovchenko "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies.cpython] 11 | version = "0.7.2" 12 | features = ["extension-module"] 13 | 14 | [dependencies] 15 | flate2 = "1" 16 | thiserror = "1" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Cargo.toml 2 | recursive-include src * 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Running `make dev` will set up links for development using symlink to the library source 3 | # Running `make install` will compile a release and install it properly as a release 4 | # 5 | 6 | LIBNAME=native_codec_impl 7 | ROOT=$(shell pwd) 8 | PIP=pip3 9 | PY=python3 10 | 11 | #.PHONY: compile 12 | #compile: 13 | # cargo build && \ 14 | # ln -fs $(ROOT)/target/debug/lib$(LIBNAME).so $(ROOT)/target/debug/$(LIBNAME).so 15 | # && cargo build --release 16 | 17 | .PHONY: clearterminal 18 | clearterminal: 19 | clear && printf '\e[3J' 20 | 21 | .PHONY: test 22 | test: clearterminal 23 | for f in $(shell ls test/*_test.py); do \ 24 | echo "RUNNING $$f"; \ 25 | $(PY) $$f || exit 1; \ 26 | done 27 | 28 | .PHONY: clearterminal dtest 29 | dtest: compile 30 | PYTHONPATH=$(ROOT):$(ROOT)/target/debug gdb --args python3 test/etf_decode_test.py 31 | 32 | .PHONY: docs 33 | docs: 34 | rm -rf $(ROOT)/docs; \ 35 | cd docs-src && \ 36 | $(MAKE) html && \ 37 | mv -f $(ROOT)/docs-src/build/html $(ROOT)/docs && \ 38 | touch $(ROOT)/docs/.nojekyll 39 | 40 | # 41 | # Installing for development, and for release 42 | # 43 | .PHONY: install dev requirements 44 | requirements: 45 | $(PIP) install -r requirements.txt 46 | dev: requirements 47 | $(PY) setup.py develop 48 | 49 | install: requirements 50 | $(PY) setup.py install 51 | 52 | # Run Pyre type check (requires: pip install pyre-check): 53 | .PHONY: pyre 54 | pyre: 55 | pyre check 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Erlang Term and Codec for Python 2 | ================================ 3 | 4 | This project is a part of http://github.com/Pyrlang/Pyrlang 5 | but does not depend on it and can be used separately. 6 | 7 | The Term library adds support classes to represent Erlang terms in Python 8 | and implements a codec for encoding and decoding data in Erlang 9 | External Term Format (abbreviated ETF) written in Python and a native 10 | and (most likely) safe Python extension written in Rust. 11 | 12 | The extension or Python implementation is selected automatically when you import 13 | `term.codec` and all API is available via `term.codec` module. If native 14 | extension was not found, a warning will be logged and the Python implementation 15 | will be used. 16 | 17 | ## Installing 18 | 19 | ### From PyPI 20 | 21 | If you just run 22 | ``` 23 | pip install pyrlang-term 24 | ``` 25 | the pure python version will be installed unless there exists a pre built binary. 26 | 27 | If you want to build the native one, you'll need rust and a few more packages. 28 | 29 | To install rust (from https://www.rust-lang.org/tools/install): 30 | 31 | ``` 32 | curl https://sh.rustup.rs -sSf | sh 33 | ``` 34 | 35 | Then install the build requirements before installing pyrlang-term: 36 | 37 | ``` 38 | pip install setuptools-rust semantic_version 39 | pip install pyrlang-term 40 | ``` 41 | 42 | ### From Source 43 | 44 | 1. Clone [Term](https://github.com/Pyrlang/Term) repository 45 | 2. Install Term from source: Go to Term directory and `pip install -e .` 46 | 47 | ## Testing 48 | 49 | To run the tests: 50 | 51 | ``` 52 | python -m unittest discover test 53 | ``` 54 | 55 | 56 | ## Atoms 57 | 58 | The native representation of atoms are found in `term.atom`. There are Two 59 | classes, `Atom` and `StrictAtom`. `Atom` is the default, it will become an 60 | atom when converting back to `etf`, however it evaluates as string so it's 61 | possible to use a map with atom keys as keyword argument. 62 | 63 | The drawback of this is if you may have a map with both atoms and string 64 | /binaries with the same content 65 | 66 | ```erlang 67 | #{foo => <<"atom">>, "foo" => <<"list">>} 68 | ``` 69 | Then you'll get 70 | ```python 71 | In [1]: from term import codec 72 | 73 | In [2]: data = bytes([131,116,0, ...]) 74 | 75 | In [3]: codec.binary_to_term(data) 76 | Out[3]: ({Atom('foo'): b'list'}, b'') 77 | ``` 78 | 79 | To allow for this we've added another atom type `StrictAtom` that will give you: 80 | ```python 81 | In [4]: codec.binary_to_term(data, {'atom': "StrictAtom"}) 82 | Out[4]: ({StrictAtom('foo'): b'atom', 'foo': b'list'}, b'') 83 | 84 | ``` 85 | Still `StrictAtom('foo') == 'foo'` so it you need something different still, you 86 | can put in your custom atom class 87 | 88 | ```python 89 | In [5]: class A: 90 | ...: def __init__(self, s): 91 | ...: self._s = s 92 | ...: def __repr__(self): 93 | ...: return 'A({})'.format(self._s) 94 | ...: 95 | 96 | In [6]: codec.binary_to_term(data, {'atom_call': A}) 97 | Out[6]: ({A(foo): b'atom', 'foo': b'list'}, b'') 98 | 99 | ``` 100 | The `'atom_call'` option takes any callable that takes a string as input, and 101 | the return value will be used for the atom representation. Only `Atom` and 102 | `StrictAtom` can be natively parsed back to atom when decoded. If you roll your 103 | own, make sure to use `encode_hook` when encoding. 104 | 105 | More Documentation 106 | ------------- 107 | 108 | Here: https://pyrlang.github.io/Term/ 109 | -------------------------------------------------------------------------------- /build-wheels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y 5 | export PATH="$HOME/.cargo/bin:$PATH" 6 | 7 | cd /io 8 | 9 | for PYBIN in /opt/python/{cp27-cp27m,cp27-cp27mu,cp35-cp35m,cp36-cp36m,cp37-cp37m}/bin; do 10 | export PYTHON_SYS_EXECUTABLE="$PYBIN/python" 11 | 12 | "${PYBIN}/pip" install -U setuptools wheel setuptools-rust 13 | "${PYBIN}/python" setup.py bdist_wheel 14 | done 15 | 16 | for whl in dist/*.whl; do 17 | auditwheel repair "$whl" -w dist/ 18 | done 19 | -------------------------------------------------------------------------------- /docs-src/.gitignore: -------------------------------------------------------------------------------- 1 | /build/* -------------------------------------------------------------------------------- /docs-src/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python3 -msphinx 7 | SPHINXPROJ = PyrlangDocumentation 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs-src/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | set SPHINXPROJ=PyrlangDocumentation 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs-src/source/_static/custom.css: -------------------------------------------------------------------------------- 1 | code.xref, a code { 2 | text-decoration: underline; 3 | } 4 | 5 | div.sphinxsidebar h3 a { 6 | text-decoration: underline !important; 7 | } 8 | 9 | a.toc-return { 10 | background: #aae; 11 | padding: 4px; 12 | margin: 4px; 13 | } -------------------------------------------------------------------------------- /docs-src/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block sidebarlogo %} 4 |
5 | Return to Start 7 |
8 | {{ super() }} 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /docs-src/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Pyrlang Documentation documentation build configuration file, created by 5 | # sphinx-quickstart on Mon May 29 12:12:47 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | 20 | import os 21 | import sys 22 | 23 | here = os.path.dirname(__file__) 24 | sys.path.insert(0, os.path.abspath(here)) 25 | sys.path.insert(0, os.path.abspath(here + '/../')) 26 | sys.path.insert(0, os.path.abspath(here + '/../../')) 27 | 28 | 29 | # -- General configuration ------------------------------------------------ 30 | 31 | # If your documentation needs a minimal Sphinx version, state it here. 32 | # 33 | # needs_sphinx = '1.6' 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be 36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 37 | # ones. 38 | extensions = [ 39 | 'sphinx.ext.autodoc', 40 | 'sphinx.ext.todo' 41 | ] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # The suffix(es) of source filenames. 47 | # You can specify multiple suffix as a list of string: 48 | # 49 | # source_suffix = ['.rst', '.md'] 50 | source_suffix = '.rst' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | project = 'Pyrlang Terms' 57 | copyright = '2017, Erlang Solutions Ltd.' 58 | author = 'Dmytro Lytovchenko' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = '1.0' 66 | # The full version, including alpha/beta/rc tags. 67 | release = '1.0' 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # 72 | # This is also used if you do content translation via gettext catalogs. 73 | # Usually you set "language" from the command line for these cases. 74 | language = "Python" 75 | 76 | # List of patterns, relative to source directory, that match files and 77 | # directories to ignore when looking for source files. 78 | # This patterns also effect to html_static_path and html_extra_path 79 | exclude_patterns = [] 80 | 81 | # The name of the Pygments (syntax highlighting) style to use. 82 | pygments_style = 'sphinx' 83 | 84 | # If true, `todo` and `todoList` produce output, else they produce nothing. 85 | todo_include_todos = True 86 | 87 | 88 | # -- Options for HTML output ---------------------------------------------- 89 | 90 | # The theme to use for HTML and HTML Help pages. See the documentation for 91 | # a list of builtin themes. 92 | # 93 | html_theme = 'alabaster' 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | # 99 | # html_theme_options = {} 100 | 101 | # Add any paths that contain custom static files (such as style sheets) here, 102 | # relative to this directory. They are copied after the builtin static files, 103 | # so a file named "default.css" will overwrite the builtin "default.css". 104 | html_static_path = ['_static'] 105 | 106 | 107 | # -- Options for HTMLHelp output ------------------------------------------ 108 | 109 | # Output file base name for HTML help builder. 110 | htmlhelp_basename = 'PyrlangDoc' 111 | 112 | 113 | # -- Options for LaTeX output --------------------------------------------- 114 | 115 | latex_elements = { 116 | # The paper size ('letterpaper' or 'a4paper'). 117 | # 118 | # 'papersize': 'letterpaper', 119 | 120 | # The font size ('10pt', '11pt' or '12pt'). 121 | # 122 | # 'pointsize': '10pt', 123 | 124 | # Additional stuff for the LaTeX preamble. 125 | # 126 | # 'preamble': '', 127 | 128 | # Latex figure (float) alignment 129 | # 130 | # 'figure_align': 'htbp', 131 | } 132 | 133 | # Grouping the document tree into LaTeX files. List of tuples 134 | # (source start file, target name, title, 135 | # author, documentclass [howto, manual, or own class]). 136 | latex_documents = [ 137 | (master_doc, 'Pyrlang.tex', 'Pyrlang Documentation', 138 | 'Dmytro Lytovchenko', 'manual'), 139 | ] 140 | 141 | 142 | # -- Options for manual page output --------------------------------------- 143 | 144 | # One entry per manual page. List of tuples 145 | # (source start file, name, description, authors, manual section). 146 | man_pages = [ 147 | (master_doc, 'pyrlang', 'Pyrlang Documentation', 148 | [author], 1) 149 | ] 150 | 151 | 152 | # -- Options for Texinfo output ------------------------------------------- 153 | 154 | # Grouping the document tree into Texinfo files. List of tuples 155 | # (source start file, target name, title, author, 156 | # dir menu entry, description, category) 157 | texinfo_documents = [ 158 | (master_doc, 'Pyrlang', 'Pyrlang Documentation', 159 | author, 'Pyrlang', 'Python x Erlang interop library.', 160 | 'Miscellaneous'), 161 | ] 162 | -------------------------------------------------------------------------------- /docs-src/source/data_types.rst: -------------------------------------------------------------------------------- 1 | Data Types in Pyrlang 2 | ===================== 3 | 4 | Decoding 5 | -------- 6 | 7 | =================== =================== =================================== 8 | Erlang Python Notes 9 | ------------------- ------------------- ----------------------------------- 10 | atom() Pyrlang.Atom Can use ``str()`` or access :py:attr:`~term.atom.Atom.text_` directly 11 | float() float 64-bit double precision floating point 12 | integer() int Any size integers 13 | list() list 14 | improper_list() (list, _Tail) A tuple with list and the tail element 15 | unicode string() list(int) Use helper functions in :py:mod:`~term.list` to convert to string 16 | byte string() bytes 17 | tuple() tuple 18 | map() dict 19 | binary() bytes 20 | bitstring() (bytes, int) A tuple of bytes and last_byte_bits:int defining incomplete last byte 21 | pid() Pyrlang.Pid 22 | reference() Pyrlang.Reference 23 | fun() Pyrlang.Fun Not useful in Python 24 | =================== =================== =================================== 25 | 26 | Encoding 27 | -------- 28 | 29 | ======================= ==================== =================================== 30 | Python Erlang Notes 31 | ----------------------- -------------------- ----------------------------------- 32 | Pyrlang.Atom atom() 33 | float float() 34 | int integer() 35 | list list() 36 | Pyrlang.ImproperList improper_list() 37 | list(int) string() 38 | bytes binary() 39 | tuple tuple() 40 | dict map() 41 | Pyrlang.Bitstring bitstring() A binary with last byte incomplete 42 | Pyrlang.Pid pid() 43 | Pyrlang.Reference reference() 44 | Pyrlang.Fun fun() Not useful in Python 45 | other objects #{'ClassName', #{}} Encoding may fail on some types 46 | ======================= ==================== =================================== 47 | 48 | Lists 49 | ----- 50 | 51 | Erlang lists can be of 2 kinds: 52 | 53 | * Regular lists of anything: 54 | 55 | * Unicode strings, which are regular lists of integers in unicode range. 56 | * 8-bit strings, sometimes called latin-1 or ASCII, which are regular lists 57 | of bytes. 58 | 59 | * Improper lists (those with last cell's tail being not ``[] NIL``). 60 | 61 | Pyrlang always decodes incoming regular lists as Python lists, 62 | use helper functions in :py:mod:`~term.list` to extract strings. 63 | If incoming string contained only bytes (integers between 0 and 255) then 64 | Erlang node will optimize the encoding and send a byte array. In this case you 65 | will receive Python ``bytes`` string, and not a list, which is 66 | **the same as if you have sent a binary**, so to reduce confusion always send 67 | strings as UTF8 binaries. 68 | 69 | A regular Erlang list always has an invisible ``[]`` (``NIL``) set as tail of 70 | its last cons cell. Regular lists map directly to Python lists and 71 | **there is no easy way to tell a list of integers from a string** other than 72 | check elements ranges and assume that is a printable string. Unless you received 73 | Python ``bytes`` then it's easy. 74 | 75 | An improper Erlang list has some other value than ``[] NIL`` as the tail of 76 | its last cell. 77 | Pyrlang returns these as Python tuple ``(list, tail)``. 78 | To tell Pyrlang encoder to send an improper list back to Erlang, use the 79 | :py:class:`~term.list.ImproperList` class. 80 | 81 | 82 | Binaries 83 | -------- 84 | 85 | Pyrlang always decodes incoming Erlang binaries into Python ``bytes`` objects. 86 | 87 | Bitstrings are decoded as Python pairs of ``(bytes, last_byte_bits:int)`` 88 | To be able to send a bitstring back to Erlang, use class 89 | :py:class:`~term.bitstring.BitString`. 90 | -------------------------------------------------------------------------------- /docs-src/source/index.rst: -------------------------------------------------------------------------------- 1 | Pyrlang Term Library 2 | ==================== 3 | 4 | Term is a Python 3.5 library which supports Erlang Term encoding, decoding and 5 | representation as Python values. There are identical implementations of codec 6 | in Python and in Rust, if your machine doesn't have Rust compiler installed, 7 | the slower Python implementation will be used automatically. 8 | 9 | This library has no dependencies on other parts of Pyrlang project and can be 10 | used standalone. 11 | 12 | Important APIs are: 13 | 14 | .. code-block:: python 15 | 16 | from term import codec, Atom 17 | t = codec.term_to_binary((1, Atom('ok'))) 18 | # Function term_to_binary will prepend a 131 byte tag 19 | # Function term_to_binary_2 encodes without a leading 131 byte 20 | 21 | (val, tail) = codec.binary_to_term(bytes) 22 | # Returns a pair: value and remaining bytes 23 | # Function binary_to_term strips leading 131 byte and also handles 24 | # decompression of an eventually compressed term 25 | # Function binary_to_term_2 decodes without a leading 131 byte 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | :caption: Contents 30 | 31 | data_types 32 | term.atom 33 | term.bitstring 34 | term.erl_typing 35 | term.fun 36 | term.list 37 | term.pid 38 | term.py_codec_impl 39 | term.reference 40 | -------------------------------------------------------------------------------- /docs-src/source/term.atom.rst: -------------------------------------------------------------------------------- 1 | term.atom module 2 | ================ 3 | 4 | .. automodule:: term.atom 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs-src/source/term.bitstring.rst: -------------------------------------------------------------------------------- 1 | term.bitstring module 2 | ===================== 3 | 4 | .. automodule:: term.bitstring 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs-src/source/term.erl_typing.rst: -------------------------------------------------------------------------------- 1 | term.erl_typing helper module 2 | ============================= 3 | 4 | .. automodule:: term.erl_typing 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs-src/source/term.fun.rst: -------------------------------------------------------------------------------- 1 | term.fun module 2 | =============== 3 | 4 | .. automodule:: term.fun 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs-src/source/term.list.rst: -------------------------------------------------------------------------------- 1 | term.list module 2 | ================ 3 | 4 | .. automodule:: term.list 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs-src/source/term.pid.rst: -------------------------------------------------------------------------------- 1 | term.pid module 2 | =============== 3 | 4 | .. automodule:: term.pid 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs-src/source/term.py_codec_impl.rst: -------------------------------------------------------------------------------- 1 | term.py_codec_impl module 2 | ========================= 3 | 4 | .. automodule:: term.py_codec_impl 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs-src/source/term.reference.rst: -------------------------------------------------------------------------------- 1 | term.reference module 2 | ===================== 3 | 4 | .. automodule:: term.reference 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: aa8419785d07798db3e2f5d8952ae148 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_sources/data_types.rst.txt: -------------------------------------------------------------------------------- 1 | Data Types in Pyrlang 2 | ===================== 3 | 4 | Decoding 5 | -------- 6 | 7 | =================== =================== =================================== 8 | Erlang Python Notes 9 | ------------------- ------------------- ----------------------------------- 10 | atom() Pyrlang.Atom Can use ``str()`` or access :py:attr:`~term.atom.Atom.text_` directly 11 | float() float 64-bit double precision floating point 12 | integer() int Any size integers 13 | list() list 14 | improper_list() (list, _Tail) A tuple with list and the tail element 15 | unicode string() list(int) Use helper functions in :py:mod:`~term.list` to convert to string 16 | byte string() bytes 17 | tuple() tuple 18 | map() dict 19 | binary() bytes 20 | bitstring() (bytes, int) A tuple of bytes and last_byte_bits:int defining incomplete last byte 21 | pid() Pyrlang.Pid 22 | reference() Pyrlang.Reference 23 | fun() Pyrlang.Fun Not useful in Python 24 | =================== =================== =================================== 25 | 26 | Encoding 27 | -------- 28 | 29 | ======================= ==================== =================================== 30 | Python Erlang Notes 31 | ----------------------- -------------------- ----------------------------------- 32 | Pyrlang.Atom atom() 33 | float float() 34 | int integer() 35 | list list() 36 | Pyrlang.ImproperList improper_list() 37 | list(int) string() 38 | bytes binary() 39 | tuple tuple() 40 | dict map() 41 | Pyrlang.Bitstring bitstring() A binary with last byte incomplete 42 | Pyrlang.Pid pid() 43 | Pyrlang.Reference reference() 44 | Pyrlang.Fun fun() Not useful in Python 45 | other objects #{'ClassName', #{}} Encoding may fail on some types 46 | ======================= ==================== =================================== 47 | 48 | Lists 49 | ----- 50 | 51 | Erlang lists can be of 2 kinds: 52 | 53 | * Regular lists of anything: 54 | 55 | * Unicode strings, which are regular lists of integers in unicode range. 56 | * 8-bit strings, sometimes called latin-1 or ASCII, which are regular lists 57 | of bytes. 58 | 59 | * Improper lists (those with last cell's tail being not ``[] NIL``). 60 | 61 | Pyrlang always decodes incoming regular lists as Python lists, 62 | use helper functions in :py:mod:`~term.list` to extract strings. 63 | If incoming string contained only bytes (integers between 0 and 255) then 64 | Erlang node will optimize the encoding and send a byte array. In this case you 65 | will receive Python ``bytes`` string, and not a list, which is 66 | **the same as if you have sent a binary**, so to reduce confusion always send 67 | strings as UTF8 binaries. 68 | 69 | A regular Erlang list always has an invisible ``[]`` (``NIL``) set as tail of 70 | its last cons cell. Regular lists map directly to Python lists and 71 | **there is no easy way to tell a list of integers from a string** other than 72 | check elements ranges and assume that is a printable string. Unless you received 73 | Python ``bytes`` then it's easy. 74 | 75 | An improper Erlang list has some other value than ``[] NIL`` as the tail of 76 | its last cell. 77 | Pyrlang returns these as Python tuple ``(list, tail)``. 78 | To tell Pyrlang encoder to send an improper list back to Erlang, use the 79 | :py:class:`~term.list.ImproperList` class. 80 | 81 | 82 | Binaries 83 | -------- 84 | 85 | Pyrlang always decodes incoming Erlang binaries into Python ``bytes`` objects. 86 | 87 | Bitstrings are decoded as Python pairs of ``(bytes, last_byte_bits:int)`` 88 | To be able to send a bitstring back to Erlang, use class 89 | :py:class:`~term.bitstring.BitString`. 90 | -------------------------------------------------------------------------------- /docs/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | Pyrlang Term Library 2 | ==================== 3 | 4 | Term is a Python 3.5 library which supports Erlang Term encoding, decoding and 5 | representation as Python values. There are identical implementations of codec 6 | in Python and in Rust, if your machine doesn't have Rust compiler installed, 7 | the slower Python implementation will be used automatically. 8 | 9 | This library has no dependencies on other parts of Pyrlang project and can be 10 | used standalone. 11 | 12 | Important APIs are: 13 | 14 | .. code-block:: python 15 | 16 | from term import codec, Atom 17 | t = codec.term_to_binary((1, Atom('ok'))) 18 | # Function term_to_binary will prepend a 131 byte tag 19 | # Function term_to_binary_2 encodes without a leading 131 byte 20 | 21 | (val, tail) = codec.binary_to_term(bytes) 22 | # Returns a pair: value and remaining bytes 23 | # Function binary_to_term strips leading 131 byte and also handles 24 | # decompression of an eventually compressed term 25 | # Function binary_to_term_2 decodes without a leading 131 byte 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | :caption: Contents 30 | 31 | data_types 32 | term.atom 33 | term.bitstring 34 | term.erl_typing 35 | term.fun 36 | term.list 37 | term.pid 38 | term.py_codec_impl 39 | term.reference 40 | -------------------------------------------------------------------------------- /docs/_sources/term.atom.rst.txt: -------------------------------------------------------------------------------- 1 | term.atom module 2 | ================ 3 | 4 | .. automodule:: term.atom 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/term.bitstring.rst.txt: -------------------------------------------------------------------------------- 1 | term.bitstring module 2 | ===================== 3 | 4 | .. automodule:: term.bitstring 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/term.erl_typing.rst.txt: -------------------------------------------------------------------------------- 1 | term.erl_typing helper module 2 | ============================= 3 | 4 | .. automodule:: term.erl_typing 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/term.fun.rst.txt: -------------------------------------------------------------------------------- 1 | term.fun module 2 | =============== 3 | 4 | .. automodule:: term.fun 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/term.list.rst.txt: -------------------------------------------------------------------------------- 1 | term.list module 2 | ================ 3 | 4 | .. automodule:: term.list 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/term.pid.rst.txt: -------------------------------------------------------------------------------- 1 | term.pid module 2 | =============== 3 | 4 | .. automodule:: term.pid 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/term.py_codec_impl.rst.txt: -------------------------------------------------------------------------------- 1 | term.py_codec_impl module 2 | ========================= 3 | 4 | .. automodule:: term.py_codec_impl 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/term.reference.rst.txt: -------------------------------------------------------------------------------- 1 | term.reference module 2 | ===================== 3 | 4 | .. automodule:: term.reference 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | word-wrap: break-word; 56 | overflow-wrap : break-word; 57 | } 58 | 59 | div.sphinxsidebar ul { 60 | list-style: none; 61 | } 62 | 63 | div.sphinxsidebar ul ul, 64 | div.sphinxsidebar ul.want-points { 65 | margin-left: 20px; 66 | list-style: square; 67 | } 68 | 69 | div.sphinxsidebar ul ul { 70 | margin-top: 0; 71 | margin-bottom: 0; 72 | } 73 | 74 | div.sphinxsidebar form { 75 | margin-top: 10px; 76 | } 77 | 78 | div.sphinxsidebar input { 79 | border: 1px solid #98dbcc; 80 | font-family: sans-serif; 81 | font-size: 1em; 82 | } 83 | 84 | div.sphinxsidebar #searchbox input[type="text"] { 85 | width: 170px; 86 | } 87 | 88 | img { 89 | border: 0; 90 | max-width: 100%; 91 | } 92 | 93 | /* -- search page ----------------------------------------------------------- */ 94 | 95 | ul.search { 96 | margin: 10px 0 0 20px; 97 | padding: 0; 98 | } 99 | 100 | ul.search li { 101 | padding: 5px 0 5px 20px; 102 | background-image: url(file.png); 103 | background-repeat: no-repeat; 104 | background-position: 0 7px; 105 | } 106 | 107 | ul.search li a { 108 | font-weight: bold; 109 | } 110 | 111 | ul.search li div.context { 112 | color: #888; 113 | margin: 2px 0 0 30px; 114 | text-align: left; 115 | } 116 | 117 | ul.keywordmatches li.goodmatch a { 118 | font-weight: bold; 119 | } 120 | 121 | /* -- index page ------------------------------------------------------------ */ 122 | 123 | table.contentstable { 124 | width: 90%; 125 | margin-left: auto; 126 | margin-right: auto; 127 | } 128 | 129 | table.contentstable p.biglink { 130 | line-height: 150%; 131 | } 132 | 133 | a.biglink { 134 | font-size: 1.3em; 135 | } 136 | 137 | span.linkdescr { 138 | font-style: italic; 139 | padding-top: 5px; 140 | font-size: 90%; 141 | } 142 | 143 | /* -- general index --------------------------------------------------------- */ 144 | 145 | table.indextable { 146 | width: 100%; 147 | } 148 | 149 | table.indextable td { 150 | text-align: left; 151 | vertical-align: top; 152 | } 153 | 154 | table.indextable ul { 155 | margin-top: 0; 156 | margin-bottom: 0; 157 | list-style-type: none; 158 | } 159 | 160 | table.indextable > tbody > tr > td > ul { 161 | padding-left: 0em; 162 | } 163 | 164 | table.indextable tr.pcap { 165 | height: 10px; 166 | } 167 | 168 | table.indextable tr.cap { 169 | margin-top: 10px; 170 | background-color: #f2f2f2; 171 | } 172 | 173 | img.toggler { 174 | margin-right: 3px; 175 | margin-top: 3px; 176 | cursor: pointer; 177 | } 178 | 179 | div.modindex-jumpbox { 180 | border-top: 1px solid #ddd; 181 | border-bottom: 1px solid #ddd; 182 | margin: 1em 0 1em 0; 183 | padding: 0.4em; 184 | } 185 | 186 | div.genindex-jumpbox { 187 | border-top: 1px solid #ddd; 188 | border-bottom: 1px solid #ddd; 189 | margin: 1em 0 1em 0; 190 | padding: 0.4em; 191 | } 192 | 193 | /* -- domain module index --------------------------------------------------- */ 194 | 195 | table.modindextable td { 196 | padding: 2px; 197 | border-collapse: collapse; 198 | } 199 | 200 | /* -- general body styles --------------------------------------------------- */ 201 | 202 | div.body p, div.body dd, div.body li, div.body blockquote { 203 | -moz-hyphens: auto; 204 | -ms-hyphens: auto; 205 | -webkit-hyphens: auto; 206 | hyphens: auto; 207 | } 208 | 209 | a.headerlink { 210 | visibility: hidden; 211 | } 212 | 213 | h1:hover > a.headerlink, 214 | h2:hover > a.headerlink, 215 | h3:hover > a.headerlink, 216 | h4:hover > a.headerlink, 217 | h5:hover > a.headerlink, 218 | h6:hover > a.headerlink, 219 | dt:hover > a.headerlink, 220 | caption:hover > a.headerlink, 221 | p.caption:hover > a.headerlink, 222 | div.code-block-caption:hover > a.headerlink { 223 | visibility: visible; 224 | } 225 | 226 | div.body p.caption { 227 | text-align: inherit; 228 | } 229 | 230 | div.body td { 231 | text-align: left; 232 | } 233 | 234 | .first { 235 | margin-top: 0 !important; 236 | } 237 | 238 | p.rubric { 239 | margin-top: 30px; 240 | font-weight: bold; 241 | } 242 | 243 | img.align-left, .figure.align-left, object.align-left { 244 | clear: left; 245 | float: left; 246 | margin-right: 1em; 247 | } 248 | 249 | img.align-right, .figure.align-right, object.align-right { 250 | clear: right; 251 | float: right; 252 | margin-left: 1em; 253 | } 254 | 255 | img.align-center, .figure.align-center, object.align-center { 256 | display: block; 257 | margin-left: auto; 258 | margin-right: auto; 259 | } 260 | 261 | .align-left { 262 | text-align: left; 263 | } 264 | 265 | .align-center { 266 | text-align: center; 267 | } 268 | 269 | .align-right { 270 | text-align: right; 271 | } 272 | 273 | /* -- sidebars -------------------------------------------------------------- */ 274 | 275 | div.sidebar { 276 | margin: 0 0 0.5em 1em; 277 | border: 1px solid #ddb; 278 | padding: 7px 7px 0 7px; 279 | background-color: #ffe; 280 | width: 40%; 281 | float: right; 282 | } 283 | 284 | p.sidebar-title { 285 | font-weight: bold; 286 | } 287 | 288 | /* -- topics ---------------------------------------------------------------- */ 289 | 290 | div.topic { 291 | border: 1px solid #ccc; 292 | padding: 7px 7px 0 7px; 293 | margin: 10px 0 10px 0; 294 | } 295 | 296 | p.topic-title { 297 | font-size: 1.1em; 298 | font-weight: bold; 299 | margin-top: 10px; 300 | } 301 | 302 | /* -- admonitions ----------------------------------------------------------- */ 303 | 304 | div.admonition { 305 | margin-top: 10px; 306 | margin-bottom: 10px; 307 | padding: 7px; 308 | } 309 | 310 | div.admonition dt { 311 | font-weight: bold; 312 | } 313 | 314 | div.admonition dl { 315 | margin-bottom: 0; 316 | } 317 | 318 | p.admonition-title { 319 | margin: 0px 10px 5px 0px; 320 | font-weight: bold; 321 | } 322 | 323 | div.body p.centered { 324 | text-align: center; 325 | margin-top: 25px; 326 | } 327 | 328 | /* -- tables ---------------------------------------------------------------- */ 329 | 330 | table.docutils { 331 | border: 0; 332 | border-collapse: collapse; 333 | } 334 | 335 | table caption span.caption-number { 336 | font-style: italic; 337 | } 338 | 339 | table caption span.caption-text { 340 | } 341 | 342 | table.docutils td, table.docutils th { 343 | padding: 1px 8px 1px 5px; 344 | border-top: 0; 345 | border-left: 0; 346 | border-right: 0; 347 | border-bottom: 1px solid #aaa; 348 | } 349 | 350 | table.footnote td, table.footnote th { 351 | border: 0 !important; 352 | } 353 | 354 | th { 355 | text-align: left; 356 | padding-right: 5px; 357 | } 358 | 359 | table.citation { 360 | border-left: solid 1px gray; 361 | margin-left: 1px; 362 | } 363 | 364 | table.citation td { 365 | border-bottom: none; 366 | } 367 | 368 | /* -- figures --------------------------------------------------------------- */ 369 | 370 | div.figure { 371 | margin: 0.5em; 372 | padding: 0.5em; 373 | } 374 | 375 | div.figure p.caption { 376 | padding: 0.3em; 377 | } 378 | 379 | div.figure p.caption span.caption-number { 380 | font-style: italic; 381 | } 382 | 383 | div.figure p.caption span.caption-text { 384 | } 385 | 386 | /* -- field list styles ----------------------------------------------------- */ 387 | 388 | table.field-list td, table.field-list th { 389 | border: 0 !important; 390 | } 391 | 392 | .field-list ul { 393 | margin: 0; 394 | padding-left: 1em; 395 | } 396 | 397 | .field-list p { 398 | margin: 0; 399 | } 400 | 401 | .field-name { 402 | -moz-hyphens: manual; 403 | -ms-hyphens: manual; 404 | -webkit-hyphens: manual; 405 | hyphens: manual; 406 | } 407 | 408 | /* -- other body styles ----------------------------------------------------- */ 409 | 410 | ol.arabic { 411 | list-style: decimal; 412 | } 413 | 414 | ol.loweralpha { 415 | list-style: lower-alpha; 416 | } 417 | 418 | ol.upperalpha { 419 | list-style: upper-alpha; 420 | } 421 | 422 | ol.lowerroman { 423 | list-style: lower-roman; 424 | } 425 | 426 | ol.upperroman { 427 | list-style: upper-roman; 428 | } 429 | 430 | dl { 431 | margin-bottom: 15px; 432 | } 433 | 434 | dd p { 435 | margin-top: 0px; 436 | } 437 | 438 | dd ul, dd table { 439 | margin-bottom: 10px; 440 | } 441 | 442 | dd { 443 | margin-top: 3px; 444 | margin-bottom: 10px; 445 | margin-left: 30px; 446 | } 447 | 448 | dt:target, .highlighted { 449 | background-color: #fbe54e; 450 | } 451 | 452 | dl.glossary dt { 453 | font-weight: bold; 454 | font-size: 1.1em; 455 | } 456 | 457 | .optional { 458 | font-size: 1.3em; 459 | } 460 | 461 | .sig-paren { 462 | font-size: larger; 463 | } 464 | 465 | .versionmodified { 466 | font-style: italic; 467 | } 468 | 469 | .system-message { 470 | background-color: #fda; 471 | padding: 5px; 472 | border: 3px solid red; 473 | } 474 | 475 | .footnote:target { 476 | background-color: #ffa; 477 | } 478 | 479 | .line-block { 480 | display: block; 481 | margin-top: 1em; 482 | margin-bottom: 1em; 483 | } 484 | 485 | .line-block .line-block { 486 | margin-top: 0; 487 | margin-bottom: 0; 488 | margin-left: 1.5em; 489 | } 490 | 491 | .guilabel, .menuselection { 492 | font-family: sans-serif; 493 | } 494 | 495 | .accelerator { 496 | text-decoration: underline; 497 | } 498 | 499 | .classifier { 500 | font-style: oblique; 501 | } 502 | 503 | abbr, acronym { 504 | border-bottom: dotted 1px; 505 | cursor: help; 506 | } 507 | 508 | /* -- code displays --------------------------------------------------------- */ 509 | 510 | pre { 511 | overflow: auto; 512 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 513 | } 514 | 515 | span.pre { 516 | -moz-hyphens: none; 517 | -ms-hyphens: none; 518 | -webkit-hyphens: none; 519 | hyphens: none; 520 | } 521 | 522 | td.linenos pre { 523 | padding: 5px 0px; 524 | border: 0; 525 | background-color: transparent; 526 | color: #aaa; 527 | } 528 | 529 | table.highlighttable { 530 | margin-left: 0.5em; 531 | } 532 | 533 | table.highlighttable td { 534 | padding: 0 0.5em 0 0.5em; 535 | } 536 | 537 | div.code-block-caption { 538 | padding: 2px 5px; 539 | font-size: small; 540 | } 541 | 542 | div.code-block-caption code { 543 | background-color: transparent; 544 | } 545 | 546 | div.code-block-caption + div > div.highlight > pre { 547 | margin-top: 0; 548 | } 549 | 550 | div.code-block-caption span.caption-number { 551 | padding: 0.1em 0.3em; 552 | font-style: italic; 553 | } 554 | 555 | div.code-block-caption span.caption-text { 556 | } 557 | 558 | div.literal-block-wrapper { 559 | padding: 1em 1em 0; 560 | } 561 | 562 | div.literal-block-wrapper div.highlight { 563 | margin: 0; 564 | } 565 | 566 | code.descname { 567 | background-color: transparent; 568 | font-weight: bold; 569 | font-size: 1.2em; 570 | } 571 | 572 | code.descclassname { 573 | background-color: transparent; 574 | } 575 | 576 | code.xref, a code { 577 | background-color: transparent; 578 | font-weight: bold; 579 | } 580 | 581 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 582 | background-color: transparent; 583 | } 584 | 585 | .viewcode-link { 586 | float: right; 587 | } 588 | 589 | .viewcode-back { 590 | float: right; 591 | font-family: sans-serif; 592 | } 593 | 594 | div.viewcode-block:target { 595 | margin: -1px -10px; 596 | padding: 0 10px; 597 | } 598 | 599 | /* -- math display ---------------------------------------------------------- */ 600 | 601 | img.math { 602 | vertical-align: middle; 603 | } 604 | 605 | div.body div.math p { 606 | text-align: center; 607 | } 608 | 609 | span.eqno { 610 | float: right; 611 | } 612 | 613 | span.eqno a.headerlink { 614 | position: relative; 615 | left: 0px; 616 | z-index: 1; 617 | } 618 | 619 | div.math:hover a.headerlink { 620 | visibility: visible; 621 | } 622 | 623 | /* -- printout stylesheet --------------------------------------------------- */ 624 | 625 | @media print { 626 | div.document, 627 | div.documentwrapper, 628 | div.bodywrapper { 629 | margin: 0 !important; 630 | width: 100%; 631 | } 632 | 633 | div.sphinxsidebar, 634 | div.related, 635 | div.footer, 636 | #top-link { 637 | display: none; 638 | } 639 | } -------------------------------------------------------------------------------- /docs/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/comment.png -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | code.xref, a code { 2 | text-decoration: underline; 3 | } 4 | 5 | div.sphinxsidebar h3 a { 6 | text-decoration: underline !important; 7 | } 8 | 9 | a.toc-return { 10 | background: #aae; 11 | padding: 4px; 12 | margin: 4px; 13 | } -------------------------------------------------------------------------------- /docs/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /* 95 | * backward compatibility for jQuery.browser 96 | * This will be supported until firefox bug is fixed. 97 | */ 98 | if (!jQuery.browser) { 99 | jQuery.uaMatch = function(ua) { 100 | ua = ua.toLowerCase(); 101 | 102 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 103 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 104 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 105 | /(msie) ([\w.]+)/.exec(ua) || 106 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 107 | []; 108 | 109 | return { 110 | browser: match[ 1 ] || "", 111 | version: match[ 2 ] || "0" 112 | }; 113 | }; 114 | jQuery.browser = {}; 115 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 116 | } 117 | 118 | /** 119 | * Small JavaScript module for the documentation. 120 | */ 121 | var Documentation = { 122 | 123 | init : function() { 124 | this.fixFirefoxAnchorBug(); 125 | this.highlightSearchWords(); 126 | this.initIndexTable(); 127 | 128 | }, 129 | 130 | /** 131 | * i18n support 132 | */ 133 | TRANSLATIONS : {}, 134 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 135 | LOCALE : 'unknown', 136 | 137 | // gettext and ngettext don't access this so that the functions 138 | // can safely bound to a different name (_ = Documentation.gettext) 139 | gettext : function(string) { 140 | var translated = Documentation.TRANSLATIONS[string]; 141 | if (typeof translated == 'undefined') 142 | return string; 143 | return (typeof translated == 'string') ? translated : translated[0]; 144 | }, 145 | 146 | ngettext : function(singular, plural, n) { 147 | var translated = Documentation.TRANSLATIONS[singular]; 148 | if (typeof translated == 'undefined') 149 | return (n == 1) ? singular : plural; 150 | return translated[Documentation.PLURALEXPR(n)]; 151 | }, 152 | 153 | addTranslations : function(catalog) { 154 | for (var key in catalog.messages) 155 | this.TRANSLATIONS[key] = catalog.messages[key]; 156 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 157 | this.LOCALE = catalog.locale; 158 | }, 159 | 160 | /** 161 | * add context elements like header anchor links 162 | */ 163 | addContextElements : function() { 164 | $('div[id] > :header:first').each(function() { 165 | $('\u00B6'). 166 | attr('href', '#' + this.id). 167 | attr('title', _('Permalink to this headline')). 168 | appendTo(this); 169 | }); 170 | $('dt[id]').each(function() { 171 | $('\u00B6'). 172 | attr('href', '#' + this.id). 173 | attr('title', _('Permalink to this definition')). 174 | appendTo(this); 175 | }); 176 | }, 177 | 178 | /** 179 | * workaround a firefox stupidity 180 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 181 | */ 182 | fixFirefoxAnchorBug : function() { 183 | if (document.location.hash) 184 | window.setTimeout(function() { 185 | document.location.href += ''; 186 | }, 10); 187 | }, 188 | 189 | /** 190 | * highlight the search words provided in the url in the text 191 | */ 192 | highlightSearchWords : function() { 193 | var params = $.getQueryParameters(); 194 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 195 | if (terms.length) { 196 | var body = $('div.body'); 197 | if (!body.length) { 198 | body = $('body'); 199 | } 200 | window.setTimeout(function() { 201 | $.each(terms, function() { 202 | body.highlightText(this.toLowerCase(), 'highlighted'); 203 | }); 204 | }, 10); 205 | $('') 207 | .appendTo($('#searchbox')); 208 | } 209 | }, 210 | 211 | /** 212 | * init the domain index toggle buttons 213 | */ 214 | initIndexTable : function() { 215 | var togglers = $('img.toggler').click(function() { 216 | var src = $(this).attr('src'); 217 | var idnum = $(this).attr('id').substr(7); 218 | $('tr.cg-' + idnum).toggle(); 219 | if (src.substr(-9) == 'minus.png') 220 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 221 | else 222 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 223 | }).css('display', ''); 224 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 225 | togglers.click(); 226 | } 227 | }, 228 | 229 | /** 230 | * helper function to hide the search marks again 231 | */ 232 | hideSearchWords : function() { 233 | $('#searchbox .highlight-link').fadeOut(300); 234 | $('span.highlighted').removeClass('highlighted'); 235 | }, 236 | 237 | /** 238 | * make the url absolute 239 | */ 240 | makeURL : function(relativeURL) { 241 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 242 | }, 243 | 244 | /** 245 | * get the current relative url 246 | */ 247 | getCurrentURL : function() { 248 | var path = document.location.pathname; 249 | var parts = path.split(/\//); 250 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 251 | if (this == '..') 252 | parts.pop(); 253 | }); 254 | var url = parts.join('/'); 255 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 256 | }, 257 | 258 | initOnKeyListeners: function() { 259 | $(document).keyup(function(event) { 260 | var activeElementType = document.activeElement.tagName; 261 | // don't navigate when in search box or textarea 262 | if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { 263 | switch (event.keyCode) { 264 | case 37: // left 265 | var prevHref = $('link[rel="prev"]').prop('href'); 266 | if (prevHref) { 267 | window.location.href = prevHref; 268 | return false; 269 | } 270 | case 39: // right 271 | var nextHref = $('link[rel="next"]').prop('href'); 272 | if (nextHref) { 273 | window.location.href = nextHref; 274 | return false; 275 | } 276 | } 277 | } 278 | }); 279 | } 280 | }; 281 | 282 | // quick alias for translations 283 | _ = Documentation.gettext; 284 | 285 | $(document).ready(function() { 286 | Documentation.init(); 287 | }); -------------------------------------------------------------------------------- /docs/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/down.png -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/plus.png -------------------------------------------------------------------------------- /docs/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #333333 } /* Generic.Output */ 19 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #902000 } /* Keyword.Type */ 29 | .highlight .m { color: #208050 } /* Literal.Number */ 30 | .highlight .s { color: #4070a0 } /* Literal.String */ 31 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 32 | .highlight .nb { color: #007020 } /* Name.Builtin */ 33 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #60add5 } /* Name.Constant */ 35 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 36 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #007020 } /* Name.Exception */ 38 | .highlight .nf { color: #06287e } /* Name.Function */ 39 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 40 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 43 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 50 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 51 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 52 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 53 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 54 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 55 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 56 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 57 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 58 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 59 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 60 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 61 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 62 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 63 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 64 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 65 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 66 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 67 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 68 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 69 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_static/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= 11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/_static/up.png -------------------------------------------------------------------------------- /docs/data_types.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | Data Types in Pyrlang — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 |

Data Types in Pyrlang

50 |
51 |

Decoding

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |
ErlangPythonNotes
atom()Pyrlang.AtomCan use str() or access text_ directly
float()float64-bit double precision floating point
integer()intAny size integers
list()list 
improper_list()(list, _Tail)A tuple with list and the tail element
unicode string()list(int)Use helper functions in list to convert to string
byte string()bytes 
tuple()tuple 
map()dict 
binary()bytes 
bitstring()(bytes, int)A tuple of bytes and last_byte_bits:int defining incomplete last byte
pid()Pyrlang.Pid 
reference()Pyrlang.Reference 
fun()Pyrlang.FunNot useful in Python
121 |
122 |
123 |

Encoding

124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |
PythonErlangNotes
Pyrlang.Atomatom() 
floatfloat() 
intinteger() 
listlist() 
Pyrlang.ImproperListimproper_list() 
list(int)string() 
bytesbinary() 
tupletuple() 
dictmap() 
Pyrlang.Bitstringbitstring()A binary with last byte incomplete
Pyrlang.Pidpid() 
Pyrlang.Referencereference() 
Pyrlang.Funfun()Not useful in Python
other objects#{'ClassName', #{}}Encoding may fail on some types
193 |
194 |
195 |

Lists

196 |

Erlang lists can be of 2 kinds:

197 |
    198 |
  • Regular lists of anything:
      199 |
    • Unicode strings, which are regular lists of integers in unicode range.
    • 200 |
    • 8-bit strings, sometimes called latin-1 or ASCII, which are regular lists 201 | of bytes.
    • 202 |
    203 |
  • 204 |
  • Improper lists (those with last cell's tail being not [] NIL).
  • 205 |
206 |

Pyrlang always decodes incoming regular lists as Python lists, 207 | use helper functions in list to extract strings. 208 | If incoming string contained only bytes (integers between 0 and 255) then 209 | Erlang node will optimize the encoding and send a byte array. In this case you 210 | will receive Python bytes string, and not a list, which is 211 | the same as if you have sent a binary, so to reduce confusion always send 212 | strings as UTF8 binaries.

213 |

A regular Erlang list always has an invisible [] (NIL) set as tail of 214 | its last cons cell. Regular lists map directly to Python lists and 215 | there is no easy way to tell a list of integers from a string other than 216 | check elements ranges and assume that is a printable string. Unless you received 217 | Python bytes then it's easy.

218 |

An improper Erlang list has some other value than [] NIL as the tail of 219 | its last cell. 220 | Pyrlang returns these as Python tuple (list, tail). 221 | To tell Pyrlang encoder to send an improper list back to Erlang, use the 222 | ImproperList class.

223 |
224 |
225 |

Binaries

226 |

Pyrlang always decodes incoming Erlang binaries into Python bytes objects.

227 |

Bitstrings are decoded as Python pairs of (bytes, last_byte_bits:int) 228 | To be able to send a bitstring back to Erlang, use class 229 | BitString.

230 |
231 |
232 | 233 | 234 |
235 | 236 |
237 |
238 | 284 |
285 |
286 | 297 | 298 | 299 | 300 | 301 | 302 | -------------------------------------------------------------------------------- /docs/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | Index — Pyrlang Terms 1.0 documentation 11 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 | 44 | 45 |
46 | 47 | 48 |

Index

49 | 50 |
51 | A 52 | | B 53 | | E 54 | | F 55 | | G 56 | | I 57 | | L 58 | | N 59 | | P 60 | | R 61 | | T 62 | 63 |
64 |

A

65 | 66 | 70 |
71 | 72 |

B

73 | 74 | 78 | 84 |
85 | 86 |

E

87 | 88 | 98 |
99 | 100 |

F

101 | 102 | 106 |
107 | 108 |

G

109 | 110 | 114 |
115 | 116 |

I

117 | 118 | 122 | 128 |
129 | 130 |

L

131 | 132 | 136 | 140 |
141 | 142 |

N

143 | 144 | 152 |
153 | 154 |

P

155 | 156 | 160 | 164 |
165 | 166 |

R

167 | 168 | 172 |
173 | 174 |

T

175 | 176 | 188 | 202 |
203 | 204 | 205 | 206 |
207 | 208 |
209 |
210 | 239 |
240 |
241 | 249 | 250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | Pyrlang Term Library — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 | 44 | 45 |
46 | 47 |
48 |

Pyrlang Term Library

49 |

Term is a Python 3.5 library which supports Erlang Term encoding, decoding and 50 | representation as Python values. There are identical implementations of codec 51 | in Python and in Rust, if your machine doesn't have Rust compiler installed, 52 | the slower Python implementation will be used automatically.

53 |

This library has no dependencies on other parts of Pyrlang project and can be 54 | used standalone.

55 |

Important APIs are:

56 |
from term import codec, Atom
 57 | t = codec.term_to_binary((1, Atom('ok')))
 58 | # Function term_to_binary will prepend a 131 byte tag
 59 | # Function term_to_binary_2 encodes without a leading 131 byte
 60 | 
 61 | (val, tail) = codec.binary_to_term(bytes)
 62 | # Returns a pair: value and remaining bytes
 63 | # Function binary_to_term strips leading 131 byte and also handles
 64 | # decompression of an eventually compressed term
 65 | # Function binary_to_term_2 decodes without a leading 131 byte
 66 | 
67 |
68 | 88 |
89 | 90 | 91 |
92 | 93 |
94 |
95 | 129 |
130 |
131 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pyrlang/Term/bf762693bff43d7ccad15c854aa075f849bd0dbc/docs/objects.inv -------------------------------------------------------------------------------- /docs/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | Python Module Index — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 |
45 | 46 | 47 |
48 | 49 | 50 |

Python Module Index

51 | 52 |
53 | t 54 |
55 | 56 | 57 | 58 | 60 | 61 | 63 | 66 | 67 | 68 | 71 | 72 | 73 | 76 | 77 | 78 | 81 | 82 | 83 | 86 | 87 | 88 | 91 | 92 | 93 | 96 | 97 | 98 | 101 | 102 | 103 | 106 |
 
59 | t
64 | term 65 |
    69 | term.atom 70 |
    74 | term.bitstring 75 |
    79 | term.erl_typing 80 |
    84 | term.fun 85 |
    89 | term.list 90 |
    94 | term.pid 95 |
    99 | term.py_codec_impl 100 |
    104 | term.reference 105 |
107 | 108 | 109 |
110 | 111 |
112 |
113 | 139 |
140 |
141 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | Search — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 |
50 | 51 | 52 |
53 | 54 |

Search

55 |
56 | 57 |

58 | Please activate JavaScript to enable the search 59 | functionality. 60 |

61 |
62 |

63 | From here you can search these documents. Enter your search 64 | words into the box below and click "search". Note that the search 65 | function will automatically search for all of the words. Pages 66 | containing fewer words won't appear in the result list. 67 |

68 |
69 | 70 | 71 | 72 |
73 | 74 |
75 | 76 |
77 | 78 |
79 | 80 |
81 |
82 | 98 |
99 |
100 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({docnames:["data_types","index","term.atom","term.bitstring","term.erl_typing","term.fun","term.list","term.pid","term.py_codec_impl","term.reference"],envversion:53,filenames:["data_types.rst","index.rst","term.atom.rst","term.bitstring.rst","term.erl_typing.rst","term.fun.rst","term.list.rst","term.pid.rst","term.py_codec_impl.rst","term.reference.rst"],objects:{"term.atom":{Atom:[2,1,1,""]},"term.atom.Atom":{equals:[2,2,1,""],text_:[2,3,1,""]},"term.bitstring":{BitString:[3,1,1,""]},"term.fun":{Fun:[5,1,1,""]},"term.list":{ImproperList:[6,1,1,""],list_to_str:[6,4,1,""],list_to_unicode_str:[6,4,1,""]},"term.pid":{Pid:[7,1,1,""]},"term.pid.Pid":{equals:[7,2,1,""],is_local_to:[7,2,1,""],node_name_:[7,3,1,""]},"term.py_codec_impl":{PyCodecError:[8,5,1,""],binary_to_term:[8,4,1,""],binary_to_term_2:[8,4,1,""],generic_serialize_object:[8,4,1,""],term_to_binary:[8,4,1,""],term_to_binary_2:[8,4,1,""]},"term.reference":{Reference:[9,1,1,""]},"term.reference.Reference":{equals:[9,2,1,""],id_:[9,3,1,""],node_name_:[9,3,1,""]},term:{atom:[2,0,0,"-"],bitstring:[3,0,0,"-"],erl_typing:[4,0,0,"-"],fun:[5,0,0,"-"],list:[6,0,0,"-"],pid:[7,0,0,"-"],py_codec_impl:[8,0,0,"-"],reference:[9,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","attribute","Python attribute"],"4":["py","function","Python function"],"5":["py","exception","Python exception"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:attribute","4":"py:function","5":"py:exception"},terms:{"byte":[0,1,3,6,8,9],"case":0,"class":[0,2,3,5,6,7,8,9],"default":8,"float":0,"function":[0,1,5,6,8],"import":1,"int":[0,3,7,8,9],"return":[0,1,8],Not:[0,5],The:8,There:1,Use:0,_tail:0,abl:[0,3],access:0,after:8,again:8,almost:8,also:[1,7],alwai:[0,7],ani:[0,8],anoth:8,anyth:0,api:1,arbitrari:[7,8],arbitrati:8,ariti:5,arrai:0,ascii:[0,6],assist:8,assum:[0,7,9],atom:[0,1,8],automat:[1,7],avoid:8,back:[0,2,3,6],base:[2,3,5,6,7,8,9],basepid:7,becom:8,been:8,befor:8,being:0,between:0,binari:[1,8],binary_to_term:[1,8],binary_to_term_2:[1,8],bit:[0,8],bitstr:[0,1,8],bool:[2,7,9],born:7,built:8,byte_str:8,call:0,callabl:[5,8],can:[0,1,2,7,8],captur:5,cell:[0,6],chang:9,check:[0,8],classnam:[0,8],cluster:7,codec:[1,9],come:9,compil:1,compon:7,compress:[1,8],con:0,confus:0,consum:8,contain:[0,8],content:1,convert:[0,6],corrupt:8,creat:[7,8,9],creation:[7,9],cycl:8,cycledetect:8,cyclic:8,data:[1,3,6,8,9],decod:[1,2,8],decompress:1,defin:0,depend:1,detect:8,dict:[0,8],dictionari:2,directli:0,distribut:8,doesn:1,doubl:0,easi:0,effort:8,element:[0,6],elements_:8,elixir:3,encod:[1,2,7,8],encode_hook:8,equal:[2,7,9],erl_typ:1,erlang:[0,1,2,3,5,6,7,8,9],error:8,etf:8,eventu:1,ever:6,except:8,exist:8,extern:8,extract:[0,8],fail:0,fair:8,field:8,format:8,free:5,from:[0,1,2,5,6,8,9],fun:[0,1],gener:7,generic_serialize_object:8,given:8,guarante:9,handl:1,has:[0,1,8,9],have:[0,1],header:8,helper:[0,1,6,8],holder:[3,6],id_:9,ident:1,identif:9,identifi:7,ids:8,implement:[1,8],improp:[0,6],improper_list:0,improperlist:[0,6],incom:[0,6,8],incomplet:[0,3,8],index:5,infinit:8,instal:1,instead:6,integ:[0,6,7],invis:0,is_local_to:7,its:0,itself:7,kei:2,kind:0,larg:6,last:[0,3,6],last_byte_bit:[0,3,8],latin:0,layer:8,lead:[1,8],like:6,list:[1,8],list_to_str:6,list_to_unicode_str:6,local:7,look:6,lst:6,machin:1,made:8,mai:0,map:[0,8],might:9,mod:5,modul:1,much:8,nativ:[7,9],need:6,network:8,nil:[0,6],node:[0,3,7,9],node_nam:[7,9],node_name_:[7,9],none:[2,7,8,9],nonetyp:8,note:[0,7,9],obj:8,object:[0,2,3,5,6,8,9],old_index:5,old_uniq:5,one:7,onli:0,opt:8,optim:0,option:8,other:[0,1,2,7,9],otherwis:7,pair:[0,1,8],param:8,paramet:8,pars:8,part:1,pass:[3,6],pid:[0,1,5,8],point:0,pointer:5,possibl:8,precis:0,prepend:[1,8],printabl:0,proce:8,process:7,project:1,py_codec_impl:1,pycodecerror:8,pyrlang:7,python:[0,1,2,5,8],rais:8,rang:0,rare:6,receiv:0,recurs:8,reduc:0,ref:[8,9],refer:[0,1,8],refid:9,regist:7,registri:7,regular:0,remain:[1,8],remov:8,repres:[5,7,8,9],represent:[1,2,8],result:8,run:7,rust:1,same:0,send:0,sent:0,serial:7,serv:2,set:[0,8],simpl:[3,6],size:0,slot:6,slower:1,some:[0,5],sometim:0,standalon:1,store:2,str:[0,2,6,7,8,9],string:[0,2,6,7,8,9],strip:[1,8],style:7,support:[1,2],tag:[1,8],tail:[0,1,6,8],tail_:8,take:[6,7],tell:0,term_to_binari:[1,8],term_to_binary_2:[1,8],text:2,text_:[0,2],than:0,thi:[0,1,6,7,9],those:0,tupl:[0,8],type:[1,6,8],typic:9,unconsum:8,undefin:8,unicod:[0,2,6,8],union:8,uniq:5,uniqu:[7,9],unknown:8,unless:0,unpack:8,use:0,used:[1,6,8],useful:0,utf8:0,val:[1,3,8],valu:[0,1,5,7,8,9],variabl:5,variou:8,veri:6,wai:0,when:[7,8],where:6,which:[0,1,8],whose:8,without:[1,8],you:[0,6],your:1},titles:["Data Types in Pyrlang","Pyrlang Term Library","term.atom module","term.bitstring module","term.erl_typing helper module","term.fun module","term.list module","term.pid module","term.py_codec_impl module","term.reference module"],titleterms:{atom:2,binari:0,bitstr:3,data:0,decod:0,encod:0,erl_typ:4,fun:5,helper:4,librari:1,list:[0,6],modul:[2,3,4,5,6,7,8,9],pid:7,py_codec_impl:8,pyrlang:[0,1],refer:9,term:[1,2,3,4,5,6,7,8,9],type:0}}) -------------------------------------------------------------------------------- /docs/term.atom.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | term.atom module — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 |

term.atom module

50 |
51 |
52 | class term.atom.Atom(text: str) → None
53 |

Bases: object

54 |

Stores a string decoded from Erlang atom. Encodes back to atom. 55 | Can serve as a Python dictionary key.

56 |
57 |
58 | equals(other) → bool
59 |
60 | 61 |
62 |
63 | text_ = None
64 |

Atom's text representation, supports unicode

65 |
66 | 67 |
68 | 69 |
70 | 71 | 72 |
73 | 74 |
75 |
76 | 111 |
112 |
113 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/term.bitstring.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | term.bitstring module — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 |

term.bitstring module

50 |
51 |
52 | class term.bitstring.BitString(val: bytes, last_byte_bits: int)
53 |

Bases: object

54 |

A simple data holder to be able to pass bitstrings (bytes with 55 | incomplete last byte) back to Erlang/Elixir node.

56 |
57 | 58 |
59 | 60 | 61 |
62 | 63 |
64 |
65 | 100 |
101 |
102 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /docs/term.erl_typing.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | term.erl_typing helper module — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 |

term.erl_typing helper module

50 |
51 | 52 | 53 |
54 | 55 |
56 |
57 | 92 |
93 |
94 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/term.fun.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | term.fun module — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 |

term.fun module

50 |
51 |
52 | class term.fun.Fun(mod, arity, pid, index, uniq, old_index, old_uniq, free)
53 |

Bases: object

54 |

Represents a pointer to a function in Erlang, with some variable 55 | values captured. Not callable from Python.

56 |
57 | 58 |
59 | 60 | 61 |
62 | 63 |
64 |
65 | 100 |
101 |
102 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /docs/term.list.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | term.list module — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 |

term.list module

50 |
51 |
52 | class term.list.ImproperList(elements: list, tail)
53 |

Bases: object

54 |

A simple data holder used to pass improper lists back to Erlang. 55 | An Erlang improper list looks like [1, 2, 3 | 4] where 4 takes 56 | tail slot of last list cell instead of NIL. This is a rare data type 57 | and very likely you will not ever need it.

58 |
59 | 60 |
61 |
62 | term.list.list_to_str(lst: list) → str
63 |

A helper function to convert a list of bytes (0..255) into an 64 | ASCII string.

65 |
66 | 67 |
68 |
69 | term.list.list_to_unicode_str(lst: list) → str
70 |

A helper function to convert a list of large integers incoming from 71 | Erlang into a unicode string.

72 |
73 | 74 |
75 | 76 | 77 |
78 | 79 |
80 |
81 | 116 |
117 |
118 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /docs/term.pid.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | term.pid module — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 |

term.pid module

50 |
51 |
52 | class term.pid.Pid(node_name: str, id: int, serial: int, creation: int) → None
53 |

Bases: term.bases.BasePid

54 |

Represents Erlang-style process identifier with 3 components. Node 55 | component is always 0 for local node, otherwise it can take arbitrary 56 | integer values.

57 |

A pid is created by Erlang automatically. Pyrlang Process also generates 58 | one when it is born to register itself in the process registry. Pid 59 | uniquely identifies a running process in the cluster.

60 |
61 |
62 | equals(other) → bool
63 |
64 | 65 |
66 |
67 | is_local_to(node)
68 |
69 | 70 |
71 |
72 | node_name_ = None
73 |

NOTE: native encoder assumes this is a string.

74 |
75 | 76 |
77 | 78 |
79 | 80 | 81 |
82 | 83 |
84 |
85 | 120 |
121 |
122 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /docs/term.py_codec_impl.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | term.py_codec_impl module — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 |

term.py_codec_impl module

50 |

Module implements encoder and decoder from ETF (Erlang External Term Format) 51 | used by the network distribution layer.

52 |
53 |
54 | term.py_codec_impl.binary_to_term(data: bytes, options: dict = None) -> (<built-in function any>, <class 'bytes'>)
55 |

Strip 131 header and unpack if the data was compressed.

56 | 57 | 58 | 59 | 60 | 71 | 72 | 75 | 76 | 78 | 79 | 80 |
Parameters:
    61 |
  • data -- The incoming encoded data with the 131 byte
  • 62 |
  • options --
      63 |
    • "atom": "str" | "bytes" | "Atom" (default "Atom"). 64 | Returns atoms as strings, as bytes or as atom.Atom objects.
    • 65 |
    • "byte_string": "str" | "bytes" (default "str"). 66 | Returns 8-bit strings as Python str or bytes.
    • 67 |
    68 |
  • 69 |
70 |
Raises:

PyCodecError -- when the tag is not 131, when compressed 73 | data is incomplete or corrupted

74 |
Returns:

Remaining unconsumed bytes

77 |
81 |
82 | 83 |
84 |
85 | term.py_codec_impl.binary_to_term_2(data: bytes, options: dict = None) -> (<built-in function any>, <class 'bytes'>)
86 |

Proceed decoding after leading tag has been checked and removed.

87 |

Erlang lists are decoded into term.List object, whose elements_ 88 | field contains the data, tail_ field has the optional tail and a 89 | helper function exists to assist with extracting an unicode string.

90 |

Atoms are decoded to Atom or optionally to bytes 91 | or to strings. 92 | Pids are decoded into Pid. 93 | Refs decoded to and Reference. 94 | Maps are decoded into Python dict. 95 | Binaries are decoded into bytes 96 | object and bitstrings into a pair of (bytes, last_byte_bits:int).

97 | 98 | 99 | 100 | 101 | 118 | 119 | 124 | 125 | 127 | 128 | 129 |
Parameters:
    102 |
  • options --
    103 |
    dict(str, _);
    104 |
      105 |
    • "atom": "str" | "bytes" | "Atom"; default "Atom". 106 | Returns atoms as strings, as bytes or as atom.Atom class objects
    • 107 |
    108 |
    109 |
    110 |
      111 |
    • "byte_string": "str" | "bytes" (default "str"). 112 | Returns 8-bit strings as Python str or bytes.
    • 113 |
    114 |
  • 115 |
  • data -- Bytes containing encoded term without 131 header
  • 116 |
117 |
Returns:

Tuple[Value, Tail: bytes] 120 | The function consumes as much data as 121 | possible and returns the tail. Tail can be used again to parse 122 | another term if there was any.

123 |
Raises:

PyCodecError(str) -- on various errors

126 |
130 |
131 | 132 |
133 |
134 | term.py_codec_impl.term_to_binary(val, opt: typing.Union[NoneType, dict] = None) → bytes
135 |

Prepend the 131 header byte to encoded data. 136 | :param opt: None or dict of options: "encode_hook" is a callable which

137 |
138 |
will return representation for unknown object types. Returning 139 | None will be encoded as such and becomes Atom('undefined').
140 |
141 | 142 |
143 |
144 | term.py_codec_impl.term_to_binary_2(val, encode_hook: typing.Union[typing.Callable, NoneType]) → bytes
145 |

Erlang lists are decoded into term.List object, whose elements_ 146 | field contains the data, tail_ field has the optional tail and a 147 | helper function exists to assist with extracting an unicode string.

148 | 149 | 150 | 151 | 152 | 159 | 160 | 162 | 163 | 164 |
Parameters:
    153 |
  • encode_hook -- None or a callable which will represent an unknown 154 | object as an Erlang term before encoding. Returning None will be 155 | encoded as such and becomes Atom('undefined').
  • 156 |
  • val -- Almost any Python value
  • 157 |
158 |
Returns:

bytes object with encoded data, but without a 131 header byte.

161 |
165 |
166 | 167 |
168 |
169 | exception term.py_codec_impl.PyCodecError
170 |

Bases: Exception

171 |
172 | 173 |
174 |
175 | term.py_codec_impl.generic_serialize_object(obj, cd: set = None)
176 |

Given an arbitraty Python object creates a tuple (ClassName, {Fields}). 177 | A fair effort is made to avoid infinite recursion on cyclic objects. 178 | :param obj: Arbitrary object to encode 179 | :param cd: A set with ids of object, for cycle detection 180 | :return: A pair of result: (ClassName :: bytes(), Fields :: {bytes(), _})

181 |
182 |
or None, and a CycleDetect value
183 |
184 | 185 |
186 | 187 | 188 |
189 | 190 |
191 |
192 | 227 |
228 |
229 | 240 | 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /docs/term.reference.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | term.reference module — Pyrlang Terms 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 | 44 | 45 |
46 | 47 |
48 |

term.reference module

49 |
50 |
51 | class term.reference.Reference(node_name: str, creation: int, refid: bytes) → None
52 |

Bases: object

53 |

Represents a reference value from Erlang, typically it has 12 bytes of 54 | unique data, but it might change.

55 |
56 |
57 | equals(other) → bool
58 |
59 | 60 |
61 |
62 | id_ = None
63 |

Identification bytes, guaranteed to be unique on the creating node

64 |
65 | 66 |
67 |
68 | node_name_ = None
69 |

Node the ref comes from. NOTE: native codec assumes this is a string.

70 |
71 | 72 |
73 | 74 |
75 | 76 | 77 |
78 | 79 |
80 |
81 | 115 |
116 |
117 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "setuptools-rust"] 3 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # Documentation requirements 2 | Jinja2>=2.10.1 3 | MarkupSafe>=1.0 4 | Pillow>=4.0.0 5 | Pygments>=2.2.0 6 | Sphinx>=1.6.2 7 | 8 | # Setup/Rust build requires 9 | setuptools_rust>=0.10.5 10 | semantic_version>=2.6.0 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Setup/Rust build requires 2 | setuptools_rust>=0.10.5 3 | semantic_version>=2.6.0 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | VERSION = '1.5-alpha' 4 | PKGNAME = "pyrlang-term" 5 | MODULENAME = "term" 6 | DESCR = 'Erlang term and External Term Format codec in Python and native Rust extension' 7 | AUTHOR = 'Erlang Solutions Ltd and S2HC Sweden AB' 8 | AUTHOR_EMAIL = 'dmytro.lytovchenko@gmail.com, pyrlang@s2hc.com' 9 | URL = "https://github.com/Pyrlang/Term" 10 | 11 | with open("README.md", "r", encoding='utf-8') as fp: 12 | LONG_DESCRPTION = fp.read() 13 | LONG_DESCRPTION_CONTENT_TYPE = "text/markdown" 14 | 15 | try: 16 | from setuptools_rust import Binding, RustExtension 17 | 18 | setup(name=PKGNAME, 19 | version=VERSION, 20 | url=URL, 21 | description=DESCR, 22 | long_description=LONG_DESCRPTION, 23 | long_description_content_type=LONG_DESCRPTION_CONTENT_TYPE, 24 | author=AUTHOR, 25 | author_email=AUTHOR_EMAIL, 26 | rust_extensions=[RustExtension("term.native_codec_impl", 27 | binding=Binding.RustCPython)], 28 | packages=[MODULENAME], 29 | # rust extensions are not zip safe, just like C-extensions. 30 | zip_safe=False) 31 | except Exception as e: 32 | print("----------------------------") 33 | print("Rust build failed, continue with Python slow implementation only") 34 | print("error was:", e) 35 | print("----------------------------") 36 | 37 | setup(name=PKGNAME, 38 | version=VERSION, 39 | url=URL, 40 | description=DESCR, 41 | long_description=LONG_DESCRPTION, 42 | long_description_content_type=LONG_DESCRPTION_CONTENT_TYPE, 43 | author=AUTHOR, 44 | author_email=AUTHOR_EMAIL, 45 | packages=[MODULENAME]) 46 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![allow(unused)] 16 | 17 | pub const ETF_VERSION_TAG: u8 = 131; 18 | 19 | pub const TAG_ATOM_EXT: u8 = 100; 20 | pub const TAG_ATOM_UTF8_EXT: u8 = 118; 21 | 22 | pub const TAG_BINARY_EXT: u8 = 109; 23 | pub const TAG_BIT_BINARY_EXT: u8 = 77; 24 | 25 | pub const TAG_COMPRESSED: u8 = 80; 26 | 27 | pub const TAG_FLOAT_EXT: u8 = 99; 28 | 29 | pub const TAG_INT: u8 = 98; 30 | 31 | pub const TAG_LARGE_BIG_EXT: u8 = 111; 32 | pub const TAG_LARGE_TUPLE_EXT: u8 = 105; 33 | pub const TAG_LIST_EXT: u8 = 108; 34 | 35 | pub const TAG_MAP_EXT: u8 = 116; 36 | 37 | pub const TAG_NEW_FLOAT_EXT: u8 = 70; 38 | pub const TAG_NEW_FUN_EXT: u8 = 112; 39 | pub const TAG_NEW_REF_EXT: u8 = 114; 40 | pub const TAG_NIL_EXT: u8 = 106; 41 | 42 | pub const TAG_PID_EXT: u8 = 103; 43 | 44 | pub const TAG_SMALL_ATOM_EXT: u8 = 115; 45 | pub const TAG_SMALL_ATOM_UTF8_EXT: u8 = 119; 46 | pub const TAG_SMALL_BIG_EXT: u8 = 110; 47 | pub const TAG_SMALL_UINT: u8 = 97; 48 | pub const TAG_SMALL_TUPLE_EXT: u8 = 104; 49 | pub const TAG_STRING_EXT: u8 = 107; 50 | 51 | pub const TAG_NEW_PID_EXT: u8 = 88; 52 | pub const TAG_NEWER_REF_EXT: u8 = 90; 53 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::{convert::From, str::Utf8Error}; 16 | 17 | use cpython::*; 18 | 19 | use crate::reader::ReadError; 20 | 21 | use super::PyCodecError; 22 | 23 | use thiserror::Error; 24 | 25 | #[derive(Error, Debug)] 26 | pub enum CodecError { 27 | #[error("ETF version 131 is expected")] 28 | UnsupportedETFVersion, 29 | #[error("Compressed size does not match decompressed")] 30 | CompressedSizeMismatch, 31 | #[error("Read failed")] 32 | ReadError(#[from] ReadError), 33 | #[error("{txt}")] 34 | PythonError { txt: String, error: PyErr }, 35 | #[error("Unrecognized term tag byte: {}", b)] 36 | UnknownTermTagByte { b: u8 }, 37 | #[error("Bad options passed: {}", txt)] 38 | BadOptions { txt: String }, 39 | #[error("Integer {} is too large (> 32bit): big integers not impl", i)] 40 | IntegerEncodingRange { i: i64 }, 41 | #[error("Float value {} is not finite", f)] 42 | NonFiniteFloat { f: f64 }, 43 | #[error("IOError")] 44 | IOError(#[from] std::io::Error), 45 | #[error("Encoding error")] 46 | EncodingError(#[from] Utf8Error), 47 | #[error("Atom too long")] 48 | AtomTooLong, 49 | } 50 | 51 | pub type CodecResult = Result; 52 | 53 | impl From for CodecError { 54 | fn from(err: PyErr) -> Self { 55 | CodecError::PythonError { 56 | txt: format!("{:?}", err), 57 | error: err, 58 | } 59 | } 60 | } 61 | 62 | impl From for PyErr { 63 | /// Somehow this works. Create a PyErr struct without traceback, containing 64 | /// a PyCodecError created from Rust CodecError with string explanation. 65 | fn from(err: CodecError) -> PyErr { 66 | let gil = Python::acquire_gil(); 67 | let py = gil.python(); 68 | let ty = py.get_type::(); 69 | 70 | // CodecErrors are formatted using #[fail...] attribute format string 71 | let err_str = format!("{}", err); 72 | let py_str = PyString::new(py, &err_str); 73 | let noargs = PyTuple::new(py, &[py_str.into_object()]); 74 | let err_val = ty.call(py, noargs, None).unwrap(); 75 | 76 | let tyo = ty.into_object(); 77 | PyErr { 78 | ptype: tyo, 79 | pvalue: Some(err_val), 80 | ptraceback: None, 81 | } 82 | } 83 | } 84 | 85 | /// Repacks CodecResult into PyResult 86 | pub fn pyresult_from(r: Result) -> Result { 87 | match r { 88 | Ok(x) => Ok(x), 89 | Err(e) => Err(PyErr::from(e)), 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use cpython::{FromPyObject, PyDict, PyObject, PyString, Python}; 16 | 17 | use super::errors::{CodecError, CodecResult}; 18 | 19 | /// Get dict value with string key, expect it to be string too, or return 20 | /// the default value. 21 | pub fn get_str_opt(py: Python, opts: &PyDict, optname: &str, default: &str) -> CodecResult { 22 | match opts.get_item(py, optname) { 23 | Some(val) => { 24 | let py_str: PyString = PyString::extract(py, &val)?; 25 | let s = py_str.to_string_lossy(py).into_owned(); 26 | Ok(s) 27 | } 28 | None => Ok(default.to_string()), 29 | } 30 | } 31 | 32 | /// Given a dict or a possibly None, return dict 33 | pub fn maybe_dict(py: Python, dict_or_none: PyObject) -> PyDict { 34 | if dict_or_none == py.None() { 35 | PyDict::new(py) 36 | } else { 37 | PyDict::extract(py, &dict_or_none).unwrap() 38 | } 39 | } 40 | 41 | #[derive(Eq, PartialEq, Copy, Clone)] 42 | pub enum AtomRepresentation { 43 | TermAtom, 44 | TermStrictAtom, 45 | Bytes, 46 | Str, 47 | } 48 | 49 | #[derive(Eq, PartialEq)] 50 | pub enum ByteStringRepresentation { 51 | Bytes, 52 | Str, 53 | IntList, 54 | } 55 | 56 | /// Option: "atom" => "bytes" | "str" | "Atom" | "StrictAtom" (as Atom class, default) 57 | pub fn get_atom_opt(py: Python, opts1: &PyDict) -> CodecResult { 58 | let opt_s = get_str_opt(py, opts1, "atom", "Atom")?; 59 | match opt_s.as_ref() { 60 | "bytes" => Ok(AtomRepresentation::Bytes), 61 | "str" => Ok(AtomRepresentation::Str), 62 | "Atom" => Ok(AtomRepresentation::TermAtom), 63 | "StrictAtom" => Ok(AtomRepresentation::TermStrictAtom), 64 | other => { 65 | let txt = format!( 66 | "'atom' option is '{}' while expected: bytes, str, Atom, StrictAtom", 67 | other 68 | ); 69 | Err(CodecError::BadOptions { txt }) 70 | } 71 | } 72 | } 73 | 74 | /// Option: "byte_string" => "bytes" | "str" | "int_list" (default: str) 75 | pub fn get_byte_str_opt(py: Python, opts1: &PyDict) -> CodecResult { 76 | let opt_s: String = get_str_opt(py, opts1, "byte_string", "str")?; 77 | match opt_s.as_ref() { 78 | "bytes" => Ok(ByteStringRepresentation::Bytes), 79 | "str" => Ok(ByteStringRepresentation::Str), 80 | "int_list" => Ok(ByteStringRepresentation::IntList), 81 | other => { 82 | let txt = format!( 83 | "'byte_string' option is '{}' while expected: bytes, str", 84 | other 85 | ); 86 | Err(CodecError::BadOptions { txt }) 87 | } 88 | } 89 | } 90 | 91 | pub trait VecWriteExt { 92 | fn push_u32(&mut self, value: u32); 93 | fn push_i32(&mut self, value: i32); 94 | fn push_u16(&mut self, value: u16); 95 | fn push_f64(&mut self, value: f64); 96 | } 97 | 98 | impl VecWriteExt for Vec { 99 | fn push_u32(&mut self, value: u32) { 100 | self.extend(value.to_be_bytes()); 101 | } 102 | 103 | fn push_i32(&mut self, value: i32) { 104 | self.extend(value.to_be_bytes()); 105 | } 106 | 107 | fn push_u16(&mut self, value: u16) { 108 | self.extend(value.to_be_bytes()); 109 | } 110 | 111 | fn push_f64(&mut self, value: f64) { 112 | self.extend(value.to_be_bytes()); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // For cpython macros 16 | #![allow(unused_braces, clippy::manual_strip)] 17 | 18 | use cpython::{ 19 | py_exception, py_fn, py_module_initializer, PyBytes, PyModule, PyObject, PyResult, Python, 20 | }; 21 | 22 | use self::decoder::Decoder; 23 | use self::encoder::Encoder; 24 | use self::errors::pyresult_from; 25 | 26 | mod consts; 27 | mod decoder; 28 | mod encoder; 29 | mod errors; 30 | mod helpers; 31 | mod reader; 32 | 33 | py_exception!(native_codec_impl, PyCodecError); 34 | 35 | /// Strips 131 byte header and unpacks if the data was compressed. 36 | fn binary_to_term(py: Python, b: PyBytes, opts: PyObject) -> PyResult { 37 | let mut dec_state = Decoder::new(py, opts)?; 38 | let mut reader = b.data(py).into(); 39 | pyresult_from(dec_state.decode_with_131tag(&mut reader)) 40 | } 41 | 42 | fn binary_to_term_2(py: Python, b: PyBytes, opts: PyObject) -> PyResult { 43 | let mut dec_state = Decoder::new(py, opts)?; 44 | let mut reader = b.data(py).into(); 45 | let result = dec_state.decode(&mut reader); 46 | pyresult_from(result) 47 | } 48 | 49 | fn term_to_binary(py: Python, py_term: PyObject, opt: PyObject) -> PyResult { 50 | let mut enc_state = Encoder::new(py, opt)?; 51 | 52 | // Rest of the function is identical to ``term_to_binary_2`` except that 53 | // 131 byte is pushed to the output before the encoder is called 54 | enc_state.data.push(consts::ETF_VERSION_TAG); 55 | 56 | enc_state.encode(&py_term)?; 57 | Ok(PyBytes::new(py, enc_state.data.as_ref())) 58 | } 59 | 60 | fn term_to_binary_2(py: Python, py_term: PyObject, opt: PyObject) -> PyResult { 61 | let mut enc_state = Encoder::new(py, opt)?; 62 | enc_state.encode(&py_term)?; 63 | Ok(PyBytes::new(py, enc_state.data.as_ref())) 64 | } 65 | 66 | // add bindings to the generated python module 67 | // N.B: names: "librust2py" must be the name of the `.so` or `.pyd` file 68 | #[inline] 69 | fn m_init(py: Python, m: &PyModule) -> PyResult<()> { 70 | m.add(py, "__doc__", "Erlang Term Format encoding and decoding.")?; 71 | m.add( 72 | py, 73 | "binary_to_term", 74 | py_fn!(py, binary_to_term(b: PyBytes, opt: PyObject)), 75 | )?; 76 | m.add( 77 | py, 78 | "binary_to_term_2", 79 | py_fn!(py, binary_to_term_2(b: PyBytes, opt: PyObject)), 80 | )?; 81 | m.add( 82 | py, 83 | "term_to_binary", 84 | py_fn!(py, term_to_binary(py_term: PyObject, opt: PyObject)), 85 | )?; 86 | m.add( 87 | py, 88 | "term_to_binary_2", 89 | py_fn!(py, term_to_binary_2(py_term: PyObject, opt: PyObject)), 90 | )?; 91 | m.add(py, "PyCodecError", py.get_type::())?; 92 | Ok(()) 93 | } 94 | py_module_initializer!( 95 | native_codec_impl, 96 | initnative_codec_impl, 97 | PyInit_native_codec_impl, 98 | |py, m| { m_init(py, m) } 99 | ); 100 | -------------------------------------------------------------------------------- /src/reader.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Erlang Solutions Ltd, and S2HC Sweden AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use thiserror::Error; 16 | 17 | use std::array::TryFromSliceError; 18 | use std::io::{BufRead, Read}; 19 | 20 | type ReadResult = Result; 21 | 22 | pub struct Reader<'a> { 23 | data: &'a [u8], 24 | offset: usize, 25 | } 26 | 27 | impl<'a> From<&'a [u8]> for Reader<'a> { 28 | fn from(data: &'a [u8]) -> Self { 29 | Self { data, offset: 0 } 30 | } 31 | } 32 | 33 | impl<'a> From<&'a Vec> for Reader<'a> { 34 | fn from(data: &'a Vec) -> Self { 35 | Self { data, offset: 0 } 36 | } 37 | } 38 | 39 | // impl<'a> From<&'a [u8]> for Reader<'a> { 40 | // fn from(data: &'a [u8]) -> Self { 41 | // Self { data, offset: 0 } 42 | // } 43 | // } 44 | 45 | impl<'a> Reader<'a> { 46 | pub fn read_u32(&mut self) -> ReadResult { 47 | Ok(u32::from_be_bytes(self.read(4)?.try_into()?)) 48 | } 49 | 50 | pub fn read_i32(&mut self) -> ReadResult { 51 | Ok(i32::from_be_bytes(self.read(4)?.try_into()?)) 52 | } 53 | 54 | pub fn read_u16(&mut self) -> ReadResult { 55 | Ok(u16::from_be_bytes(self.read(2)?.try_into()?)) 56 | } 57 | 58 | pub fn read_f64(&mut self) -> ReadResult { 59 | Ok(f64::from_be_bytes(self.read(8)?.try_into()?)) 60 | } 61 | 62 | pub fn read_u8(&mut self) -> ReadResult { 63 | Ok(self.read(1)?[0]) 64 | } 65 | 66 | pub fn read_with(&mut self) -> ReadResult { 67 | T::do_read(self) 68 | } 69 | 70 | pub fn peek(&self) -> ReadResult { 71 | if self.offset < self.data.len() { 72 | Ok(self.data[self.offset]) 73 | } else { 74 | Err(ReadError::BufferTooShort) 75 | } 76 | } 77 | 78 | pub fn read(&mut self, n: usize) -> ReadResult<&'a [u8]> { 79 | let old_offset = self.offset; 80 | self.offset += n; 81 | if self.offset <= self.data.len() { 82 | Ok(&self.data[old_offset..self.offset]) 83 | } else { 84 | Err(ReadError::BufferTooShort) 85 | } 86 | } 87 | 88 | pub fn rest(&self) -> &'a [u8] { 89 | &self.data[self.offset..] 90 | } 91 | 92 | // pub fn done(&self) -> bool { 93 | // self.data.len() <= self.offset 94 | // } 95 | } 96 | 97 | impl<'a> Read for Reader<'a> { 98 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 99 | let to_read = (self.data.len() - self.offset).min(buf.len()); 100 | let slice = self.read(to_read).unwrap(); 101 | buf.copy_from_slice(slice); 102 | Ok(to_read) 103 | } 104 | } 105 | 106 | impl<'a> BufRead for Reader<'a> { 107 | fn fill_buf(&mut self) -> std::io::Result<&[u8]> { 108 | Ok(self.rest()) 109 | } 110 | 111 | fn consume(&mut self, amt: usize) { 112 | self.offset += amt; 113 | } 114 | } 115 | 116 | pub trait Readable 117 | where 118 | Self: Sized, 119 | { 120 | fn do_read(reader: &mut Reader) -> ReadResult; 121 | } 122 | 123 | impl Readable for u32 { 124 | fn do_read(reader: &mut Reader) -> ReadResult { 125 | reader.read_u32() 126 | } 127 | } 128 | impl Readable for u16 { 129 | fn do_read(reader: &mut Reader) -> ReadResult { 130 | reader.read_u16() 131 | } 132 | } 133 | impl Readable for u8 { 134 | fn do_read(reader: &mut Reader) -> ReadResult { 135 | reader.read_u8() 136 | } 137 | } 138 | impl Readable for f64 { 139 | fn do_read(reader: &mut Reader) -> ReadResult { 140 | reader.read_f64() 141 | } 142 | } 143 | impl Readable for i32 { 144 | fn do_read(reader: &mut Reader) -> ReadResult { 145 | reader.read_i32() 146 | } 147 | } 148 | 149 | #[derive(Debug, Error)] 150 | pub enum ReadError { 151 | #[error("Buffer too short")] 152 | BufferTooShort, 153 | #[error("Buffer too short to read value")] 154 | BufferTooShortForValue(#[from] TryFromSliceError), 155 | } 156 | -------------------------------------------------------------------------------- /term/__init__.py: -------------------------------------------------------------------------------- 1 | # This directory is also a Python package 2 | 3 | from term.atom import Atom 4 | from term.bitstring import BitString 5 | from term.fun import Fun 6 | from term.list import List, ImproperList, NIL 7 | from term.pid import Pid 8 | from term.reference import Reference 9 | 10 | __all__ = ['Atom', 'BitString', 'Fun', 'List', 'ImproperList', 'NIL', 11 | 'Pid', 'Reference'] 12 | -------------------------------------------------------------------------------- /term/atom.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from term.basetypes import BaseTerm, Term 16 | 17 | ATOM_MARKER = "pyrlang.Atom" 18 | 19 | 20 | class Atom(str, BaseTerm): 21 | """ Stores a string decoded from Erlang atom. Encodes back to atom. 22 | Beware this is equivalent to a Python string, when using this as a dict key: 23 | 24 | {Atom('foo'): 1, 'foo': 2} == {'foo': 2} 25 | """ 26 | 27 | def __repr__(self): 28 | the_repr = super(Atom, self).__repr__() 29 | return f'Atom({the_repr})' 30 | 31 | @staticmethod 32 | def from_string_or_atom(s: Term) -> 'Atom': 33 | """ Create an Atom from a string or return unchanged if it was an atom. """ 34 | return s if isinstance(s, Atom) else Atom(s) 35 | 36 | 37 | class StrictAtom(Atom): 38 | """ 39 | Stores a string decoded from Erlang atom. Encodes back to atom. 40 | 41 | Can serve as a Python dictionary key besides str with same content: 42 | 43 | {StrictAtom('foo'): 1, 'foo': 2} == {StrictAtom('foo'): 1, 'foo': 2} 44 | """ 45 | 46 | def __repr__(self) -> str: 47 | the_repr = super(StrictAtom, self).__repr__() 48 | return f"Strict{the_repr}" 49 | 50 | def __str__(self): 51 | return self.__repr__() 52 | 53 | def __hash__(self): 54 | return hash((ATOM_MARKER, super(StrictAtom, self).__hash__())) 55 | -------------------------------------------------------------------------------- /term/basetypes.py: -------------------------------------------------------------------------------- 1 | from typing import Union, List, Any 2 | 3 | 4 | class BaseTerm: 5 | """ Base class for all Erlang terms. """ 6 | pass 7 | 8 | 9 | class BasePid(BaseTerm): 10 | """ Serves as a base for local pid and remote pid. """ 11 | pass 12 | 13 | 14 | class BaseRef(BaseTerm): 15 | """ Serves as a base for local ref and remote ref. """ 16 | pass 17 | 18 | 19 | # Type AnyTerm includes Erlang type wrappers + compatible Python types 20 | Term = Union[str, List[Any], tuple, dict, int, float, bytes, BaseTerm, None] 21 | -------------------------------------------------------------------------------- /term/bitstring.py: -------------------------------------------------------------------------------- 1 | from term.basetypes import BaseTerm 2 | 3 | 4 | class BitString(BaseTerm): 5 | """ A simple data holder to be able to pass bitstrings (bytes with 6 | incomplete last byte) back to Erlang/Elixir node. 7 | """ 8 | def __init__(self, val: bytes, last_byte_bits: int): 9 | self.last_byte_bits_ = last_byte_bits 10 | self.value_ = val 11 | -------------------------------------------------------------------------------- /term/codec.py: -------------------------------------------------------------------------------- 1 | """ Adapter module which attempts to import native (Rust) codec implementation 2 | and then if import fails, uses Python codec implementation which is slower 3 | but always works. 4 | """ 5 | import logging 6 | from typing import Tuple 7 | 8 | from term.basetypes import BaseTerm, Term 9 | 10 | LOG = logging.getLogger("term") 11 | 12 | try: 13 | import term.native_codec_impl as co_impl 14 | except ImportError: 15 | LOG.warning("Native term ETF codec library import failed, falling back to slower Python impl") 16 | import term.py_codec_impl as co_impl 17 | 18 | 19 | def binary_to_term(data: bytes, options=None, decode_hook=None) -> Tuple[Term, bytes]: 20 | """ 21 | Strip 131 header and unpack if the data was compressed. 22 | 23 | :param data: The incoming encoded data with the 131 byte 24 | :param options: None or Options dict (pending design) 25 | * "atom": "str" | "bytes" | "Atom" (default "Atom"). 26 | Returns atoms as strings, as bytes or as atom.Atom objects. 27 | * "atom_call": callabe object that returns the atom representation 28 | * "byte_string": "str" | "bytes" | "int_list" (default "str"). 29 | Returns 8-bit strings as Python str, bytes or list of integers. 30 | :param decode_hook: 31 | Key/value pairs t: str,f : callable, s.t. f(v) is run before encoding 32 | for values v of type t. This allows for overriding the built-in encoding. 33 | "catch_all": f is a callable which will return representation for unknown 34 | object types. 35 | :raises PyCodecError: when the tag is not 131, when compressed 36 | data is incomplete or corrupted 37 | :returns: Value and Remaining unconsumed bytes 38 | """ 39 | opt = options if options else {} 40 | if decode_hook: 41 | opt['decode_hook'] = decode_hook 42 | return co_impl.binary_to_term(data, opt) 43 | 44 | 45 | def term_to_binary(term: object, options=None, encode_hook=None): 46 | """ 47 | Prepend the 131 header byte to encoded data. 48 | :param options: None or a dict of options with key/values "encode_hook": f where f 49 | is a callable which will return representation for unknown object types. 50 | This is kept for backward compatibility, and is equivalent to 51 | encode_hook={"catch_all": f} 52 | None will be encoded as such and becomes Atom('undefined'). 53 | :param encode_hook: 54 | Key/value pairs t: str,f : callable, s.t. f(v) is run before rust encoding 55 | for values of the type t. This allows for overriding the built-in encoding. 56 | "catch_all": f is a callable which will return representation for unknown 57 | object types. 58 | :returns: Bytes, the term object encoded with erlang binary term format 59 | """ 60 | opt = options if options else {} 61 | if options and hasattr(options.get('encode_hook', {}), '__call__'): 62 | # legacy encode_hook as single function transformed to 'catch_all' in new encode_hook dict 63 | opt['encode_hook'] = {'catch_all': options.get('encode_hook')} 64 | elif encode_hook: 65 | opt['encode_hook'] = encode_hook 66 | return co_impl.term_to_binary(term, opt) 67 | 68 | 69 | PyCodecError = co_impl.PyCodecError 70 | 71 | # aliases 72 | 73 | encode = pack = dumps = term_to_binary 74 | decode = unpack = loads = binary_to_term 75 | 76 | __all__ = ['term_to_binary', 'binary_to_term', 'PyCodecError', 77 | 'encode', 'decode', 78 | 'pack', 'unpack', 79 | 'dumps', 'loads'] 80 | -------------------------------------------------------------------------------- /term/fun.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from term.basetypes import BaseTerm 15 | 16 | 17 | class Fun(BaseTerm): 18 | """ Represents a pointer to a function in Erlang, with some variable 19 | values captured. Not callable from Python. """ 20 | 21 | def __init__(self, mod, arity, pid, index, uniq, old_index, old_uniq, free): 22 | # Pid of the Erlang process which created the function 23 | self.pid_ = pid 24 | 25 | self.arity_ = arity 26 | 27 | # Atom which contains the code for this function 28 | self.module_ = mod 29 | 30 | self.old_index_ = old_index 31 | 32 | # Internal index, an integer, in the module's internal fun table. 33 | # Has no meaning in Python 34 | self.index_ = index 35 | 36 | # An integer with the hash value of the function parse 37 | self.old_uniq_ = old_uniq 38 | 39 | # An md5 hash of the significant parts of the BEAM file 40 | self.uniq_ = uniq 41 | 42 | # A list of values: frozen captured variables 43 | self.free_ = free 44 | -------------------------------------------------------------------------------- /term/list.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import List 16 | 17 | import array 18 | 19 | from term.basetypes import BaseTerm 20 | 21 | NIL = [] # type: List 22 | 23 | 24 | class ImproperList(List, BaseTerm): 25 | """ A simple data holder used to pass improper lists back to Erlang. 26 | An Erlang improper list looks like `[1, 2, 3 | 4]` where 4 takes 27 | tail slot of last list cell instead of `NIL`. This is a rare data type 28 | and very likely you will not ever need it. 29 | """ 30 | 31 | def __init__(self, elements: list, tail=None): 32 | """ tail is optional, if omitted, the last element in elements 33 | `elements[-1]` will be considered to be the tail. if it's 34 | present it will be appended to the list so it becomes `self[-1]` 35 | """ 36 | super().__init__(elements) 37 | if tail: 38 | self.append(tail) 39 | 40 | @property 41 | def _elements(self): 42 | return self[:-1] 43 | 44 | @property 45 | def _tail(self): 46 | return self[-1] 47 | 48 | 49 | def list_to_unicode_str(lst: list) -> str: 50 | """ A helper function to convert a list of large integers incoming from 51 | Erlang into a unicode string. """ 52 | return "".join(map(chr, lst)) 53 | 54 | 55 | def list_to_str(lst: List[int]) -> str: 56 | """ A helper function to convert a list of bytes (0..255) into an 57 | ASCII string. """ 58 | return bytearray(lst).decode('utf-8') 59 | -------------------------------------------------------------------------------- /term/pid.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from term.basetypes import BasePid 15 | 16 | PID_MARKER = "pyrlang.Pid" 17 | 18 | 19 | class Pid(BasePid): 20 | """ Represents Erlang-style process identifier with 3 components. Node 21 | component is always 0 for local node, otherwise it can take arbitrary 22 | integer values. 23 | 24 | A pid is created by Erlang automatically. Pyrlang Process also generates 25 | one when it is born to register itself in the process registry. Pid 26 | uniquely identifies a running process in the cluster. 27 | """ 28 | 29 | def __init__(self, node_name: str, id: int, serial: int, 30 | creation: int) -> None: 31 | self.node_name_ = node_name 32 | """ NOTE: native encoder assumes this is a string. """ 33 | 34 | self.id_ = id 35 | self.serial_ = serial 36 | self.creation_ = creation 37 | 38 | def is_local_to(self, node): 39 | """ Compares self.node_name to other node.node_name 40 | :param node: Check whether pid is local to the node. 41 | :type node: pyrlang.node.Node 42 | """ 43 | return self.node_name_ == node.node_name_ 44 | 45 | def __repr__(self) -> str: 46 | return "<%d.%d.%d @ %s>" % (self.creation_, self.id_, self.serial_, 47 | self.node_name_) 48 | 49 | def __str__(self) -> str: 50 | return self.__repr__() 51 | 52 | def equals(self, other) -> bool: 53 | return isinstance(other, Pid) \ 54 | and self.node_name_ == other.node_name_ \ 55 | and self.id_ == other.id_ \ 56 | and self.serial_ == other.serial_ \ 57 | and self.creation_ == other.creation_ 58 | 59 | __eq__ = equals 60 | 61 | def __ne__(self, other): 62 | return not self.equals(other) 63 | 64 | def __hash__(self): 65 | return hash((PID_MARKER, self.node_name_, 66 | self.id_, self.serial_, self.creation_)) 67 | -------------------------------------------------------------------------------- /term/reference.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import random 15 | import struct 16 | import time 17 | from typing import Union 18 | 19 | from term import util, Atom 20 | from term.basetypes import BaseRef, Term 21 | 22 | REF_MARKER = "pyrlang.Ref" 23 | 24 | 25 | class Reference(BaseRef): 26 | """ Represents a reference value from Erlang, typically it has 12 bytes of 27 | unique data, but it might change. 28 | """ 29 | 30 | @staticmethod 31 | def create(node_name: str, creation: int): 32 | """ Construct a (most possibly) unique ref with random bytes and time 33 | :param node_name: string, not a Node object 34 | :param creation: int value 0, 1 or 2 from node's distribution object 35 | :rtype: term.reference.Reference 36 | """ 37 | rand_val = int(time.monotonic() * 1000000) + random.randrange(1000000) 38 | rand_bytes = rand_val.to_bytes(length=12, 39 | byteorder="big", 40 | signed=False) 41 | return Reference(node_name=node_name, 42 | creation=creation, 43 | refid=rand_bytes) 44 | 45 | def __init__(self, node_name: Term, creation: int, refid: bytes) -> None: 46 | self.node_name_ = Atom.from_string_or_atom(node_name) 47 | """ Node the ref comes from. NOTE: native codec assumes this is a string. """ 48 | 49 | self.id_ = refid 50 | """ Identification bytes, guaranteed to be unique on the creating node """ 51 | 52 | self.creation_ = creation 53 | 54 | def __repr__(self) -> str: 55 | # Assume that ref has only 3 32-bit words (actually id size is not 56 | # specified in docs and can be a different multiple of 4) 57 | if len(self.id_) == 12: 58 | v = struct.unpack(">III", self.id_) 59 | return "Ref<%d,%d,%d,%d>@%s" % \ 60 | (self.creation_, v[0], v[1], v[2], self.node_name_) 61 | else: 62 | return "Ref<%d,%s>" % (self.creation_, 63 | util.hex_bytes(self.id_, ",")) 64 | 65 | def __str__(self) -> str: 66 | return self.__repr__() 67 | 68 | # Eq, Ne and Hash are used for having this class as a dict key 69 | 70 | def equals(self, other) -> bool: 71 | return isinstance(other, Reference) \ 72 | and self.node_name_ == other.node_name_ \ 73 | and self.id_ == other.id_ \ 74 | and self.creation_ == other.creation_ 75 | 76 | __eq__ = equals 77 | 78 | def __ne__(self, other): 79 | return not self.equals(other) 80 | 81 | def __hash__(self): 82 | return hash((REF_MARKER, self.node_name_, self.id_, self.creation_)) 83 | -------------------------------------------------------------------------------- /term/util.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018, Erlang Solutions Ltd, and S2HC Sweden AB 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import struct 16 | 17 | 18 | def u16(data: bytes, pos: int = 0): 19 | return struct.unpack(">H", data[pos:pos + 2])[0] 20 | 21 | 22 | def u32(data: bytes, pos: int = 0): 23 | return struct.unpack(">I", data[pos:pos + 4])[0] 24 | 25 | 26 | def i32(data: bytes, pos: int = 0): 27 | return struct.unpack(">i", data[pos:pos + 4])[0] 28 | 29 | 30 | def to_u32(val): 31 | return struct.pack(">I", val) 32 | 33 | 34 | def to_i32(val): 35 | return struct.pack(">i", val) 36 | 37 | 38 | def to_u16(val): 39 | return struct.pack(">H", val) 40 | 41 | 42 | def hex_bytes(data: bytes, sep: str= " "): 43 | """ Format a bytes() object as a hex dump """ 44 | return sep.join("{:02x}".format(bval) for bval in data) 45 | 46 | 47 | def dec_bytes(data: bytes, sep: str= " "): 48 | """ Format a bytes() object as a decimal dump """ 49 | return sep.join(str(bval) for bval in data) 50 | 51 | 52 | __all__ = ['hex_bytes', 'dec_bytes', 53 | 'u16', 'u32', 'i32', 54 | 'to_u16', 'to_u32', 'to_i32'] 55 | -------------------------------------------------------------------------------- /test/test_rust_extension.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import term.native_codec_impl as nativecodec 3 | import term.py_codec_impl as pycodec 4 | from term.atom import Atom 5 | 6 | 7 | class TestNativeCodecDriver(unittest.TestCase): 8 | def test_simple_b2t_errors(self): 9 | with self.assertRaises(BaseException): 10 | nativecodec.binary_to_term(b'xxx', None) 11 | # Empty term creates empty input error 12 | nativecodec.binary_to_term(b'', None) 13 | 14 | def test_b2t_read_errors(self): 15 | with self.assertRaises(BaseException): 16 | # Compressed term (131, 80) with incomplete length field 17 | # should create read error 18 | nativecodec.binary_to_term(b'\x83\x50\x00', None) 19 | 20 | def test_b2t_library_equality_atoms(self): 21 | a = pycodec.term_to_binary(Atom("hello")) 22 | (b, _) = nativecodec.binary_to_term(a, None) 23 | self.assertEqual(Atom("hello"), b) 24 | 25 | 26 | if __name__ == '__main__': 27 | unittest.main() 28 | --------------------------------------------------------------------------------