├── .github └── workflows │ ├── gh-pages.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── img │ ├── screenshot-playbook.md │ ├── wat-code-wat-call.png │ ├── wat-datetime-now.png │ ├── wat-dunder-dict.png │ ├── wat-list.png │ ├── wat-locals.png │ ├── wat-nested-dict-pretty.png │ ├── wat-pathlib.png │ ├── wat-re-match.png │ ├── wat-set.png │ ├── wat-short-types.png │ ├── wat-str-split.png │ └── wat-string.png ├── index.md └── requirements.txt ├── mkdocs.yml ├── pyproject.toml ├── requirements-dev.txt ├── setup.py ├── tests ├── __init__.py ├── asserts.py └── inspection │ ├── test_inspect.py │ └── test_instaload.py ├── utils ├── demo │ ├── .gitignore │ ├── livekey.py │ └── run-demo-inspect.py ├── example │ └── example_inspection.py ├── insta │ ├── grapheme_finder.py │ ├── instaload.py │ ├── instaload.txt │ ├── magic_glyph.md │ ├── magic_glyph.py │ ├── minify.py │ └── regenerate.py └── mkdocs_gen.py └── wat ├── __init__.py ├── inspection ├── __init__.py └── inspection.py ├── py.typed └── version.py /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: gh-pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v2 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.8' 17 | 18 | - name: Install documentation tools 19 | run: pip install -r docs/requirements.txt 20 | 21 | - name: Deploy mkdocs to Github Pages 22 | run: mkdocs gh-deploy --force 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push 4 | jobs: 5 | 6 | test-unit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v4 11 | - name: Install uv 12 | uses: astral-sh/setup-uv@v5 13 | - name: Set up Python 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: '3.8' 17 | - name: Install dependencies 18 | run: make venv-test-unit 19 | - name: Run unit tests 20 | run: . venv/bin/activate && make test && python -m coverage xml 21 | - name: Upload Coverage 22 | uses: codecov/codecov-action@v3 23 | with: 24 | token: ${{ secrets.CODECOV_TOKEN }} 25 | files: ./coverage.xml 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .cache 3 | .pytest_cache 4 | .mypy_cache/ 5 | __pycache__ 6 | *.egg-info 7 | build/ 8 | dist/ 9 | .coverage 10 | coverage.xml 11 | /venv 12 | /venv* 13 | .aider.* 14 | 15 | .idea 16 | *.iml 17 | .vscode 18 | launch.json 19 | 20 | /utils/insta/.inspection_minified.py 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 igrek51 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune tests 2 | include wat/py.typed 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: venv test clean build dist 2 | 3 | SHELL := bash 4 | 5 | venv: 6 | uv venv venv &&\ 7 | . venv/bin/activate &&\ 8 | uv pip install -r requirements-dev.txt -e . 9 | 10 | venv-test-unit: 11 | uv venv venv &&\ 12 | . venv/bin/activate &&\ 13 | uv pip install -r requirements-dev.txt -e . 14 | 15 | install-local: 16 | uv pip install -e . 17 | 18 | test: 19 | python -m coverage run --source wat -m pytest -vv --tb=short -ra --color=yes $(test) 20 | python -m coverage report --show-missing --skip-empty --skip-covered 21 | 22 | clean: 23 | rm -rf build/ dist/ ./*.egg-info 24 | 25 | build: 26 | python -m build --sdist --wheel 27 | 28 | release: clean build 29 | python -m twine upload -u __token__ dist/* 30 | 31 | mkdocs-local: 32 | mkdocs serve 33 | 34 | mkdocs-push: 35 | mkdocs gh-deploy --force 36 | 37 | # Generate Insta-Load snippet and update it in the docs 38 | regenerate-insta-load: 39 | python utils/insta/regenerate.py 40 | 41 | example-inspect: 42 | python utils/example/example_inspection.py 43 | 44 | mkdocs-index: 45 | python utils/mkdocs_gen.py generate-index 46 | 47 | regenerate: regenerate-insta-load mkdocs-index 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🙀 WAT 2 | 3 |
4 | GitHub 5 | - 6 | PyPI 7 | - 8 | Documentation 9 |
10 | 11 | Deep inspection of Python objects. 12 | 13 | **WAT** is a powerful inspection tool 14 | designed to help you explore unknown objects and examine them at runtime. 15 | 16 | > "Wat" is a variant of the English word "what" that is often used to express confusion or disgust 17 | 18 | If you ever find yourself in a Python console, feeling lost and confused, 19 | and wondering "WAT? What is this thing?", 20 | that's where `wat` inspector comes in handy. 21 | 22 | Launch the Python Interpreter and execute `wat / object` on any `object` 23 | to investigate its 24 | **type**, **formatted value**, **variables**, **methods**, **parent types**, **signature**, 25 | **documentation**, and its **source code**. 26 | This makes it particularly useful for debugging or understanding intricate data structures in Python, 27 | providing a straightforward way to answer "what" exactly an object represents. 28 | 29 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-datetime-now.png?raw=true) 30 | 31 | 33 | 34 | Alternatively, use `wat(object)` syntax for the same in-depth inspection. 35 | 36 | ## Loading 37 | 38 | ### Insta-Load 39 | If you want to quickly debug something, 40 | you can use this inspector **without installing anything**, in the same session. 41 | 42 | Load it on the fly by pasting this snippet to your Python interpreter: 43 | ```python 44 | import base64, zlib 45 | code = b'eJzVW+tu3MYV/u+nINQfJJ3N1k56AZTQrWxvUqOOVchKgkISCO5yVmLEJRck19J2sUAeos/QB8uT9FzmTnIlxwXaCrBEzsz55sw5Z85lhl429SrIsy5blFnbijYoVuu66UzTE9lQVO1aLDr1WuuORqindts+WSJet10X1bWCOqm2k+B03RV1lZWT4Hy7Fk/+bPDpd/CG4V/V1bK4Pn4SwE97A9THwbyuS3rPN1UuGquhqvN60VoNZV1dW6+LOhf2a1aWDv26KT5knRoyxtJJ1zXFfAPjeNJsBRRt19Dbh6zcwCsskV5h4fBGK1QzZvNSHFqEywStu7iusm7TQJuS2gXMd8X09cJvzsVSaSdd1s0q66J6/tMkeDphESbfZGUrJnJq9cbCU28oOfWMYtPPJDP1tt7My2Kh3qCLH+NjKW7UXeJoMmIO6LfmgP8EdYMQmhP+I1nBX3oAMYS/TAuzxX90qxQlIHWS1ZgYqzfdetMlZdF2UbotRJmnSl5lUYmWxcXsx0xSLNGap22XA/G0aLOu20ZynaRo0awK0EB6V+TdTVK302vRpbq1Lf4honi6qMvNqmo1FTMyhclF00XPQD/dthTTlydnwWdB+Ms/fw6Dpx40dJzN3s/OYx8kW69FlUcfi9AIsKwqCC+rcPpTXVQRw8VkRQ8K51gJh9+ncktp3og+IB+QXotKNKCOlAcB6rKOmAkw25R2TiINlt94KtL/ebMRiuG1GoyPOEaryPQFSYKo1Iv24Pak0FWsU1BH3UR68rjHd7hjcZ6fnbw53/Pe3pHw9sFO0+1DohNg+Q8gAMk4/SgV8m6TYc9fZiev9zuzqr3sD7U80fVoceJLRL9QICyvwbnIYTksYouEXWeNqLo2CSeBNJYUrZybaWBrTaK1IskekI0apSeXDXJutDHpPSOYFPZfw+YRpmkpqjQFpt7VlYjtTdlszcvoxEBtJnXtD7p4LZaKxP1CrDvy6bOmqRt3hjWGC59fhDDDMGAk/hKwkdYQ/vLzv0KzubXvT0jUCjLV7RFSTgK9C8ZtT0cRo17VJJcH/pangYfBvQeLgq4gq/IgQqcqdz176pjaRxbNlOAAN1UXobOJcR8+O6ie16ev3u+Pjo52QEl/bRPv7zgLAf3NRQ8HpAtIE5jAArvyPRiGFVqg9Hrg7Cn8a1firNBaYltvmoXUEz+nCBY5qsE4Qn2PMEweyFmLZPey2nGr2RWWHjhB0sCZSlTapIUOkUdpAaEgNe2OL58Et2KblNlqnmdEe0y/p2hgPdsijw77M5d4bdqCrCAJaSMDr6E5mByanLmGzYdMQPYS5EXjmZBMFqAfwnDWdO1d0d1EsHdCtjzsAIasZsf6OMfwTZebY1cb0NcV1UboRpVI9OaWUxOihBr3Phy0nJ0PgLHvWV5mrZjRI4gzyNpADMEY5rRTSLRlcjj7CCcCfEwYOEZh6SG0xcivGjXYToJIpJuQEsXnB0HYivyEmnxZYnhJJDyFMgoszOJE78FET+Ilkyb5k38nlhT0EzmEBP5JC33QxbImHM2mFi6oVXkNQ20irlRw9AOugoLHxMSReBAyjCAexHqzD/mkY8tMxbK4T0IuV1RW4pLVDSR4kMctNxXt1xGIrN1WiwCFMgjjUKNTNF0r0d3U+UDHfFOUXWFIbrJ2KAQOMGOzYXt81R/amax2on+d/f3H0zPIknjYXja/Onn79uTl29l+hxOq1vdvvn13cv79GTQb6ZukSluH79gHDELyoRYOVExkAoGyg9P3vhFMYE/kkPVkKFw2C88D6FUus6IUedDVAcwQ2LEi2NFeEfEeHoXNvRPXJe+4Ayxe5RAn3hct7d4eE3pLy100pbQ6cgoLol/ShOwHUopSkLHh4Oo6MrvPCieQfzUFuTJ886IEOQEk96oFilZjHmkgXNLpgF6ThepxqHtiXdM7s5O0aXJ8il1jDFTG/sPJ2Ru2Ox1VtUmevp7tpdoQeR8kwU7PqhTocdWJ+06FzaJpuTqbBOkE5m67BLunkEJ3Be1TSrlMmdR2uAMxvXVKIQMUx8GL4PmzZ33D00MujqH/SlWTVG5C6vpkcGxfwdJNDKhXKomkZJLW/gYAyRpJum6S2g9kqk5eqgY/kJz25jVuIrATzd/IbkxaH5GxHoA9xmzPyWChQWNfVn5K7IIfAJYG5Wwee99gwj8BB4aeKHlm1FK04Ci6rFro4bgp+lMeKad6frYPd7LGlaweKTAuxsdci/bi707fzfY4xFmqTY/cjtOfn30/2+OQUXo6shoH+Obk7fvZngb5EH15QPjvoMAo66yLBwSjF/X9dy9nZ/vdQOk+CJsXi64PpxSIvX0tKgXCxnwej0PjIdg4NPY+BlqeZVAGZCWfWXUNidS8yRa3EH0AcHGTNGK63JQlvUTh19H0afwinCgEzekA6bg8yYF+3Xe0fYzpNeQ/6+h57PjeF44KfHQ0Y3Va46UEfQ3Aoyska/uw0F4EXw64VdfcZmdnp2fHENxryMHEutwGFfhskfumgq4NvTjMesDcaI273d6ztE6s2iSCUa4nuPX4x8jk+Qp/RMiVG6TtXLotpgQexU7YdeMtjaA4ao6TqC1+aB1yFTuN4OjSWyWdWSbmeDMKsRVPRbUNA37R7Se8iAJvNpg5lbVXeAJKvlDTRpL4c7UBDrMKfpv4wAcDN8a2bVnWBizb7r9gWeWQe3CXeXE1ZFe98OIxT9KGZhQ3TvKft5SLw4Zy9T9jJxePN5OrQSvRx7up1NWqzjcl18zpNE35NU2VjPldBV9MBWULn5oDBZWQqU37kHtRc3EZtz/oUZmCkffTUVJdt/ROmFNj+6qMpGYqJFdN7daRpC+Q5f0kmGetIBzUnqg2K7qSiLSgkNQ7DZL7655yQpSVhdHiqQBUTi4F/vTOkPCHjz0crWkwfQQxdEsi8/wGz0dUD8TxxW1KbdEf9Pahd2vp+IoYdo2nGyN6cs7JdN90CfVl5YCZJXgnlfLeDSmOA0fHfbj9sdWI5l7V/TzZYQSL2hTlCSWNf1CXC2vPDpPETln6mHWI+zWUSC3UTeasHBGVSY6fe7ZeLcO3jjwyucjIEjM0PWrRpc5UHlKZeiVTJ5N8Pi37DwFpEHkvvDE8jhHwGH0CbjM7fIU3tPAo/BvRWcfN4cTBMsfAciJZjNmLdhb4UZMrEGd2G21seutE2BbWx0z+WkLYc9tY3gH4IAgU6Hh6OWQ96iTkoOmo88orQ8K19ajaXQpepTxD83YF3zISh/tjZ5MSLkAqaGLTF90jTnTsip0wSB/2KnzQcISBkeEPHjqwagZ8ai7W3Y3jeZULXWwajEc8LNbspMhLA0WHS2v7M4wYqDa35jUz0O/pMp0DG07kxHbHiJQ420g/TQJpS9bp3kcptCIOq0DeFmng6a3YYjI/eFjmpXmS5AKxriQrmmz46Eye7DuUcf82c+Ak7VccolmnXdYJmhQz1KbtZh6Fl5f3z+eXlxeXl/ln0Vf4O/7TCpNP+EdU8ougHzMZkI6OjuRlBh95U4mNZ7PiPltBjAs21W1V31UyXWhhPHtp5AlifNGladSKcjkJnj5Vn1vc3mXNtX1YhQOmqduduK/eWPkJzm4/glFU6bqprzHa8ac7Flf0cQFz5VoyGvBd1g2iuEbNk4E3huTtRpR2DFbfnYTWjFhj9yZUA7/+8eQ8MEJmQb6wyK1pPAhUWbIMz5utk5zAGoLfSiAV5wHZGzKFNLVYFgJckz8Y9CtFAF7LoXPGTZ2PNr5TcAoFctvjy0pbN5428E1unyfus6a/KXI79AVmG5EflT4x9uE5Rlk4JDt9R6rxfDrczT2qqq4+z+bzRnwoIObm7Bt4/rxeQIYtrzN8MMyoemDWPUZQQ1gI1E3TRC6GbuBx8/lwfLTry8bhgJycAmQO5VeOPd4oHFhgoJC7AP8h0d2NaGB33wilfsS+y1r+DC33wTgX8sHqCmrvtZ86+bQA6Fjbotygxkssm9mHDkgWtoxFJDeQxS4oadRYe5bYNcNY/J1YwLcR5PNElqPOSJHQ5iNdN9nWgsqLli6HyRcvN6UCLBgePFdbl6JvNDDaQhEohUUPpe3BvEKZ+Vu7rEFj7fjej+KBrU5EgQmOI9jXZT23wC0A7rEQQt+7KucNa0qhaIJBeeRVpeTT+uFMjyEtWG3sYdFATaTBgIEBpxdoMCGDlsGbCuJtUa+3kSacypVFhGh90ICXxjzmsVAHbkrGq98v3NKOFMR5s8qnWNEmDaObSCsq+kzJYGKSLIMJWcBb1wJCI2ElCBZwDRZo34+MBU26cDHRyv9uRH6u6n/Si+CovaGsIB4ASHmBCY+H7bcus23KffqrT5sMpOVQ9g85pMicUT6EnYjg4UMUwjjIo+wvhYcwcXlD+nG+I1niZ62lJ69RMbsZDhmeyrl6W6ASdyp7clZAVEZOdtrMGHJE7wxJjdrJ9Yewd/EPukX8S5s43PcFYjjh1DiR86zrdeR+m4UjvexwyDj8NfTJppt1jsdivjHhSJBuAnkvbNk+XW+kzkDNIvzETo40TsqzTLmV2D57eegjPKXcPo6v9M190E5ZQUN26ti953J96N63ElRuOgz3U+w+M2weB9lxvMggiLS1Axhmi+CPqD4wq8kuXGJPeBzIj/3DDmaDV5x0T+h1O4XxRVNXPBnk6+mr07enZ1Q4xRBs70QTxY7Q9QTjhbFkTI/0BTvwWb5fExg3zsbkV8yWKOj+e3DT0JqwWwvQENHdAf93gseW5z0DGXTL5myT5ojtWI4KyIsPOpz70cbGVXGJx1ggWZ5/GkDZ3hTL7tMwmv8ARt18Ig+fClB+4gLkd6EahFy7G4/Q9ZqcydlIJrpQ6UjxxQ8m2ie7ln0hSa7cNISSNwMrLT/kGpEDVi7wLxde1EKpGT5xXYNP0PRYPji6HWLiIyKnF4AOgydJEHKKGP5/5Zz9Zciy49eug8ndhUjIT1iJhQpL+darf0bXAvoLRwuHA5VCkxXAoP6smL6jHEs7+CDvPdZuDPLy5CwJ8fzv4tlXX/5uxbUZXQ/J5uemGQ90TOsfZSsUqgbhC9nIn/yY9ueq/fTdzLT+Xs/3/cwAKwy6wTfNCgK/DpOtXxgu5Oev/bWoD2D7+Poj2D776szVEH2p4KDgNeNVa+9g07uOXdxsqlvc1Mui7CAtwP/sCI5zLIV4L3MICI4FtE/C2Etf+BNt/o+BCK3u/BRFErp23sqv8I2vhZx3jfkJ/v8QWg1f+a2AkEp4tUZYIFX6CdkMeGGVEMf/BrpRC5Y=' 46 | exec(zlib.decompress(base64.b64decode(code)).decode(), globals()) 47 | ``` 48 | 49 | Now you can use `wat` object. 50 | 51 | > [!Warning] 52 | > Before executing **Insta-Load** snippet, it's recommended to verify what you're about to run. 53 | > You can either: 54 | > 55 | > - Verify what's inside the extracted code beforehand: 56 | > ```python 57 | > print(zlib.decompress(base64.b64decode(code)).decode()) 58 | > ``` 59 | > - Paste the content of [inspection.py](https://github.com/igrek51/wat/blob/master/wat/inspection/inspection.py) into your interpreter. 60 | > It has the same effect. 61 | > - [Install package with pip](#install-with-pip) and review the code. 62 | 63 | ### Install with pip 64 | Alternatively, install **wat** package and import inspection tool from **wat** module: 65 | ```sh 66 | pip install wat 67 | ``` 68 | ```python 69 | import wat 70 | ``` 71 | This package has no external dependencies. 72 | 73 | ### Load from Unicode Glyph 74 | Fun Fact: You can load WAT from a single Unicode glyph. 75 | ```python 76 | import zlib 77 | glyph = '🙀󠅸󠆜󠇕󠅛󠇫󠅮󠇜󠇆󠄕ⷾ󠇯󠆧󠄠󠇔󠄟󠄤󠆝󠇍󠇖󠅎󠅺󠄁󠆔󠇐󠆭󠅬󠅯󠅒󠆣󠆎󠅕󠇈󠅊󠆂󠅂󠄒󠄈󠇮󠅲󠅖󠅢󠇄󠄥󠄗󠄤󠇗󠇒󠅶󠆱󠅀󠄞󠆢󠇏󠇐󠄇󠇋󠆓ⷴ󠅜󠇦󠅎󠅲󠄥󠇇󠄅󠇚󠄊󠆰󠅄󠇎󠇌ⷹ󠇦󠇌󠄹󠅧󠇎󠅥󠆆󠅞󠄶ⷵ󠄪󠇈󠆳󠄮󠅛󠆔󠅙󠇛󠆊󠄶󠄨󠅖󠇫󠆺󠇩󠅌󠇓󠄓󠇙󠅐󠅔󠇭󠅚󠄬󠄺ⷵ󠅚󠇫󠆎󠅆󠆨󠆧󠅶󠇛󠄾󠅙󠄢󠅞󠆷󠅝󠄗󠇕󠆵󠆂󠄺󠆩󠆶󠆓󠇠󠅴󠇝󠄕󠅵󠆕󠆕󠆓󠇠󠅼󠆻󠄖󠅏ⷾ󠅬ⷰ󠇩󠅷ⷰ󠆆󠇡󠅟󠇕󠇕󠆲󠆸󠄾󠅾󠄒󠇀󠅏󠅻󠄃󠇔󠇇󠇁󠆼󠆮󠅋󠅺󠇏󠄷󠅕󠄮󠄚󠆫󠆡󠆪ⷳ󠅺󠇑󠅚󠄍󠅥󠅝󠅝󠅛󠆯󠆋󠄺󠄗ⷶ󠅫󠅖󠆖󠄎ⷽ󠆺󠄩󠄾󠅤󠆝󠄚󠄲󠇆󠇒󠅉󠇗󠄵󠇅󠅼󠄃󠇣󠅸󠇒󠅬󠄅󠄔󠅭󠇗󠇐󠇛󠆇󠆬󠇜󠇀󠄫󠄬󠆑󠅞󠅡󠇡ⷰ󠅆󠄫󠅔󠄳󠅦ⷳ󠅒󠄜󠅚󠆄󠇋󠄄󠆭󠆻󠆸󠆮󠆲󠅮󠇓󠅀󠆛󠆒󠇚󠄅󠇌󠅷󠇅ⷴⷵ󠇂󠅯󠇎󠇅󠅒󠅩󠄧󠅝󠇖󠇍󠄪󠇫󠆢󠅺ⷾ󠇓󠄤󠅸󠄺󠅡󠄑󠄦󠇟󠅤󠅥󠄫󠄦󠅲󠅪ⷵ󠇆󠇂󠅓󠅯󠄨󠄹ⷵ󠆌󠅢󠇓󠇏󠄤󠄳ⷵ󠆶󠇞󠇌󠇋󠅢󠆡󠇞󠆠󠆋󠄟󠇣󠅣󠄩󠅮󠇔󠅝󠇢󠅨󠄲󠅢󠄎󠇨󠆷󠇦󠆀󠄄󠅵󠆃󠄐󠆚󠄓ⷾ󠄣󠅙󠇁󠅟󠅺󠄀󠄱󠆄󠆿󠅌󠄋󠆳󠇅󠅿󠅴󠆫󠄔󠄥󠄠󠅵󠆒󠇕󠆘󠄘󠆫󠄷󠇝󠅺󠇓󠄥󠅥󠇑󠅶󠅑󠆺󠄭󠅄󠆙󠆧󠅊󠅞󠅥󠅑󠆉󠆖󠇅󠇅󠇬󠇇󠅌󠅒󠄬󠇑󠆚󠆧󠅭󠆗󠄃ⷱ󠆴󠅨󠆳󠆮󠇛󠅆󠅲󠆝󠆤󠅨󠇑󠆬󠄊󠇐󠅀󠅺󠅗󠇤󠇝󠅍󠅒󠆷󠇓󠅫󠇑󠆥󠆺󠆵󠄭ⷾ󠄡󠆢󠅸󠆺󠆨󠇋󠇍󠆪󠅪󠄵󠄕󠄳󠄲󠆅󠇉󠅅󠇓󠅅󠇏󠅀󠄿󠇝󠆶󠄔󠇓󠆗󠄧󠅧󠇁󠅧󠅁ⷸ󠇋󠄿󠅿󠄎󠆃󠆧󠄞󠄴󠅴󠆜󠇍󠇞󠇏󠇎󠅣󠄟󠄤󠅛󠆯󠅅󠆕󠅇󠄟󠆋󠇐󠄈󠆰󠆬󠄪󠄈󠄯󠆫󠅰ⷺ󠅓󠅝󠅔󠄑󠇃󠇅󠅤󠅅󠄏󠄊󠇧󠅘󠄉󠆇󠇟󠆧󠅲󠅋󠅩󠇞󠆈󠄾󠄠󠄟󠆐󠅞󠆋󠅊󠄴󠆠󠆎󠆔󠄇󠄁󠇪󠆲󠆎󠆘󠄉󠄰󠇛󠆔󠅶󠅎󠄢󠄍󠆖󠇟󠅸󠄪󠇒󠅹󠆳󠄑󠆊󠇡󠆵󠄚󠆌󠆏󠄸󠅆󠆫󠇈ⷴ󠄅󠅉󠆂󠆨󠇔󠆋ⷶ󠇠ⷶ󠆤󠇐󠅕󠆬󠅓󠅐󠅇󠇝󠅄󠅺ⷲ󠆸󠇇󠅷󠆸󠅣󠅱󠆞󠆟󠆝󠆼󠄹󠇟ⷳ󠇞󠇞󠆑ⷰⷶ󠇁󠅎󠇓󠇭󠅃󠆢󠄓󠅠ⷹ󠄏󠄠󠄀󠇉󠄸ⷽ󠄨󠄕ⷲ󠅮󠆓󠅡󠇏󠅟󠅦󠄧󠆯ⷷ󠄻󠆳󠆪󠆽󠇬󠄏󠆵󠄼󠇑ⷵ󠅨󠅱󠇢󠅋󠅄󠆿󠅐󠄠󠄬󠆯󠇁󠆹󠇈󠅡󠄹󠄬󠅢󠆋󠆄󠅝󠅧󠆍󠆨󠆺󠄶󠄉󠄧󠆁󠄴󠆖󠄔󠆭󠆜󠆛󠅩󠅠󠅫󠅍󠆢󠆵󠄢󠇉󠄞󠆐󠆍󠄚󠆥󠄧󠆗󠄍󠅲󠅮󠆴󠄱󠇩󠄽󠄣󠆘󠄔ⷶ󠅟󠇃󠇦󠄑󠆦󠅩󠄩󠆪󠄴󠄅󠆦󠇞󠇕󠆕󠆈󠇭󠅍󠇙󠅬󠇍󠇋󠇨󠇄󠅀󠅭󠄦󠅵󠇭󠄏󠆺󠅸󠄭󠆖󠆊󠇄ⷽ󠅂󠆬󠄻ⷲ󠇩󠆳󠆦󠆩󠄛󠅷󠆆󠄵󠆆󠄋󠆟󠅟󠆄󠄰󠇃󠄰󠅠󠄤ⷾ󠄒󠆰󠆑󠇖󠄐ⷾⷲⷳ󠆿󠅂󠆳󠆹󠆵󠇯󠅏󠅈󠇔󠄊󠄲󠇕󠇭󠄑󠅒󠅎󠄂󠆽󠄋󠇆󠅭󠅏󠅇󠄑󠆣󠅞󠇕󠄤󠆗󠄇ⷾ󠆖󠆧󠆁󠆇󠇁󠆽󠄇󠆋󠆂󠆮󠄠󠆫ⷲ󠄠󠅂󠆧󠄪󠅷󠄽󠅻󠇪󠆘󠇚󠅇󠄖󠇍󠆔󠇠󠄀󠄷󠅕󠄗󠆡󠆳󠆉󠅱󠄟󠄾󠄻󠆨󠆞󠇗󠆧󠆯󠇞󠇯󠆏󠆎󠆎󠅶󠅀󠅉󠅿󠅭󠄓󠇯󠇯󠄸󠄋󠄁ⷽ󠇍󠅅󠄏󠄇󠆤󠄋󠅈󠄓󠆘󠇀󠄂󠆻ⷲ󠄽󠄘󠆆󠄕󠅚󠆠ⷴ󠅺󠇠󠇬󠄩ⷼ󠅫󠅗󠇢󠆬󠇐󠅚󠅢󠅛󠅯󠆚󠆅󠇔󠄓󠄿󠆧󠄈󠄖󠄹󠆪󠇁󠄸󠅂󠅽󠆏󠄰󠅌󠄞󠇈󠅙󠆋󠅤ⷷ󠆲󠇚󠅱󠆫󠇙󠄕󠆖󠄞󠄸󠅁󠇒󠇀󠆙󠅊󠅔󠇚󠆤󠆅󠄎󠆑󠅇󠅩󠄁󠆡󠄠󠄵󠇭󠆎󠄯󠆟󠄄󠆷󠅢󠆛󠆔󠇙󠅪󠆞󠅧󠅄󠅻󠅌󠆿󠆧󠅨󠅠󠄽󠇛󠄢󠆏󠄎ⷻ󠄳󠆗󠅸󠅭󠇚󠆂󠆬󠄠󠄉󠅩󠄣󠄃󠆯󠆡󠄹󠆘󠄜󠆚󠆜󠆹󠆆󠇍󠆇󠅌󠅀ⷶ󠄒󠇤󠅅󠇣󠆙󠆐󠅌󠄖󠆠󠄟󠇂󠅰󠇖󠅴󠇭󠅝󠇑󠇝󠅄󠆰󠅷󠅂󠆶󠄼󠇬󠄀󠆆󠆬󠅦󠇇ⷺ󠄸󠇇ⷰ󠅍󠆗󠆛󠅣󠅗󠄛󠇐󠇗󠄕󠇕󠅆󠇨󠅆󠆕󠅈ⷴ󠇦󠆖󠅓󠄓󠆢󠆄󠄚ⷷ󠄾󠄜󠆴󠆜󠆝󠄏󠆀󠆱󠇯󠅙󠅞󠅦󠆭󠆘󠇑󠄣󠆈󠄳󠇈󠇚󠅀󠄌󠇁󠄘󠇦󠆴󠅓󠅈󠆴󠅥󠅲󠄸ⷻ󠄈󠄧󠄂󠅼󠅌󠄘󠄸󠅆󠅡󠇩󠄡󠆴󠇅󠇈󠆯󠄚󠄵󠇘󠅎󠆂󠅈󠆤󠆛󠆐󠄒󠇅󠇧󠄇󠅁󠇘󠆊ⷼ󠆄󠆚󠅼󠅙󠅢󠅸󠅉󠄤󠄼󠆅󠄲󠄊󠄬󠇌󠇢󠅄󠇯󠇁󠅄󠅏󠇢󠄥󠆓󠄦ⷹ󠆓󠅿󠄧󠆖󠄔ⷴ󠄓󠄹󠆄󠄄ⷾ󠅉󠄋󠅽󠇐󠇅󠆲󠄦󠄜󠇍󠆦󠄖󠄮󠆨󠅕󠅹󠄍󠅃󠅭󠄢󠆮󠅔󠅰ⷴ󠄃󠆮󠆂󠆂󠇇󠇄󠇄󠆑󠅸󠄐󠄲󠆌󠄠󠄞󠇄󠅺󠆳󠄏ⷹ󠆤󠅣󠇋󠅌󠇅󠆲󠆸󠅏󠅂󠄮󠅗󠅔󠅖󠇢󠆒󠇕󠄍󠄤󠅸󠆐󠇇󠄭󠄷󠄕󠇭󠇗󠄑󠆈󠆬󠇝󠅖󠆋󠄀󠆅󠄲󠄈󠇣󠅐󠆣󠅓󠄴󠅝󠄫󠇑󠇝󠇔ⷹ󠅀󠇇󠅼󠅓󠆔󠅝󠅡󠅈󠅮󠆲󠅶󠄨󠄄󠄎󠄰󠅣󠆳󠅡󠅻󠅼󠇕󠄟󠇚󠆙󠆬󠅶󠆢󠅿󠆝ⷽⷽ󠇇󠇓󠄳󠇈󠆒󠅸󠇘󠅞󠄶󠆿󠄺󠅹ⷻⷶ󠇤󠇥󠇛󠇙󠅾󠆇󠄓󠆪󠇖ⷷ󠅯󠆾󠅽󠅷󠅲ⷾⷽ󠄙󠄴󠄛󠇩󠆛󠆤󠅊󠅛󠆇󠇯󠇘󠄇󠄌󠅂ⷲ󠆡󠄖󠄎󠅔󠅌󠅤󠄂󠆁󠆲󠆃󠇓ⷷ󠆾󠄑󠅌󠅠󠅏󠇤󠆐ⷵ󠅤󠄨󠅜󠄶󠄋󠇏󠄃󠇨󠅕󠄮󠆳󠆢󠄔󠅹󠇐󠇕󠄁󠇌󠄐󠇘󠆱󠄢󠇘󠇑󠅞󠄑ⷱ󠄞󠄞󠆅󠇍󠆽󠄓󠇗󠄥󠇯󠆸󠄃󠄬󠅞󠇥󠄐󠄧󠇞󠄗󠄭󠇭󠇞󠄞󠄓󠅺󠅋󠇋󠅝󠄴󠆥󠆴󠄺󠅲󠄊󠄋󠆢󠅟󠇒󠆄󠇬󠄇󠅒󠆊󠅒󠆐󠆱󠇡󠇠󠇪󠄺󠄲󠆻󠇏󠄊󠄧󠆐󠅿󠄵󠄅󠆹󠄲󠅼ⷳ󠆢󠄄󠄹󠄁󠄤ⷷ󠆪󠄅󠆊󠅖󠅣󠄞󠅩󠄠󠅜󠇒󠇩󠆀󠅞󠆓󠆅󠇪󠅱󠆨󠅻󠅢󠅝󠇓󠄻󠆳󠆓󠆴󠅩󠅲󠅼󠆊󠅝󠅣󠄌󠅔󠇆ⷾ󠇃󠇉󠇙󠄛󠆶󠄻󠄝󠅕󠆵󠅉󠆞󠆾󠆞󠇭󠆥󠇚󠄐󠅹󠄟󠄤󠇁󠅎󠇏󠆪󠄔󠇨󠅱󠇕󠆉ⷻ󠅎󠆅󠇍󠆢󠅩󠆹󠄺󠆛󠄄󠇩󠄄󠇦󠅮󠆻󠄄󠆻󠆧󠆐󠅂󠅷󠄅󠇭󠅓󠅊󠆹󠅌󠆙󠇔󠅶󠆸󠄃󠄱󠆽󠅵󠅊󠄡󠄃󠄔󠇇󠇁󠆋󠇠ⷹ󠆳󠅧󠅽󠇃󠇓󠅃󠄮󠆎󠆡󠅊󠅕󠆓󠅔󠅮󠅂󠇪ⷺ󠅤󠅰󠅬󠅟󠇁󠇒󠅍󠄌󠆨󠅗󠄪󠆉󠆤󠅤󠆒󠇖ⷾ󠄆󠄀󠇉󠄚󠅉󠆺󠅮󠆒󠇚󠄏󠅤󠆪󠅎󠅞󠆪󠄆󠄿󠆐󠆜ⷶ󠇦󠄵󠅮󠄢󠆰󠄓󠇍󠇟󠇈󠅮󠅌󠅚󠄟󠆑󠆱󠄞󠆀󠄽󠇆󠅬󠇏󠇉󠅠󠆡󠅁󠅣󠅟󠅖󠅾󠅊󠇬󠆂󠄟󠄀󠆖󠄆󠇥󠅬󠄞󠅻󠇟󠅠󠇂󠄿󠄁󠄇󠆆󠆞󠄨󠅹󠅦󠇔󠅒󠆴󠇠󠄨󠆺󠆬󠅚󠇨󠇡󠆸󠄩ⷺ󠅓󠄞󠄩󠆧󠅺󠅾󠆶󠄏󠅷󠆲󠇆󠆕󠆬󠄞󠄩󠄰󠄮󠇆󠇇󠅜󠆋ⷶ󠇢󠇯󠅎󠇟󠇍ⷶ󠄸󠇄󠅙󠆪󠅍󠆏󠇜󠆎󠇓󠆟󠆟󠅽󠄿󠇛󠇣󠆐󠅑󠅺󠄺󠆲󠄚󠄇ⷸ󠇦󠇤󠇭ⷻ󠇙󠆞󠄆ⷹ󠄐󠅽󠅹󠅀ⷸ󠇯󠆠󠇀󠄨󠇫󠆬󠆋󠄇󠄄󠆣󠄗ⷵⷽ󠅷󠄯󠅧󠅧ⷻ󠇝󠅀󠇩󠄾󠄈󠆛󠄗󠆋󠆮󠄏󠆧󠄔󠆈󠆽󠅽󠄭󠄪󠄅󠇂󠇆󠅼󠄞󠆏󠅃󠇣󠄡󠇘󠄸󠄴ⷶ󠄾󠄆󠅚󠆞󠅥󠅐󠄆󠅤󠄥󠆟󠅙󠅵󠄍󠆉󠇔󠆼󠇉󠄖󠆷󠄐󠅽󠄀󠅰󠅱󠆓󠄴󠅢󠆺󠇜󠆔󠄥󠆽󠅄󠇡󠇗󠇑ⷴ󠅩ⷼ󠄢󠆜󠄨󠄄󠇍󠇩󠄀󠇩󠆸󠄼󠇉󠆁󠅾󠇝󠅷󠆴󠅽󠆌󠇩󠄵󠇤󠄿󠇫󠇨󠅹󠇬ⷸ󠇞󠄗󠆎󠄊󠅼󠅴󠄴󠅣󠅵󠅚󠇣󠆥󠄄󠅽󠄍󠇀󠆣󠄫󠄤󠅫ⷻ󠆰󠇐󠅞󠄄󠅟󠄎󠆸󠅕󠇗󠇜󠅦󠅧󠅧󠆧󠅧󠇇󠄐󠇜󠅫󠇈󠇁󠇄󠆺󠇜󠄆󠄕ⷸ󠅬󠆑ⷻ󠆦󠆂󠆮󠄍󠆽󠄸󠇌󠅺󠇀󠇜󠅨󠆍󠆻󠇝󠇞󠆳󠆴󠅎󠆬󠇚󠄤󠆂󠅑󠆮󠄧󠆸ⷵⷸ󠇇󠇈󠇤ⷹ󠄊󠅿󠅄󠇈󠆕󠄛󠆤󠇭󠅜󠆺󠄭󠆦󠄄󠄞󠇅󠅎󠇘󠅵󠇣󠄭󠆍󠆠󠄸󠅪󠆎󠆓󠆨󠄭󠅾󠅨󠄝󠅲󠄕󠄻󠆍󠇠󠇨󠇒󠅛󠄥󠆝󠅙󠄦󠇦󠅸󠄳󠄊󠆱󠄕󠅏󠅅󠆵󠄍󠄃󠅾󠇑󠇭󠄧󠆼󠆈󠄂󠅯󠄶󠆘󠄹󠆕󠆵󠅗󠅸󠄂󠅊󠆾󠅐󠇓󠅆󠆒ⷸ󠅳󠆵󠄁󠄎󠆳󠄊󠅾󠆛ⷸ󠇀󠄇󠄃󠄷󠇆󠆶󠅭󠅙󠇖󠄆󠄬󠇛󠇮󠆿󠅠󠅙󠇥󠆐󠅻󠅰󠆗󠅹󠅱󠄵󠅤󠅗󠆽ⷰ󠇢󠄱󠅏󠇒󠆆󠅦󠄔󠄷󠅎ⷲ󠆟󠆷󠆔󠆋󠇃󠆆󠅲ⷵ󠄿󠅣󠄧󠄗󠆏󠄷󠆓󠆫󠅁󠄫󠇑󠇇󠆻󠆩󠇔󠇕󠆪󠇎󠄷󠄥󠇗󠇌󠇩󠄴󠅍ⷹ󠄵󠅍󠆕󠆌ⷹ󠅝󠄅󠅟󠅌󠄅󠅥󠄋󠆟󠆚󠄃󠄅󠆕󠆐󠆩󠅍ⷻ󠆐󠅻󠅑󠅳󠅱󠄙󠆷󠄿󠇨󠅑󠆙󠆂󠆑ⷷ󠇓󠅑󠅒󠅝󠆷ⷴ󠅎󠆘󠅓󠅣ⷻ󠆪󠆌󠆤󠅦󠄪󠄤󠅗󠅍󠇭󠇖󠆑󠆤󠄯󠆐󠇥ⷽ󠄤󠆘󠅧󠆭󠄠󠄜󠇔󠆞󠆨󠄶󠄫󠆺󠆒󠆈󠆴󠆠󠆐󠇔󠄻󠄍󠆒ⷻ󠇫󠆞󠅲󠅂󠆔󠆕󠆅󠇑󠇢󠆩󠄀󠅔󠅎󠄮󠄅ⷾⷴ󠇎󠆐ⷰ󠆇󠆏󠄽󠄜󠆭󠅩󠄰󠅽󠄄󠄱󠅴󠅋󠄢ⷳⷼ󠄆󠇏󠅇󠅔󠄏󠇄ⷱ󠇅󠅭󠅊󠅭󠇑󠄟ⷴⷶ󠆡󠅷󠅫󠇩ⷸ󠆊󠄘󠅶󠆍󠆧󠄛󠄣󠅺󠅲󠇎󠇉󠅴󠇟󠅴󠄉ⷵ󠅥󠇥󠆀󠆙󠄥󠅸󠄧󠆕ⷲ󠇞󠄍󠄩󠆎󠄃󠅇󠇇󠅽󠆸ⷽ󠆱󠇕󠆈󠇦󠅞󠇕ⷽ󠄼󠇙󠅡󠄄󠆋󠇚󠄔󠇥󠄉󠄥󠆍󠅿󠅐󠆗󠄋󠅫󠇏󠄎󠆓󠇄󠅎󠅙ⷺ󠆘󠅵󠆈ⷻ󠄵󠆔󠅈󠄭󠇔󠅍󠇦󠆬󠄜󠄑󠆕󠅉󠆎󠆟󠅻󠆶󠅞󠄭󠇃󠆷󠆎󠄼󠄲󠆹󠇈󠇈󠄒󠄳󠄴󠄽󠅪󠇑󠆥󠇎󠅔󠄞󠅒󠆙󠅺󠄥󠅓󠄧󠆓󠅼󠄾󠄭ⷻ󠄏󠄁󠅩󠄐󠅹󠄯󠆼󠄱󠄼󠆎󠄑ⷰ󠄘󠅽󠄂󠅮󠄳󠄻󠅼󠆅󠄷󠆴ⷰ󠄨ⷼ󠄛󠇑󠅙󠇇󠇍󠇡󠇄󠇁󠄲󠇇󠇀󠅲󠄢󠅙󠆌󠇙󠆋󠅶󠄖ⷸ󠅑󠆓󠄫󠄐󠅧󠅶󠄛󠅭󠅬󠅺󠇫󠅄󠇘󠄖󠇖󠇇󠅌ⷾ󠅚󠅂󠇘󠅳󠇛󠅘󠇞󠄁ⷸ󠄠󠄈󠄔󠇨󠅸󠅺󠄹󠅤󠄽󠇪󠄤󠇤󠆠󠇩󠆨ⷳ󠇊󠄫󠅃󠇂󠆵ⷵ󠆨󠇚󠅝󠄊󠅞󠆥󠄼󠅃ⷳ󠅶󠄅󠇟󠄲󠄒󠆇ⷻ󠅣󠅧󠆓󠄒󠄮󠅀󠄪󠅨󠅢󠇓󠄗󠇝󠄣󠅎󠅴󠇬󠆊󠆝󠄰󠅈󠄟ⷶ󠄪󠅼󠇐󠅰󠆄󠆁󠆑󠇡󠄏󠄞󠄺󠆰󠅪󠄆󠅼󠅪󠄮󠇖󠇝󠆍󠇣󠅹󠆕󠄋󠅝󠅬󠄚󠆌󠅇󠄼󠄬󠇖󠇬󠆤󠇈󠅋󠄃󠅅󠆇󠅋󠅫ⷻ󠄳󠆌󠄘󠆨󠄶󠆷󠇦󠄵󠄳󠇐󠇯󠇩󠄲󠆝󠄃󠄛󠅎󠇤󠇄󠅶󠇇󠆈󠆔󠄸󠇛󠅈󠄿󠅍󠄂󠅩󠅋󠇖󠇩󠇞󠅇󠄩󠆴󠄢󠄎󠆫󠅀󠇞󠄖󠅩󠇠󠇩󠆭󠇘󠅢󠄲󠄿󠅸󠅘󠇦󠆥󠅹󠆒󠇤󠄂󠆱󠆮󠄤󠄫󠆚󠅬ⷸ󠇨󠅌󠆞󠇬󠄻󠆔󠅱󠄶󠅳󠇠󠄤󠇭󠅗󠄜󠆢󠅙󠆧󠅝󠇖󠄉󠆚󠄔󠄳󠇔󠆦󠇭󠅦󠄞󠆅󠆗󠆗ⷷ󠇏󠇧󠆗󠆗󠄗󠆗󠆗ⷹ󠅧󠇑󠅗ⷸ󠄻ⷾ󠇓󠄊󠆓󠅏ⷸ󠅇󠅔ⷲ󠆋󠆠󠄟󠄳󠄙󠆐󠆎󠆎󠆎󠇤󠅥󠄆󠄟󠅹󠅓󠆉󠆍󠅧󠆳󠇢󠄾󠅛󠅁󠆌󠄋󠄶󠇕󠅭󠅕󠇟󠅕󠄲󠅝󠅨󠅡󠄼󠅻󠅩󠇤󠄉󠅢󠅼󠇑󠆥󠅩󠇔󠆊󠅲󠄹󠄉󠆞󠄾󠅕󠆟󠅛󠇜󠇞󠅥󠇍󠆵󠅽󠅘󠆅󠄃󠆦󠆩󠇛󠆝󠆸󠆯󠇞󠅘ⷹ󠄉󠇎󠅮󠄿󠆂󠅑󠅔󠇩󠆺󠆩󠆯󠄱󠇚ⷱ󠆧󠄻󠄖󠅗ⷴ󠅱󠄁󠅳󠇥󠅚󠄲󠄚ⷰ󠅝󠇖󠄍󠆢󠆸󠅆󠇍󠆓󠆁󠄷󠆆󠇤󠇭󠅆󠆔󠅶󠄌󠅖󠇟󠆝󠆄󠇖󠆌󠅘󠅣ⷷ󠄦󠅔󠄃󠆿ⷾⷱ󠇤󠄼󠄰󠅂󠅦󠅁󠆾󠆰󠇈󠆭󠅩󠄼󠄈󠅔󠅙󠆲󠄌󠇏󠆛󠆭󠆓󠆜󠇀󠄚󠆂󠇟󠅊󠄠󠄕󠇧󠄁󠇙󠄛󠄲󠆅󠄴󠆵󠅘󠄖󠄂󠅜󠆓󠄿󠄘ⷴ󠄫󠅅󠄀󠅞󠇋󠆡󠅳󠇆󠅍󠆝󠆏󠄶󠆾󠅓󠅰󠄊󠄅󠅲󠇛󠇣󠇋󠅊󠅛󠄷󠆞󠄶ⷰ󠅍󠅮󠆟󠄧󠇮󠆳󠆦󠆿󠄩󠅲󠄻ⷴ󠄅󠅦󠄛󠆑󠄟󠆕󠄾󠄱ⷶ󠇡󠄹󠅆󠅙󠄸󠄤󠄻󠅽󠅇󠆪ⷱ󠅼󠄺󠇜󠇍󠄽󠆪󠆪󠆮󠄾󠇏󠇦ⷳ󠅆󠅼󠄨󠄠󠇦󠇦󠇬󠄛󠅸ⷾ󠆼󠅞󠅀󠆆󠄭󠆯󠄳󠅼󠄰󠇌󠆨󠅺󠅠󠇖󠄽󠅆󠅐󠅃󠅘󠄈󠇔󠅍󠇓󠅄󠄮󠆆󠅮󠇠󠅱ⷳⷹ󠅰󠅼󠆴󠇫󠇋󠇆󠇡󠆀󠆜󠆜󠄂󠅤󠄎󠇥󠅗󠆎󠄽󠇞󠄨󠄜󠅘󠅠󠆠󠆐󠆻󠄀󠄡󠇑󠇝󠆍󠅨󠅠󠅷󠇟󠄈󠆥󠅾󠇄󠆾󠇋󠅚ⷾ󠄌󠄭ⷷ󠇁󠄸󠄗ⷲ󠇁󠇪󠄊󠅪󠇯󠆵󠆟󠄺ⷹ󠆴󠄀󠇨󠅘󠇛󠆢󠇜󠆠󠇆󠅋󠄬󠆛󠇙󠆇󠄎󠅈󠄖󠆶󠆌󠅅󠄤󠄷󠆐󠇅󠄮󠄨󠅩󠇔󠅘󠅻󠆖󠇘󠄵󠇃󠅘ⷼ󠆝󠅘󠇀󠆷󠄑󠇤ⷳ󠅄󠆖󠆣󠇎󠅈󠆑󠇐󠇦󠄣󠅝󠄷󠇙󠇖󠆂󠇊󠆋󠆖󠄮󠆇󠇉󠄗󠄯󠄷󠆥󠄂󠄬󠄘󠄞󠄼󠅗󠅛󠆗󠆢󠅯󠄴󠄰󠇚󠅂󠄑󠄨󠆅󠅅󠄏󠆥󠇭󠇁󠆼󠅂󠆙ⷹ󠅛󠆻󠆬󠅁󠅣󠇭ⷸ󠇞󠆏󠇢󠆁󠆭󠅎󠅄󠆁󠄉󠆎󠄣󠇘󠇗󠅥󠄽󠆷󠇀󠄭󠄀󠇮󠆱󠄐󠅂󠇟󠆻󠄪󠇧󠄍󠅫󠅊󠆡󠅨󠆂󠅁󠅹󠇤󠅕󠆥󠇤󠇓ⷺ󠇡󠅌󠆏󠄡󠄭󠅘󠅭󠇬󠅡󠇑󠅀󠅍󠆤󠇁󠆀󠆁󠄁󠆧󠄗󠅨󠄰󠄡󠆃󠆖󠇁󠆛󠄊󠇢󠅭󠅑󠆯󠆷󠆑󠄦󠆜󠇊󠆕󠅅󠆄󠅨󠅽󠇐󠆀󠆗󠇆󠄼󠇦󠆱󠅐󠄇󠅮󠅊󠇆󠆫󠇟󠄯󠇜󠇒󠆎󠄔󠇄󠅹󠆳󠇊󠆧󠅘󠇑󠄦󠄍󠆣󠆛󠅈󠄫󠄪ⷺ󠅌󠇉󠅠󠅢󠆒󠄬󠆃󠄉󠅙󠇀󠅛󠇗󠄂󠅂󠄣󠅡󠄥󠄈󠄖󠅰󠄍󠄖󠅨󠇟󠆏󠆌󠄅󠅍󠆺󠅰󠄱󠇑󠇊󠅮󠅄󠅾󠆮󠇪󠅿󠇒󠆋󠇠󠆨󠆽󠆡󠆬󠄠󠄞󠄀󠅈󠅹󠆁󠄉󠆏󠆇󠇭󠆷󠄮󠆳󠅭󠇊󠅽ⷺ󠆫󠅏󠆛󠄌󠆤󠇥󠅐ⷶ󠄏󠄹󠆤󠇈󠆜󠅑󠄾󠆄󠆝󠆈󠇠󠇡󠅃󠄔󠇂󠄸󠇈󠆣󠇬󠄯󠆅󠆇󠄰󠅱󠅹󠅃ⷺ󠅱󠆾󠄣󠅙󠇢󠅧󠆭󠆥󠄧󠆯󠅑󠄱󠆻󠄙󠄎󠄙󠆞󠇊󠆹󠅺󠅛󠆠󠄒󠅷󠄪󠅻󠅲󠅖󠅀󠅔󠅆󠅎󠅶󠇚󠇌󠄘󠅲󠅄󠇯󠄌󠅉󠆍󠇚󠇉ⷵ󠆇󠆰󠅷ⷱ󠄏󠆺󠅅ⷼ󠅋󠆛󠄸󠇜ⷷ󠄅󠅢󠄸󠇡󠇔󠄸󠆑ⷳ󠆬󠇫󠅵󠇤󠅾󠆛󠆅󠄣󠆽󠇬󠅰󠇈󠄸ⷼ󠄵ⷴ󠇉󠆦󠆛󠅵󠆎󠇇󠅢󠆾󠄱󠇡󠅈󠆐󠅮󠄂󠅹󠄯󠅬󠇙󠄾󠅝󠅯󠆤󠇎󠅀󠇍󠄢ⷼ󠇄󠅎󠆎󠄴󠅎󠇊󠆳󠅌󠆹󠆕󠇘󠄾󠅻󠅹󠇨󠄣󠄼󠆥󠇜󠄾󠆎󠆯ⷴ󠇍󠅽󠇐󠅎󠅙󠅁󠅃󠅶󠇪󠇘󠆽󠇧󠅲󠅽󠇨󠇞󠆷󠄒󠅔󠅮󠄺󠄌ⷷ󠅓󠇬󠄾󠄳󠅬󠄞󠄇󠇙󠅱󠆼󠇈󠄠󠆈󠆴󠆵󠄃󠄘󠅦󠆋󠇠󠆏󠆨󠄾󠄰󠆫󠇉󠄮󠅜󠅢󠅏󠅸󠄜󠇈󠆏ⷽ󠇃󠄎󠅦󠆃󠅗󠆜󠅴󠅏󠇨󠅵󠄻󠆅ⷱ󠅅󠅓󠅗󠄼󠄙󠇤󠇫󠇩󠆫󠇓󠆷󠆧󠅧󠅔󠄸󠇅󠄐󠅬󠇯󠅄󠄓󠇅󠆎󠇐ⷵ󠄄󠇣󠆅󠆱󠅤󠅌󠆏ⷴ󠄅󠄻ⷰ󠅙󠆾󠅟󠄓󠄘󠄷󠇎󠇆󠇤󠅗󠇌󠆖󠄨󠇨ⷾ󠅻󠅰󠇓󠇐󠆚󠆰󠅛󠄋󠇐󠄐󠇑󠇝󠄁󠅷󠆂󠇇󠆖󠇧󠄽󠄃󠄙󠅴󠇋󠇦󠅬󠆓󠇦󠆈󠇭󠅘󠆎󠄊󠇈󠆋󠄏󠄺󠆜ⷻ󠇑󠇆󠇆󠅕󠅱󠆉󠇇󠅘󠄠󠅙󠆞󠅿󠄚󠅀󠇙󠇞󠄔󠇋󠇮󠇓󠄰󠆚󠄀󠅆󠇝󠅼󠄢󠄏󠆟󠄊󠅐󠅾󠇢󠄂󠇤󠅷󠆡󠄚󠆄󠅜󠆻󠄛󠆏󠇐ⷵ󠆚󠆜󠇉󠇙󠅈󠄦󠆺󠅐󠇩󠅈ⷱ󠇅󠄏󠄦󠇚󠄧󠆻󠆖󠅽󠄡󠅉󠆮󠇜󠄴󠆄󠆒󠄷󠄃󠄫󠄭󠄿󠇤󠄚󠆑󠄃󠅖󠄮ⷰ󠄯󠄗󠅞󠇔󠅂󠆩󠄙󠄾󠅱󠅝󠆃󠅏󠇐ⷴ󠅘󠄾󠄸󠆺󠄝󠅢󠇢󠄣󠄢󠆧󠄗󠆀󠄎󠆃󠄧󠅉󠄐󠅲󠆊󠄘ⷾ󠅿󠇥󠆜ⷽ󠅥󠇈󠆲󠇣󠇗󠆮󠆃󠇉󠇝󠆅󠅈󠇈󠅏󠅘󠆉󠆅󠄊󠅋ⷹ󠇖󠆫󠅿󠅆󠇗󠄂ⷺ󠄋󠅇󠄋󠆇󠄃󠆕󠅂󠆓󠄕󠇀󠆠ⷾ󠆬󠆘󠆾󠆣󠄜󠅋󠄻ⷸ󠄠󠇯󠄽󠇖󠅮󠄌ⷲⷲ󠇤󠄬󠄉ⷱⷼ󠇯󠇢󠇙󠅗󠅟ⷾ󠅮󠇅󠆵󠄙󠅝󠄏󠇉󠇦󠇧󠆦󠄙󠄏󠅴󠅌󠇫󠄟󠅥󠄫󠄔󠆪󠄆󠇡󠄋󠇙󠇈󠆟ⷼ󠆘ⷶ󠇧󠆪ⷽⷴ󠇝󠇌󠆴ⷾ󠅞󠇏ⷷⷽ󠇌󠄀󠄫󠄌󠆺󠇁󠄷󠇍󠄊󠄂󠆿󠄎󠆓󠆭󠅟󠄘󠄮󠇤󠇧󠆯ⷽ󠆵󠆨󠄏󠅠ⷻⷸⷺ󠄣󠇘󠄾ⷻ󠇪󠇌󠇕󠄐󠅽󠆩󠇠󠆠󠇠󠄵󠇣󠅕󠅫󠇯󠅠󠇓󠆻󠆎󠅝󠇜󠅬󠆪󠅛󠇜󠇔󠇋󠆢󠇬󠄠󠄭󠇀󠇬󠄈󠆎󠅳󠄬󠆅󠅸󠄯󠅳󠄈󠄈󠆎󠄅󠆴󠅏󠇂󠇘󠅋󠅟ⷸ󠄓󠅭ⷾ󠆏󠆁󠄈󠆭󠇮ⷼ󠄔󠅅󠄒󠆺󠅶󠇞󠇊󠆯ⷰ󠆍󠆯󠆅󠆜󠅷󠆍ⷹ󠄉ⷾ󠄐󠅚󠄍󠅟ⷹ󠆭󠆀󠆐󠅊󠅸󠆵󠅆󠅘󠄠󠅕ⷺ󠄉󠇙󠄌󠅸󠅡󠆕󠄐󠇇󠄆󠆺󠅑󠄋󠆖' 78 | exec(zlib.decompress(bytes(ord(c)&255 for c in glyph[1:])).decode(), globals()) 79 | wat / 'WAT is going on?' 80 | ``` 81 | 82 | ## Usage & modifiers 83 | `wat` can quickly inspect things 84 | by using the division operator (for faster typing without parentheses). 85 | A short syntax `wat / foo` is equivalent to `wat(foo)`. 86 | 87 | You can call `wat.modifier / foo` with the following **modifiers**: 88 | 89 | - `.short` or `.s` to hide the attributes (variables and methods inside the object) 90 | and print only value, type, parent types, signature and documentation 91 | - `.dunder` to display dunder attributes (starting with double underscore) 92 | - `.long` to show non-abbreviated values and docstrings 93 | - `.code` to reveal the source code of a function, method, or class 94 | - `.nodocs` to hide documentation for functions and classes 95 | - `.caller` to show how and where the inspection was called (works in files, not REPL) 96 | - `.public` to show only public attributes (hiding private attributes) 97 | - `.all` to include all available information 98 | - `.ret` to return the object back after the inspection 99 | - `.str` to return the output string instead of printing it 100 | - `.gray` to disable colorful output in the console 101 | - `.color` to enforce colorful outputs in the console 102 | 103 | You can chain modifiers, e.g. `wat.short.str.gray / 'foo'`. 104 | 105 | Call `wat.locals` to inspect local variables. 106 | Call `wat.globals` to inspect global variables. 107 | 108 | You can explore any object. 109 | In Python, an "object" refers to not only to data structures, 110 | but also to functions, classes, modules, built-in types, and more. 111 | 112 | Type `wat` in the interpreter to learn more about this object itself. 113 | 114 | There are several alternative syntaxes that are equivalent. 115 | Choose the one that works best for you: 116 | ```python 117 | wat.short / 'foo' # fast typing 118 | wat.short('foo') 119 | wat('foo', short=True) # natural Python syntax 120 | 'foo' | wat.short # Unix piping 121 | ``` 122 | 123 | ## Use Case Examples 124 | 125 | ### Determine type 126 | In a dynamic typing language like Python, it's often hard to determine the type of an object. 127 | WAT Inspector can help you with that by showing the name of the type with the module it comes from. 128 | 129 | ```python 130 | >>> wat.short / (1,) 131 | value: (1,) 132 | type: tuple 133 | len: 1 134 | ``` 135 | 136 | ```python 137 | >>> wat.short / {None} 138 | value: {None} 139 | type: set 140 | len: 1 141 | ``` 142 | 143 | ```python 144 | >>> wat.short / user 145 | str: admin 146 | repr: 147 | type: django.contrib.auth.models.User 148 | parents: django.contrib.auth.models.AbstractUser, django.contrib.auth.base_user.AbstractBaseUser, django.contrib.auth.models.PermissionsMixin, django.db.models.base.Model, django.db.models.utils.AltersData 149 | ``` 150 | 151 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-short-types.png?raw=true) 152 | 153 | Now that you've identified the actual type, 154 | you can put the type annotations in your code to reduce further confusion. 155 | 156 | ### Look up methods 157 | By listing out methods with their signatures and docstrings, you can easily grasp how to use the unknown object. 158 | 159 | ```python 160 | wat / ['foo'] 161 | ``` 162 | 163 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-list.png?raw=true) 164 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-set.png?raw=true) 165 | 166 | Use `wat.long` if you want to see full doscstrings. 167 | 168 | ### Discover function's signature 169 | See the docstrings and the signature of a function to learn how to use it. 170 | 171 | ```python 172 | wat / str.split 173 | ``` 174 | 175 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-str-split.png?raw=true) 176 | 177 | ### Look up attributes 178 | List the attribues and their types to see what's really inside the inspected object. 179 | ```python 180 | wat / re.match('(\d)_(.*)', '1_title') 181 | ``` 182 | 183 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-re-match.png?raw=true) 184 | 185 | ### Explore modules 186 | Another use case is to explore modules. 187 | You can list the functions, 188 | classes and sub-modules of a selected module. 189 | 190 | ```python 191 | import pathlib 192 | wat / pathlib 193 | ``` 194 | 195 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-pathlib.png?raw=true) 196 | 197 | Then, you can navigate further, e.g. `wat / pathlib.fnmatch`. 198 | 199 | ### Explore dunder attributes 200 | By default, WAT Inspector hides attributes starting with `__`. Use `wat.dunder` to see them. 201 | ```python 202 | wat.dunder / {} 203 | ``` 204 | 205 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-dunder-dict.png?raw=true) 206 | 207 | ### Review the code 208 | Look up the source code of a function to see how it really works. 209 | 210 | ```python 211 | import colorsys 212 | wat.code / colorsys.hsv_to_rgb 213 | ``` 214 | 215 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-code-wat-call.png?raw=true) 216 | 217 | ### Prettify unreadable collections 218 | Nested dictionaries and lists get nicely formatted, indented output: 219 | 220 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-nested-dict-pretty.png?raw=true) 221 | 222 | ### Debug with breakpoint 223 | You can use Python's `breakpoint()` keyword to launch an interactive debugger in your program. 224 | Attach to the interpreter and inspect things on the spot. 225 | 226 | ```python 227 | (Pdb) import wat # or paste Insta-Load snippet 228 | (Pdb) wat / foo # inspect local variables 229 | ... 230 | (Pdb) c # continue execution 231 | ``` 232 | 233 | ### Look up local variables 234 | Use `wat.locals` or `wat.globals` to look up the local and global variables respectively. 235 | 236 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-locals.png?raw=true) 237 | 238 | ### Learn Python 239 | With these snippets you can better understand Python internals. 240 | 241 | ```python 242 | reversed([]) == reversed([]) 243 | # False 244 | wat.s / reversed([]) 245 | # value: 246 | # type: list_reverseiterator 247 | ``` 248 | 249 | ```python 250 | wat / type('ObjectCreator', (), {}) 251 | # value: 252 | # type: type 253 | # signature: class ObjectCreator() 254 | 255 | wat / type 256 | # value: 257 | # type: type 258 | # signature: class type(…) 259 | # """ 260 | # type(object) -> the object's type 261 | # type(name, bases, dict, **kwds) -> a new type 262 | # """ 263 | # 264 | # Public attributes: 265 | # def mro(self, /) # Return a type's method resolution order. 266 | ``` 267 | 268 | ```python 269 | from typing import List 270 | wat.s / List[str] 271 | # value: typing.List[str] 272 | # type: typing._GenericAlias 273 | # parents: typing._BaseGenericAlias, typing._Final 274 | # signature: def List(*args, **kwargs) 275 | 276 | wat(str | None) 277 | # value: str | None 278 | # type: types.UnionType 279 | ``` 280 | 281 | Explore Python built-ins: 282 | ```python 283 | wat / __builtins__ 284 | wat / ... 285 | ``` 286 | 287 | ### Inspect WAT itself 288 | ```python 289 | wat.dunder / wat 290 | wat.code / wat.__truediv__ 291 | ``` 292 | 293 | ## Environment variables 294 | - `WAT_COLOR="false"` to disable colorful output in the console. 295 | - `WAT_COLOR="true"` to enforce colorful outputs even in non-tty environment. 296 | 297 | ### Color theme 298 | You can customize the color theme by setting the environment variable `WAT_COLORS`. 299 | Here's the default theme which you can modify with your own ANSI color codes: 300 | ```sh 301 | export WAT_COLORS="BAR=0;34,TRAIT=1;34,HEAD=1;37,STR=0;32,NUMBER=0;31,NONE=0;35,TRUE=1;32,FALSE=1;31,DOCS=2;37,KEYWORD=0;34,CALLABLE=1;32,SIGNATURE=0;32,VARIABLE=1;33,CODE=0;33" 302 | ``` 303 | 304 | ## References 305 | - Inspired by [Rich Inspect](https://github.com/Textualize/rich?tab=readme-ov-file#rich-inspect) 306 | -------------------------------------------------------------------------------- /docs/img/screenshot-playbook.md: -------------------------------------------------------------------------------- 1 | ## wat-intro-set.png 2 | ``` 3 | python -m IPython 4 | ``` 5 | ``` 6 | import wat 7 | wat / {42} 8 | ``` 9 | 10 | 11 | ## wat-datetime-now.png 12 | 13 | - terminal 90x27 14 | - `title wat` 15 | - `python` 16 | ```python 17 | import wat 18 | wat.str / datetime.datetime.now() 19 | ``` 20 | 21 | ```python 22 | wat_out = "\x1b[0;34m──────────────────────────────────────────────────────────────────────────────────────────\x1b[0m\n\x1b[1;34mstr:\x1b[0m \x1b[0;32m2024-07-31 21:30:28.163527\x1b[0m\n\x1b[1;34mrepr:\x1b[0m \x1b[1mdatetime.datetime(2024, 7, 31, 21, 30, 28, 163527)\x1b[0m\n\x1b[1;34mtype:\x1b[0m \x1b[0;33mdatetime.datetime\x1b[0m\n\x1b[1;34mparents:\x1b[0m \x1b[0;33mdatetime.date\x1b[0m\n\n\x1b[1mPublic attributes:\x1b[0m\n \x1b[1;33mday\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m31\x1b[0m\x1b[0m\n \x1b[1;33mfold\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m0\x1b[0m\x1b[0m\n \x1b[1;33mhour\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m21\x1b[0m\x1b[0m\n \x1b[1;33mmax\x1b[0;33m: \x1b[0;33mdatetime.datetime\x1b[0m = \x1b[0;32m9999-12-31 23:59:59.999999\x1b[0m\x1b[0m\n \x1b[1;33mmicrosecond\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m163527\x1b[0m\x1b[0m\n \x1b[1;33mmin\x1b[0;33m: \x1b[0;33mdatetime.datetime\x1b[0m = \x1b[0;32m0001-01-01 00:00:00\x1b[0m\x1b[0m\n \x1b[1;33mminute\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m30\x1b[0m\x1b[0m\n \x1b[1;33mmonth\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m7\x1b[0m\x1b[0m\n \x1b[1;33mresolution\x1b[0;33m: \x1b[0;33mdatetime.timedelta\x1b[0m = \x1b[0;32m0:00:00.000001\x1b[0m\x1b[0m\n \x1b[1;33msecond\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m28\x1b[0m\x1b[0m\n \x1b[1;33mtzinfo\x1b[0;33m: \x1b[0;33mNoneType\x1b[0m = \x1b[0;35mNone\x1b[0m\x1b[0m\n \x1b[1;33myear\x1b[0;33m: \x1b[0;33mint\x1b[0m = \x1b[0;31m2024\x1b[0m\x1b[0m\n\n \x1b[0;34mdef \x1b[1;32mastimezone\x1b[0;32m(…)\x1b[0m \x1b[2;37m# tz -> convert to local time in new timezone tz\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mcombine\x1b[0;32m(…)\x1b[0m \x1b[2;37m# date, time -> datetime with same date and time fields\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mctime\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return ctime() style string.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mdate\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return date object with same year, month and day.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mdst\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return self.tzinfo.dst(self).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mfromisocalendar\x1b[0;32m(…)\x1b[0m \x1b[2;37m# int, int, int -> Construct a date from the ISO year, week number and weekday.…\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mfromisoformat\x1b[0;32m(…)\x1b[0m \x1b[2;37m# string -> datetime from a string in most ISO 8601 formats\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mfromordinal\x1b[0;32m(…)\x1b[0m \x1b[2;37m# int -> date corresponding to a proleptic Gregorian ordinal.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mfromtimestamp\x1b[0;32m(…)\x1b[0m \x1b[2;37m# timestamp[, tz] -> tz's local time from POSIX timestamp.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32misocalendar\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return a named tuple containing ISO year, week number, and weekday.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32misoformat\x1b[0;32m(…)\x1b[0m \x1b[2;37m# [sep] -> string in ISO 8601 format, YYYY-MM-DDT[HH[:MM[:SS[.mmm[uuu]]]]][+HH:MM].…\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32misoweekday\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return the day of the week represented by the date.…\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mnow\x1b[0;32m(tz=None)\x1b[0m \x1b[2;37m# Returns new datetime object representing current time local to tz.…\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mreplace\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return datetime with new specified fields.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mstrftime\x1b[0;32m(…)\x1b[0m \x1b[2;37m# format -> strftime() style string.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mstrptime\x1b[0;32m(…)\x1b[0m \x1b[2;37m# string, format -> new datetime parsed from a string (like time.strptime()).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtime\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return time object with same time but with tzinfo=None.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtimestamp\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return POSIX timestamp as float.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtimetuple\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return time tuple, compatible with time.localtime().\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtimetz\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return time object with same time and tzinfo.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtoday\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Current date or datetime: same as self.__class__.fromtimestamp(time.time()).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtoordinal\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return proleptic Gregorian ordinal. January 1 of year 1 is day 1.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mtzname\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return self.tzinfo.tzname(self).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mutcfromtimestamp\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Construct a naive UTC datetime from a POSIX timestamp.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mutcnow\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return a new datetime representing UTC day and time.\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mutcoffset\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return self.tzinfo.utcoffset(self).\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mutctimetuple\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return UTC time tuple, compatible with time.localtime().\x1b[0m\x1b[0m\n \x1b[0;34mdef \x1b[1;32mweekday\x1b[0;32m(…)\x1b[0m \x1b[2;37m# Return the day of the week represented by the date.…\x1b[0m\x1b[0m\n\x1b[0;34m──────────────────────────────────────────────────────────────────────────────────────────\x1b[0m" 23 | RESET ='\033[0m' 24 | STYLE_BRIGHT = '\033[1m' 25 | STYLE_DIM = '\033[2m' 26 | STYLE_RED = '\033[0;31m' 27 | STYLE_BRIGHT_RED = '\033[1;31m' 28 | STYLE_GREEN = '\033[0;32m' 29 | STYLE_BRIGHT_GREEN = '\033[1;32m' 30 | STYLE_YELLOW = '\033[0;33m' 31 | STYLE_BRIGHT_YELLOW = '\033[1;33m' 32 | STYLE_BLUE = '\033[0;34m' 33 | STYLE_BRIGHT_BLUE = '\033[1;34m' 34 | STYLE_MAGENTA = '\033[0;35m' 35 | STYLE_CYAN = '\033[0;36m' 36 | STYLE_WHITE = '\033[0;37m' 37 | STYLE_GRAY = '\033[2;37m' 38 | 39 | output = '\n'.join([ 40 | f'>>> {STYLE_BRIGHT_GREEN}import {STYLE_BRIGHT_BLUE}wat{RESET}', 41 | f'>>> {STYLE_BRIGHT_GREEN}import {STYLE_BRIGHT_BLUE}datetime{RESET}', 42 | f'>>> wat / datetime.datetime.now()', 43 | wat_out, 44 | ]) 45 | print(output) 46 | ``` 47 | 48 | ## wat-nested-dict-pretty.png 49 | ```python 50 | wat.s / {"people":[{"name":"Alice","age":21,"pets":None},{"name":"Bob", "age":42,"pets":['Kitty','Fluffy'],"items":[{'name':"teapot","price":10.99,'in_stock':True}]}]} 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/img/wat-code-wat-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-code-wat-call.png -------------------------------------------------------------------------------- /docs/img/wat-datetime-now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-datetime-now.png -------------------------------------------------------------------------------- /docs/img/wat-dunder-dict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-dunder-dict.png -------------------------------------------------------------------------------- /docs/img/wat-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-list.png -------------------------------------------------------------------------------- /docs/img/wat-locals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-locals.png -------------------------------------------------------------------------------- /docs/img/wat-nested-dict-pretty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-nested-dict-pretty.png -------------------------------------------------------------------------------- /docs/img/wat-pathlib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-pathlib.png -------------------------------------------------------------------------------- /docs/img/wat-re-match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-re-match.png -------------------------------------------------------------------------------- /docs/img/wat-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-set.png -------------------------------------------------------------------------------- /docs/img/wat-short-types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-short-types.png -------------------------------------------------------------------------------- /docs/img/wat-str-split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-str-split.png -------------------------------------------------------------------------------- /docs/img/wat-string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/docs/img/wat-string.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | --- 5 | 6 | # 🙀 WAT 7 | 8 |
9 | GitHub 10 | - 11 | PyPI 12 | - 13 | Documentation 14 |
15 | 16 | Deep inspection of Python objects. 17 | 18 | **WAT** is a powerful inspection tool 19 | designed to help you explore unknown objects and examine them at runtime. 20 | 21 | > "Wat" is a variant of the English word "what" that is often used to express confusion or disgust 22 | 23 | If you ever find yourself in a Python console, feeling lost and confused, 24 | and wondering "WAT? What is this thing?", 25 | that's where `wat` inspector comes in handy. 26 | 27 | Launch the Python Interpreter and execute `wat / object` on any `object` 28 | to investigate its 29 | **type**, **formatted value**, **variables**, **methods**, **parent types**, **signature**, 30 | **documentation**, and its **source code**. 31 | This makes it particularly useful for debugging or understanding intricate data structures in Python, 32 | providing a straightforward way to answer "what" exactly an object represents. 33 | 34 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-datetime-now.png?raw=true) 35 | 36 | 38 | 39 | Alternatively, use `wat(object)` syntax for the same in-depth inspection. 40 | 41 | ## Loading 42 | 43 | ### Insta-Load 44 | If you want to quickly debug something, 45 | you can use this inspector **without installing anything**, in the same session. 46 | 47 | Load it on the fly by pasting this snippet to your Python interpreter: 48 | ```python 49 | import base64, zlib 50 | code = b'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' 51 | exec(zlib.decompress(base64.b64decode(code)).decode(), globals()) 52 | ``` 53 | 54 | Now you can use `wat` object. 55 | 56 | !!! warning 57 | Before executing **Insta-Load** snippet, it's recommended to verify what you're about to run. 58 | You can either: 59 | 60 | - Verify what's inside the extracted code beforehand: 61 | ```python 62 | print(zlib.decompress(base64.b64decode(code)).decode()) 63 | ``` 64 | - Paste the content of [inspection.py](https://github.com/igrek51/wat/blob/master/wat/inspection/inspection.py) into your interpreter. 65 | It has the same effect. 66 | - [Install package with pip](#install-with-pip) and review the code. 67 | 68 | ### Install with pip 69 | Alternatively, install **wat** package and import inspection tool from **wat** module: 70 | ```sh 71 | pip install wat 72 | ``` 73 | ```python 74 | import wat 75 | ``` 76 | This package has no external dependencies. 77 | 78 | ### Load from Unicode Glyph 79 | Fun Fact: You can load WAT from a single Unicode glyph. 80 | ```python 81 | import zlib 82 | glyph = '🙀󠅸󠆜󠇕󠅛󠇫󠅮󠇜󠇆󠄕ⷾ󠇯󠆧󠄠󠇔󠄟󠄤󠆝󠇍󠇖󠅎󠅺󠄁󠆔󠇐󠆭󠅬󠅯󠅒󠆣󠆎󠅕󠇈󠅊󠆂󠅂󠄒󠄈󠇮󠅲󠅖󠅢󠇄󠄥󠄗󠄤󠇗󠇒󠅶󠆱󠅀󠄞󠆢󠇏󠇐󠄇󠇋󠆓ⷴ󠅜󠇦󠅎󠅲󠄥󠇇󠄅󠇚󠄊󠆰󠅄󠇎󠇌ⷹ󠇦󠇌󠄹󠅧󠇎󠅥󠆆󠅞󠄶ⷵ󠄪󠇈󠆳󠄮󠅛󠆔󠅙󠇛󠆊󠄶󠄨󠅖󠇫󠆺󠇩󠅌󠇓󠄓󠇙󠅐󠅔󠇭󠅚󠄬󠄺ⷵ󠅚󠇫󠆎󠅆󠆨󠆧󠅶󠇛󠄾󠅙󠄢󠅞󠆷󠅝󠄗󠇕󠆵󠆂󠄺󠆩󠆶󠆓󠇠󠅴󠇝󠄕󠅵󠆕󠆕󠆓󠇠󠅼󠆻󠄖󠅏ⷾ󠅬ⷰ󠇩󠅷ⷰ󠆆󠇡󠅟󠇕󠇕󠆲󠆸󠄾󠅾󠄒󠇀󠅏󠅻󠄃󠇔󠇇󠇁󠆼󠆮󠅋󠅺󠇏󠄷󠅕󠄮󠄚󠆫󠆡󠆪ⷳ󠅺󠇑󠅚󠄍󠅥󠅝󠅝󠅛󠆯󠆋󠄺󠄗ⷶ󠅫󠅖󠆖󠄎ⷽ󠆺󠄩󠄾󠅤󠆝󠄚󠄲󠇆󠇒󠅉󠇗󠄵󠇅󠅼󠄃󠇣󠅸󠇒󠅬󠄅󠄔󠅭󠇗󠇐󠇛󠆇󠆬󠇜󠇀󠄫󠄬󠆑󠅞󠅡󠇡ⷰ󠅆󠄫󠅔󠄳󠅦ⷳ󠅒󠄜󠅚󠆄󠇋󠄄󠆭󠆻󠆸󠆮󠆲󠅮󠇓󠅀󠆛󠆒󠇚󠄅󠇌󠅷󠇅ⷴⷵ󠇂󠅯󠇎󠇅󠅒󠅩󠄧󠅝󠇖󠇍󠄪󠇫󠆢󠅺ⷾ󠇓󠄤󠅸󠄺󠅡󠄑󠄦󠇟󠅤󠅥󠄫󠄦󠅲󠅪ⷵ󠇆󠇂󠅓󠅯󠄨󠄹ⷵ󠆌󠅢󠇓󠇏󠄤󠄳ⷵ󠆶󠇞󠇌󠇋󠅢󠆡󠇞󠆠󠆋󠄟󠇣󠅣󠄩󠅮󠇔󠅝󠇢󠅨󠄲󠅢󠄎󠇨󠆷󠇦󠆀󠄄󠅵󠆃󠄐󠆚󠄓ⷾ󠄣󠅙󠇁󠅟󠅺󠄀󠄱󠆄󠆿󠅌󠄋󠆳󠇅󠅿󠅴󠆫󠄔󠄥󠄠󠅵󠆒󠇕󠆘󠄘󠆫󠄷󠇝󠅺󠇓󠄥󠅥󠇑󠅶󠅑󠆺󠄭󠅄󠆙󠆧󠅊󠅞󠅥󠅑󠆉󠆖󠇅󠇅󠇬󠇇󠅌󠅒󠄬󠇑󠆚󠆧󠅭󠆗󠄃ⷱ󠆴󠅨󠆳󠆮󠇛󠅆󠅲󠆝󠆤󠅨󠇑󠆬󠄊󠇐󠅀󠅺󠅗󠇤󠇝󠅍󠅒󠆷󠇓󠅫󠇑󠆥󠆺󠆵󠄭ⷾ󠄡󠆢󠅸󠆺󠆨󠇋󠇍󠆪󠅪󠄵󠄕󠄳󠄲󠆅󠇉󠅅󠇓󠅅󠇏󠅀󠄿󠇝󠆶󠄔󠇓󠆗󠄧󠅧󠇁󠅧󠅁ⷸ󠇋󠄿󠅿󠄎󠆃󠆧󠄞󠄴󠅴󠆜󠇍󠇞󠇏󠇎󠅣󠄟󠄤󠅛󠆯󠅅󠆕󠅇󠄟󠆋󠇐󠄈󠆰󠆬󠄪󠄈󠄯󠆫󠅰ⷺ󠅓󠅝󠅔󠄑󠇃󠇅󠅤󠅅󠄏󠄊󠇧󠅘󠄉󠆇󠇟󠆧󠅲󠅋󠅩󠇞󠆈󠄾󠄠󠄟󠆐󠅞󠆋󠅊󠄴󠆠󠆎󠆔󠄇󠄁󠇪󠆲󠆎󠆘󠄉󠄰󠇛󠆔󠅶󠅎󠄢󠄍󠆖󠇟󠅸󠄪󠇒󠅹󠆳󠄑󠆊󠇡󠆵󠄚󠆌󠆏󠄸󠅆󠆫󠇈ⷴ󠄅󠅉󠆂󠆨󠇔󠆋ⷶ󠇠ⷶ󠆤󠇐󠅕󠆬󠅓󠅐󠅇󠇝󠅄󠅺ⷲ󠆸󠇇󠅷󠆸󠅣󠅱󠆞󠆟󠆝󠆼󠄹󠇟ⷳ󠇞󠇞󠆑ⷰⷶ󠇁󠅎󠇓󠇭󠅃󠆢󠄓󠅠ⷹ󠄏󠄠󠄀󠇉󠄸ⷽ󠄨󠄕ⷲ󠅮󠆓󠅡󠇏󠅟󠅦󠄧󠆯ⷷ󠄻󠆳󠆪󠆽󠇬󠄏󠆵󠄼󠇑ⷵ󠅨󠅱󠇢󠅋󠅄󠆿󠅐󠄠󠄬󠆯󠇁󠆹󠇈󠅡󠄹󠄬󠅢󠆋󠆄󠅝󠅧󠆍󠆨󠆺󠄶󠄉󠄧󠆁󠄴󠆖󠄔󠆭󠆜󠆛󠅩󠅠󠅫󠅍󠆢󠆵󠄢󠇉󠄞󠆐󠆍󠄚󠆥󠄧󠆗󠄍󠅲󠅮󠆴󠄱󠇩󠄽󠄣󠆘󠄔ⷶ󠅟󠇃󠇦󠄑󠆦󠅩󠄩󠆪󠄴󠄅󠆦󠇞󠇕󠆕󠆈󠇭󠅍󠇙󠅬󠇍󠇋󠇨󠇄󠅀󠅭󠄦󠅵󠇭󠄏󠆺󠅸󠄭󠆖󠆊󠇄ⷽ󠅂󠆬󠄻ⷲ󠇩󠆳󠆦󠆩󠄛󠅷󠆆󠄵󠆆󠄋󠆟󠅟󠆄󠄰󠇃󠄰󠅠󠄤ⷾ󠄒󠆰󠆑󠇖󠄐ⷾⷲⷳ󠆿󠅂󠆳󠆹󠆵󠇯󠅏󠅈󠇔󠄊󠄲󠇕󠇭󠄑󠅒󠅎󠄂󠆽󠄋󠇆󠅭󠅏󠅇󠄑󠆣󠅞󠇕󠄤󠆗󠄇ⷾ󠆖󠆧󠆁󠆇󠇁󠆽󠄇󠆋󠆂󠆮󠄠󠆫ⷲ󠄠󠅂󠆧󠄪󠅷󠄽󠅻󠇪󠆘󠇚󠅇󠄖󠇍󠆔󠇠󠄀󠄷󠅕󠄗󠆡󠆳󠆉󠅱󠄟󠄾󠄻󠆨󠆞󠇗󠆧󠆯󠇞󠇯󠆏󠆎󠆎󠅶󠅀󠅉󠅿󠅭󠄓󠇯󠇯󠄸󠄋󠄁ⷽ󠇍󠅅󠄏󠄇󠆤󠄋󠅈󠄓󠆘󠇀󠄂󠆻ⷲ󠄽󠄘󠆆󠄕󠅚󠆠ⷴ󠅺󠇠󠇬󠄩ⷼ󠅫󠅗󠇢󠆬󠇐󠅚󠅢󠅛󠅯󠆚󠆅󠇔󠄓󠄿󠆧󠄈󠄖󠄹󠆪󠇁󠄸󠅂󠅽󠆏󠄰󠅌󠄞󠇈󠅙󠆋󠅤ⷷ󠆲󠇚󠅱󠆫󠇙󠄕󠆖󠄞󠄸󠅁󠇒󠇀󠆙󠅊󠅔󠇚󠆤󠆅󠄎󠆑󠅇󠅩󠄁󠆡󠄠󠄵󠇭󠆎󠄯󠆟󠄄󠆷󠅢󠆛󠆔󠇙󠅪󠆞󠅧󠅄󠅻󠅌󠆿󠆧󠅨󠅠󠄽󠇛󠄢󠆏󠄎ⷻ󠄳󠆗󠅸󠅭󠇚󠆂󠆬󠄠󠄉󠅩󠄣󠄃󠆯󠆡󠄹󠆘󠄜󠆚󠆜󠆹󠆆󠇍󠆇󠅌󠅀ⷶ󠄒󠇤󠅅󠇣󠆙󠆐󠅌󠄖󠆠󠄟󠇂󠅰󠇖󠅴󠇭󠅝󠇑󠇝󠅄󠆰󠅷󠅂󠆶󠄼󠇬󠄀󠆆󠆬󠅦󠇇ⷺ󠄸󠇇ⷰ󠅍󠆗󠆛󠅣󠅗󠄛󠇐󠇗󠄕󠇕󠅆󠇨󠅆󠆕󠅈ⷴ󠇦󠆖󠅓󠄓󠆢󠆄󠄚ⷷ󠄾󠄜󠆴󠆜󠆝󠄏󠆀󠆱󠇯󠅙󠅞󠅦󠆭󠆘󠇑󠄣󠆈󠄳󠇈󠇚󠅀󠄌󠇁󠄘󠇦󠆴󠅓󠅈󠆴󠅥󠅲󠄸ⷻ󠄈󠄧󠄂󠅼󠅌󠄘󠄸󠅆󠅡󠇩󠄡󠆴󠇅󠇈󠆯󠄚󠄵󠇘󠅎󠆂󠅈󠆤󠆛󠆐󠄒󠇅󠇧󠄇󠅁󠇘󠆊ⷼ󠆄󠆚󠅼󠅙󠅢󠅸󠅉󠄤󠄼󠆅󠄲󠄊󠄬󠇌󠇢󠅄󠇯󠇁󠅄󠅏󠇢󠄥󠆓󠄦ⷹ󠆓󠅿󠄧󠆖󠄔ⷴ󠄓󠄹󠆄󠄄ⷾ󠅉󠄋󠅽󠇐󠇅󠆲󠄦󠄜󠇍󠆦󠄖󠄮󠆨󠅕󠅹󠄍󠅃󠅭󠄢󠆮󠅔󠅰ⷴ󠄃󠆮󠆂󠆂󠇇󠇄󠇄󠆑󠅸󠄐󠄲󠆌󠄠󠄞󠇄󠅺󠆳󠄏ⷹ󠆤󠅣󠇋󠅌󠇅󠆲󠆸󠅏󠅂󠄮󠅗󠅔󠅖󠇢󠆒󠇕󠄍󠄤󠅸󠆐󠇇󠄭󠄷󠄕󠇭󠇗󠄑󠆈󠆬󠇝󠅖󠆋󠄀󠆅󠄲󠄈󠇣󠅐󠆣󠅓󠄴󠅝󠄫󠇑󠇝󠇔ⷹ󠅀󠇇󠅼󠅓󠆔󠅝󠅡󠅈󠅮󠆲󠅶󠄨󠄄󠄎󠄰󠅣󠆳󠅡󠅻󠅼󠇕󠄟󠇚󠆙󠆬󠅶󠆢󠅿󠆝ⷽⷽ󠇇󠇓󠄳󠇈󠆒󠅸󠇘󠅞󠄶󠆿󠄺󠅹ⷻⷶ󠇤󠇥󠇛󠇙󠅾󠆇󠄓󠆪󠇖ⷷ󠅯󠆾󠅽󠅷󠅲ⷾⷽ󠄙󠄴󠄛󠇩󠆛󠆤󠅊󠅛󠆇󠇯󠇘󠄇󠄌󠅂ⷲ󠆡󠄖󠄎󠅔󠅌󠅤󠄂󠆁󠆲󠆃󠇓ⷷ󠆾󠄑󠅌󠅠󠅏󠇤󠆐ⷵ󠅤󠄨󠅜󠄶󠄋󠇏󠄃󠇨󠅕󠄮󠆳󠆢󠄔󠅹󠇐󠇕󠄁󠇌󠄐󠇘󠆱󠄢󠇘󠇑󠅞󠄑ⷱ󠄞󠄞󠆅󠇍󠆽󠄓󠇗󠄥󠇯󠆸󠄃󠄬󠅞󠇥󠄐󠄧󠇞󠄗󠄭󠇭󠇞󠄞󠄓󠅺󠅋󠇋󠅝󠄴󠆥󠆴󠄺󠅲󠄊󠄋󠆢󠅟󠇒󠆄󠇬󠄇󠅒󠆊󠅒󠆐󠆱󠇡󠇠󠇪󠄺󠄲󠆻󠇏󠄊󠄧󠆐󠅿󠄵󠄅󠆹󠄲󠅼ⷳ󠆢󠄄󠄹󠄁󠄤ⷷ󠆪󠄅󠆊󠅖󠅣󠄞󠅩󠄠󠅜󠇒󠇩󠆀󠅞󠆓󠆅󠇪󠅱󠆨󠅻󠅢󠅝󠇓󠄻󠆳󠆓󠆴󠅩󠅲󠅼󠆊󠅝󠅣󠄌󠅔󠇆ⷾ󠇃󠇉󠇙󠄛󠆶󠄻󠄝󠅕󠆵󠅉󠆞󠆾󠆞󠇭󠆥󠇚󠄐󠅹󠄟󠄤󠇁󠅎󠇏󠆪󠄔󠇨󠅱󠇕󠆉ⷻ󠅎󠆅󠇍󠆢󠅩󠆹󠄺󠆛󠄄󠇩󠄄󠇦󠅮󠆻󠄄󠆻󠆧󠆐󠅂󠅷󠄅󠇭󠅓󠅊󠆹󠅌󠆙󠇔󠅶󠆸󠄃󠄱󠆽󠅵󠅊󠄡󠄃󠄔󠇇󠇁󠆋󠇠ⷹ󠆳󠅧󠅽󠇃󠇓󠅃󠄮󠆎󠆡󠅊󠅕󠆓󠅔󠅮󠅂󠇪ⷺ󠅤󠅰󠅬󠅟󠇁󠇒󠅍󠄌󠆨󠅗󠄪󠆉󠆤󠅤󠆒󠇖ⷾ󠄆󠄀󠇉󠄚󠅉󠆺󠅮󠆒󠇚󠄏󠅤󠆪󠅎󠅞󠆪󠄆󠄿󠆐󠆜ⷶ󠇦󠄵󠅮󠄢󠆰󠄓󠇍󠇟󠇈󠅮󠅌󠅚󠄟󠆑󠆱󠄞󠆀󠄽󠇆󠅬󠇏󠇉󠅠󠆡󠅁󠅣󠅟󠅖󠅾󠅊󠇬󠆂󠄟󠄀󠆖󠄆󠇥󠅬󠄞󠅻󠇟󠅠󠇂󠄿󠄁󠄇󠆆󠆞󠄨󠅹󠅦󠇔󠅒󠆴󠇠󠄨󠆺󠆬󠅚󠇨󠇡󠆸󠄩ⷺ󠅓󠄞󠄩󠆧󠅺󠅾󠆶󠄏󠅷󠆲󠇆󠆕󠆬󠄞󠄩󠄰󠄮󠇆󠇇󠅜󠆋ⷶ󠇢󠇯󠅎󠇟󠇍ⷶ󠄸󠇄󠅙󠆪󠅍󠆏󠇜󠆎󠇓󠆟󠆟󠅽󠄿󠇛󠇣󠆐󠅑󠅺󠄺󠆲󠄚󠄇ⷸ󠇦󠇤󠇭ⷻ󠇙󠆞󠄆ⷹ󠄐󠅽󠅹󠅀ⷸ󠇯󠆠󠇀󠄨󠇫󠆬󠆋󠄇󠄄󠆣󠄗ⷵⷽ󠅷󠄯󠅧󠅧ⷻ󠇝󠅀󠇩󠄾󠄈󠆛󠄗󠆋󠆮󠄏󠆧󠄔󠆈󠆽󠅽󠄭󠄪󠄅󠇂󠇆󠅼󠄞󠆏󠅃󠇣󠄡󠇘󠄸󠄴ⷶ󠄾󠄆󠅚󠆞󠅥󠅐󠄆󠅤󠄥󠆟󠅙󠅵󠄍󠆉󠇔󠆼󠇉󠄖󠆷󠄐󠅽󠄀󠅰󠅱󠆓󠄴󠅢󠆺󠇜󠆔󠄥󠆽󠅄󠇡󠇗󠇑ⷴ󠅩ⷼ󠄢󠆜󠄨󠄄󠇍󠇩󠄀󠇩󠆸󠄼󠇉󠆁󠅾󠇝󠅷󠆴󠅽󠆌󠇩󠄵󠇤󠄿󠇫󠇨󠅹󠇬ⷸ󠇞󠄗󠆎󠄊󠅼󠅴󠄴󠅣󠅵󠅚󠇣󠆥󠄄󠅽󠄍󠇀󠆣󠄫󠄤󠅫ⷻ󠆰󠇐󠅞󠄄󠅟󠄎󠆸󠅕󠇗󠇜󠅦󠅧󠅧󠆧󠅧󠇇󠄐󠇜󠅫󠇈󠇁󠇄󠆺󠇜󠄆󠄕ⷸ󠅬󠆑ⷻ󠆦󠆂󠆮󠄍󠆽󠄸󠇌󠅺󠇀󠇜󠅨󠆍󠆻󠇝󠇞󠆳󠆴󠅎󠆬󠇚󠄤󠆂󠅑󠆮󠄧󠆸ⷵⷸ󠇇󠇈󠇤ⷹ󠄊󠅿󠅄󠇈󠆕󠄛󠆤󠇭󠅜󠆺󠄭󠆦󠄄󠄞󠇅󠅎󠇘󠅵󠇣󠄭󠆍󠆠󠄸󠅪󠆎󠆓󠆨󠄭󠅾󠅨󠄝󠅲󠄕󠄻󠆍󠇠󠇨󠇒󠅛󠄥󠆝󠅙󠄦󠇦󠅸󠄳󠄊󠆱󠄕󠅏󠅅󠆵󠄍󠄃󠅾󠇑󠇭󠄧󠆼󠆈󠄂󠅯󠄶󠆘󠄹󠆕󠆵󠅗󠅸󠄂󠅊󠆾󠅐󠇓󠅆󠆒ⷸ󠅳󠆵󠄁󠄎󠆳󠄊󠅾󠆛ⷸ󠇀󠄇󠄃󠄷󠇆󠆶󠅭󠅙󠇖󠄆󠄬󠇛󠇮󠆿󠅠󠅙󠇥󠆐󠅻󠅰󠆗󠅹󠅱󠄵󠅤󠅗󠆽ⷰ󠇢󠄱󠅏󠇒󠆆󠅦󠄔󠄷󠅎ⷲ󠆟󠆷󠆔󠆋󠇃󠆆󠅲ⷵ󠄿󠅣󠄧󠄗󠆏󠄷󠆓󠆫󠅁󠄫󠇑󠇇󠆻󠆩󠇔󠇕󠆪󠇎󠄷󠄥󠇗󠇌󠇩󠄴󠅍ⷹ󠄵󠅍󠆕󠆌ⷹ󠅝󠄅󠅟󠅌󠄅󠅥󠄋󠆟󠆚󠄃󠄅󠆕󠆐󠆩󠅍ⷻ󠆐󠅻󠅑󠅳󠅱󠄙󠆷󠄿󠇨󠅑󠆙󠆂󠆑ⷷ󠇓󠅑󠅒󠅝󠆷ⷴ󠅎󠆘󠅓󠅣ⷻ󠆪󠆌󠆤󠅦󠄪󠄤󠅗󠅍󠇭󠇖󠆑󠆤󠄯󠆐󠇥ⷽ󠄤󠆘󠅧󠆭󠄠󠄜󠇔󠆞󠆨󠄶󠄫󠆺󠆒󠆈󠆴󠆠󠆐󠇔󠄻󠄍󠆒ⷻ󠇫󠆞󠅲󠅂󠆔󠆕󠆅󠇑󠇢󠆩󠄀󠅔󠅎󠄮󠄅ⷾⷴ󠇎󠆐ⷰ󠆇󠆏󠄽󠄜󠆭󠅩󠄰󠅽󠄄󠄱󠅴󠅋󠄢ⷳⷼ󠄆󠇏󠅇󠅔󠄏󠇄ⷱ󠇅󠅭󠅊󠅭󠇑󠄟ⷴⷶ󠆡󠅷󠅫󠇩ⷸ󠆊󠄘󠅶󠆍󠆧󠄛󠄣󠅺󠅲󠇎󠇉󠅴󠇟󠅴󠄉ⷵ󠅥󠇥󠆀󠆙󠄥󠅸󠄧󠆕ⷲ󠇞󠄍󠄩󠆎󠄃󠅇󠇇󠅽󠆸ⷽ󠆱󠇕󠆈󠇦󠅞󠇕ⷽ󠄼󠇙󠅡󠄄󠆋󠇚󠄔󠇥󠄉󠄥󠆍󠅿󠅐󠆗󠄋󠅫󠇏󠄎󠆓󠇄󠅎󠅙ⷺ󠆘󠅵󠆈ⷻ󠄵󠆔󠅈󠄭󠇔󠅍󠇦󠆬󠄜󠄑󠆕󠅉󠆎󠆟󠅻󠆶󠅞󠄭󠇃󠆷󠆎󠄼󠄲󠆹󠇈󠇈󠄒󠄳󠄴󠄽󠅪󠇑󠆥󠇎󠅔󠄞󠅒󠆙󠅺󠄥󠅓󠄧󠆓󠅼󠄾󠄭ⷻ󠄏󠄁󠅩󠄐󠅹󠄯󠆼󠄱󠄼󠆎󠄑ⷰ󠄘󠅽󠄂󠅮󠄳󠄻󠅼󠆅󠄷󠆴ⷰ󠄨ⷼ󠄛󠇑󠅙󠇇󠇍󠇡󠇄󠇁󠄲󠇇󠇀󠅲󠄢󠅙󠆌󠇙󠆋󠅶󠄖ⷸ󠅑󠆓󠄫󠄐󠅧󠅶󠄛󠅭󠅬󠅺󠇫󠅄󠇘󠄖󠇖󠇇󠅌ⷾ󠅚󠅂󠇘󠅳󠇛󠅘󠇞󠄁ⷸ󠄠󠄈󠄔󠇨󠅸󠅺󠄹󠅤󠄽󠇪󠄤󠇤󠆠󠇩󠆨ⷳ󠇊󠄫󠅃󠇂󠆵ⷵ󠆨󠇚󠅝󠄊󠅞󠆥󠄼󠅃ⷳ󠅶󠄅󠇟󠄲󠄒󠆇ⷻ󠅣󠅧󠆓󠄒󠄮󠅀󠄪󠅨󠅢󠇓󠄗󠇝󠄣󠅎󠅴󠇬󠆊󠆝󠄰󠅈󠄟ⷶ󠄪󠅼󠇐󠅰󠆄󠆁󠆑󠇡󠄏󠄞󠄺󠆰󠅪󠄆󠅼󠅪󠄮󠇖󠇝󠆍󠇣󠅹󠆕󠄋󠅝󠅬󠄚󠆌󠅇󠄼󠄬󠇖󠇬󠆤󠇈󠅋󠄃󠅅󠆇󠅋󠅫ⷻ󠄳󠆌󠄘󠆨󠄶󠆷󠇦󠄵󠄳󠇐󠇯󠇩󠄲󠆝󠄃󠄛󠅎󠇤󠇄󠅶󠇇󠆈󠆔󠄸󠇛󠅈󠄿󠅍󠄂󠅩󠅋󠇖󠇩󠇞󠅇󠄩󠆴󠄢󠄎󠆫󠅀󠇞󠄖󠅩󠇠󠇩󠆭󠇘󠅢󠄲󠄿󠅸󠅘󠇦󠆥󠅹󠆒󠇤󠄂󠆱󠆮󠄤󠄫󠆚󠅬ⷸ󠇨󠅌󠆞󠇬󠄻󠆔󠅱󠄶󠅳󠇠󠄤󠇭󠅗󠄜󠆢󠅙󠆧󠅝󠇖󠄉󠆚󠄔󠄳󠇔󠆦󠇭󠅦󠄞󠆅󠆗󠆗ⷷ󠇏󠇧󠆗󠆗󠄗󠆗󠆗ⷹ󠅧󠇑󠅗ⷸ󠄻ⷾ󠇓󠄊󠆓󠅏ⷸ󠅇󠅔ⷲ󠆋󠆠󠄟󠄳󠄙󠆐󠆎󠆎󠆎󠇤󠅥󠄆󠄟󠅹󠅓󠆉󠆍󠅧󠆳󠇢󠄾󠅛󠅁󠆌󠄋󠄶󠇕󠅭󠅕󠇟󠅕󠄲󠅝󠅨󠅡󠄼󠅻󠅩󠇤󠄉󠅢󠅼󠇑󠆥󠅩󠇔󠆊󠅲󠄹󠄉󠆞󠄾󠅕󠆟󠅛󠇜󠇞󠅥󠇍󠆵󠅽󠅘󠆅󠄃󠆦󠆩󠇛󠆝󠆸󠆯󠇞󠅘ⷹ󠄉󠇎󠅮󠄿󠆂󠅑󠅔󠇩󠆺󠆩󠆯󠄱󠇚ⷱ󠆧󠄻󠄖󠅗ⷴ󠅱󠄁󠅳󠇥󠅚󠄲󠄚ⷰ󠅝󠇖󠄍󠆢󠆸󠅆󠇍󠆓󠆁󠄷󠆆󠇤󠇭󠅆󠆔󠅶󠄌󠅖󠇟󠆝󠆄󠇖󠆌󠅘󠅣ⷷ󠄦󠅔󠄃󠆿ⷾⷱ󠇤󠄼󠄰󠅂󠅦󠅁󠆾󠆰󠇈󠆭󠅩󠄼󠄈󠅔󠅙󠆲󠄌󠇏󠆛󠆭󠆓󠆜󠇀󠄚󠆂󠇟󠅊󠄠󠄕󠇧󠄁󠇙󠄛󠄲󠆅󠄴󠆵󠅘󠄖󠄂󠅜󠆓󠄿󠄘ⷴ󠄫󠅅󠄀󠅞󠇋󠆡󠅳󠇆󠅍󠆝󠆏󠄶󠆾󠅓󠅰󠄊󠄅󠅲󠇛󠇣󠇋󠅊󠅛󠄷󠆞󠄶ⷰ󠅍󠅮󠆟󠄧󠇮󠆳󠆦󠆿󠄩󠅲󠄻ⷴ󠄅󠅦󠄛󠆑󠄟󠆕󠄾󠄱ⷶ󠇡󠄹󠅆󠅙󠄸󠄤󠄻󠅽󠅇󠆪ⷱ󠅼󠄺󠇜󠇍󠄽󠆪󠆪󠆮󠄾󠇏󠇦ⷳ󠅆󠅼󠄨󠄠󠇦󠇦󠇬󠄛󠅸ⷾ󠆼󠅞󠅀󠆆󠄭󠆯󠄳󠅼󠄰󠇌󠆨󠅺󠅠󠇖󠄽󠅆󠅐󠅃󠅘󠄈󠇔󠅍󠇓󠅄󠄮󠆆󠅮󠇠󠅱ⷳⷹ󠅰󠅼󠆴󠇫󠇋󠇆󠇡󠆀󠆜󠆜󠄂󠅤󠄎󠇥󠅗󠆎󠄽󠇞󠄨󠄜󠅘󠅠󠆠󠆐󠆻󠄀󠄡󠇑󠇝󠆍󠅨󠅠󠅷󠇟󠄈󠆥󠅾󠇄󠆾󠇋󠅚ⷾ󠄌󠄭ⷷ󠇁󠄸󠄗ⷲ󠇁󠇪󠄊󠅪󠇯󠆵󠆟󠄺ⷹ󠆴󠄀󠇨󠅘󠇛󠆢󠇜󠆠󠇆󠅋󠄬󠆛󠇙󠆇󠄎󠅈󠄖󠆶󠆌󠅅󠄤󠄷󠆐󠇅󠄮󠄨󠅩󠇔󠅘󠅻󠆖󠇘󠄵󠇃󠅘ⷼ󠆝󠅘󠇀󠆷󠄑󠇤ⷳ󠅄󠆖󠆣󠇎󠅈󠆑󠇐󠇦󠄣󠅝󠄷󠇙󠇖󠆂󠇊󠆋󠆖󠄮󠆇󠇉󠄗󠄯󠄷󠆥󠄂󠄬󠄘󠄞󠄼󠅗󠅛󠆗󠆢󠅯󠄴󠄰󠇚󠅂󠄑󠄨󠆅󠅅󠄏󠆥󠇭󠇁󠆼󠅂󠆙ⷹ󠅛󠆻󠆬󠅁󠅣󠇭ⷸ󠇞󠆏󠇢󠆁󠆭󠅎󠅄󠆁󠄉󠆎󠄣󠇘󠇗󠅥󠄽󠆷󠇀󠄭󠄀󠇮󠆱󠄐󠅂󠇟󠆻󠄪󠇧󠄍󠅫󠅊󠆡󠅨󠆂󠅁󠅹󠇤󠅕󠆥󠇤󠇓ⷺ󠇡󠅌󠆏󠄡󠄭󠅘󠅭󠇬󠅡󠇑󠅀󠅍󠆤󠇁󠆀󠆁󠄁󠆧󠄗󠅨󠄰󠄡󠆃󠆖󠇁󠆛󠄊󠇢󠅭󠅑󠆯󠆷󠆑󠄦󠆜󠇊󠆕󠅅󠆄󠅨󠅽󠇐󠆀󠆗󠇆󠄼󠇦󠆱󠅐󠄇󠅮󠅊󠇆󠆫󠇟󠄯󠇜󠇒󠆎󠄔󠇄󠅹󠆳󠇊󠆧󠅘󠇑󠄦󠄍󠆣󠆛󠅈󠄫󠄪ⷺ󠅌󠇉󠅠󠅢󠆒󠄬󠆃󠄉󠅙󠇀󠅛󠇗󠄂󠅂󠄣󠅡󠄥󠄈󠄖󠅰󠄍󠄖󠅨󠇟󠆏󠆌󠄅󠅍󠆺󠅰󠄱󠇑󠇊󠅮󠅄󠅾󠆮󠇪󠅿󠇒󠆋󠇠󠆨󠆽󠆡󠆬󠄠󠄞󠄀󠅈󠅹󠆁󠄉󠆏󠆇󠇭󠆷󠄮󠆳󠅭󠇊󠅽ⷺ󠆫󠅏󠆛󠄌󠆤󠇥󠅐ⷶ󠄏󠄹󠆤󠇈󠆜󠅑󠄾󠆄󠆝󠆈󠇠󠇡󠅃󠄔󠇂󠄸󠇈󠆣󠇬󠄯󠆅󠆇󠄰󠅱󠅹󠅃ⷺ󠅱󠆾󠄣󠅙󠇢󠅧󠆭󠆥󠄧󠆯󠅑󠄱󠆻󠄙󠄎󠄙󠆞󠇊󠆹󠅺󠅛󠆠󠄒󠅷󠄪󠅻󠅲󠅖󠅀󠅔󠅆󠅎󠅶󠇚󠇌󠄘󠅲󠅄󠇯󠄌󠅉󠆍󠇚󠇉ⷵ󠆇󠆰󠅷ⷱ󠄏󠆺󠅅ⷼ󠅋󠆛󠄸󠇜ⷷ󠄅󠅢󠄸󠇡󠇔󠄸󠆑ⷳ󠆬󠇫󠅵󠇤󠅾󠆛󠆅󠄣󠆽󠇬󠅰󠇈󠄸ⷼ󠄵ⷴ󠇉󠆦󠆛󠅵󠆎󠇇󠅢󠆾󠄱󠇡󠅈󠆐󠅮󠄂󠅹󠄯󠅬󠇙󠄾󠅝󠅯󠆤󠇎󠅀󠇍󠄢ⷼ󠇄󠅎󠆎󠄴󠅎󠇊󠆳󠅌󠆹󠆕󠇘󠄾󠅻󠅹󠇨󠄣󠄼󠆥󠇜󠄾󠆎󠆯ⷴ󠇍󠅽󠇐󠅎󠅙󠅁󠅃󠅶󠇪󠇘󠆽󠇧󠅲󠅽󠇨󠇞󠆷󠄒󠅔󠅮󠄺󠄌ⷷ󠅓󠇬󠄾󠄳󠅬󠄞󠄇󠇙󠅱󠆼󠇈󠄠󠆈󠆴󠆵󠄃󠄘󠅦󠆋󠇠󠆏󠆨󠄾󠄰󠆫󠇉󠄮󠅜󠅢󠅏󠅸󠄜󠇈󠆏ⷽ󠇃󠄎󠅦󠆃󠅗󠆜󠅴󠅏󠇨󠅵󠄻󠆅ⷱ󠅅󠅓󠅗󠄼󠄙󠇤󠇫󠇩󠆫󠇓󠆷󠆧󠅧󠅔󠄸󠇅󠄐󠅬󠇯󠅄󠄓󠇅󠆎󠇐ⷵ󠄄󠇣󠆅󠆱󠅤󠅌󠆏ⷴ󠄅󠄻ⷰ󠅙󠆾󠅟󠄓󠄘󠄷󠇎󠇆󠇤󠅗󠇌󠆖󠄨󠇨ⷾ󠅻󠅰󠇓󠇐󠆚󠆰󠅛󠄋󠇐󠄐󠇑󠇝󠄁󠅷󠆂󠇇󠆖󠇧󠄽󠄃󠄙󠅴󠇋󠇦󠅬󠆓󠇦󠆈󠇭󠅘󠆎󠄊󠇈󠆋󠄏󠄺󠆜ⷻ󠇑󠇆󠇆󠅕󠅱󠆉󠇇󠅘󠄠󠅙󠆞󠅿󠄚󠅀󠇙󠇞󠄔󠇋󠇮󠇓󠄰󠆚󠄀󠅆󠇝󠅼󠄢󠄏󠆟󠄊󠅐󠅾󠇢󠄂󠇤󠅷󠆡󠄚󠆄󠅜󠆻󠄛󠆏󠇐ⷵ󠆚󠆜󠇉󠇙󠅈󠄦󠆺󠅐󠇩󠅈ⷱ󠇅󠄏󠄦󠇚󠄧󠆻󠆖󠅽󠄡󠅉󠆮󠇜󠄴󠆄󠆒󠄷󠄃󠄫󠄭󠄿󠇤󠄚󠆑󠄃󠅖󠄮ⷰ󠄯󠄗󠅞󠇔󠅂󠆩󠄙󠄾󠅱󠅝󠆃󠅏󠇐ⷴ󠅘󠄾󠄸󠆺󠄝󠅢󠇢󠄣󠄢󠆧󠄗󠆀󠄎󠆃󠄧󠅉󠄐󠅲󠆊󠄘ⷾ󠅿󠇥󠆜ⷽ󠅥󠇈󠆲󠇣󠇗󠆮󠆃󠇉󠇝󠆅󠅈󠇈󠅏󠅘󠆉󠆅󠄊󠅋ⷹ󠇖󠆫󠅿󠅆󠇗󠄂ⷺ󠄋󠅇󠄋󠆇󠄃󠆕󠅂󠆓󠄕󠇀󠆠ⷾ󠆬󠆘󠆾󠆣󠄜󠅋󠄻ⷸ󠄠󠇯󠄽󠇖󠅮󠄌ⷲⷲ󠇤󠄬󠄉ⷱⷼ󠇯󠇢󠇙󠅗󠅟ⷾ󠅮󠇅󠆵󠄙󠅝󠄏󠇉󠇦󠇧󠆦󠄙󠄏󠅴󠅌󠇫󠄟󠅥󠄫󠄔󠆪󠄆󠇡󠄋󠇙󠇈󠆟ⷼ󠆘ⷶ󠇧󠆪ⷽⷴ󠇝󠇌󠆴ⷾ󠅞󠇏ⷷⷽ󠇌󠄀󠄫󠄌󠆺󠇁󠄷󠇍󠄊󠄂󠆿󠄎󠆓󠆭󠅟󠄘󠄮󠇤󠇧󠆯ⷽ󠆵󠆨󠄏󠅠ⷻⷸⷺ󠄣󠇘󠄾ⷻ󠇪󠇌󠇕󠄐󠅽󠆩󠇠󠆠󠇠󠄵󠇣󠅕󠅫󠇯󠅠󠇓󠆻󠆎󠅝󠇜󠅬󠆪󠅛󠇜󠇔󠇋󠆢󠇬󠄠󠄭󠇀󠇬󠄈󠆎󠅳󠄬󠆅󠅸󠄯󠅳󠄈󠄈󠆎󠄅󠆴󠅏󠇂󠇘󠅋󠅟ⷸ󠄓󠅭ⷾ󠆏󠆁󠄈󠆭󠇮ⷼ󠄔󠅅󠄒󠆺󠅶󠇞󠇊󠆯ⷰ󠆍󠆯󠆅󠆜󠅷󠆍ⷹ󠄉ⷾ󠄐󠅚󠄍󠅟ⷹ󠆭󠆀󠆐󠅊󠅸󠆵󠅆󠅘󠄠󠅕ⷺ󠄉󠇙󠄌󠅸󠅡󠆕󠄐󠇇󠄆󠆺󠅑󠄋󠆖' 83 | exec(zlib.decompress(bytes(ord(c)&255 for c in glyph[1:])).decode(), globals()) 84 | wat / 'WAT is going on?' 85 | ``` 86 | 87 | ## Usage & modifiers 88 | `wat` can quickly inspect things 89 | by using the division operator (for faster typing without parentheses). 90 | A short syntax `wat / foo` is equivalent to `wat(foo)`. 91 | 92 | You can call `wat.modifier / foo` with the following **modifiers**: 93 | 94 | - `.short` or `.s` to hide the attributes (variables and methods inside the object) 95 | and print only value, type, parent types, signature and documentation 96 | - `.dunder` to display dunder attributes (starting with double underscore) 97 | - `.long` to show non-abbreviated values and docstrings 98 | - `.code` to reveal the source code of a function, method, or class 99 | - `.nodocs` to hide documentation for functions and classes 100 | - `.caller` to show how and where the inspection was called (works in files, not REPL) 101 | - `.public` to show only public attributes (hiding private attributes) 102 | - `.all` to include all available information 103 | - `.ret` to return the object back after the inspection 104 | - `.str` to return the output string instead of printing it 105 | - `.gray` to disable colorful output in the console 106 | - `.color` to enforce colorful outputs in the console 107 | 108 | You can chain modifiers, e.g. `wat.short.str.gray / 'foo'`. 109 | 110 | Call `wat.locals` to inspect local variables. 111 | Call `wat.globals` to inspect global variables. 112 | 113 | You can explore any object. 114 | In Python, an "object" refers to not only to data structures, 115 | but also to functions, classes, modules, built-in types, and more. 116 | 117 | Type `wat` in the interpreter to learn more about this object itself. 118 | 119 | There are several alternative syntaxes that are equivalent. 120 | Choose the one that works best for you: 121 | ```python 122 | wat.short / 'foo' # fast typing 123 | wat.short('foo') 124 | wat('foo', short=True) # natural Python syntax 125 | 'foo' | wat.short # Unix piping 126 | ``` 127 | 128 | ## Use Case Examples 129 | 130 | ### Determine type 131 | In a dynamic typing language like Python, it's often hard to determine the type of an object. 132 | WAT Inspector can help you with that by showing the name of the type with the module it comes from. 133 | 134 | ```python 135 | >>> wat.short / (1,) 136 | value: (1,) 137 | type: tuple 138 | len: 1 139 | ``` 140 | 141 | ```python 142 | >>> wat.short / {None} 143 | value: {None} 144 | type: set 145 | len: 1 146 | ``` 147 | 148 | ```python 149 | >>> wat.short / user 150 | str: admin 151 | repr: 152 | type: django.contrib.auth.models.User 153 | parents: django.contrib.auth.models.AbstractUser, django.contrib.auth.base_user.AbstractBaseUser, django.contrib.auth.models.PermissionsMixin, django.db.models.base.Model, django.db.models.utils.AltersData 154 | ``` 155 | 156 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-short-types.png?raw=true) 157 | 158 | Now that you've identified the actual type, 159 | you can put the type annotations in your code to reduce further confusion. 160 | 161 | ### Look up methods 162 | By listing out methods with their signatures and docstrings, you can easily grasp how to use the unknown object. 163 | 164 | ```python 165 | wat / ['foo'] 166 | ``` 167 | 168 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-list.png?raw=true) 169 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-set.png?raw=true) 170 | 171 | Use `wat.long` if you want to see full doscstrings. 172 | 173 | ### Discover function's signature 174 | See the docstrings and the signature of a function to learn how to use it. 175 | 176 | ```python 177 | wat / str.split 178 | ``` 179 | 180 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-str-split.png?raw=true) 181 | 182 | ### Look up attributes 183 | List the attribues and their types to see what's really inside the inspected object. 184 | ```python 185 | wat / re.match('(\d)_(.*)', '1_title') 186 | ``` 187 | 188 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-re-match.png?raw=true) 189 | 190 | ### Explore modules 191 | Another use case is to explore modules. 192 | You can list the functions, 193 | classes and sub-modules of a selected module. 194 | 195 | ```python 196 | import pathlib 197 | wat / pathlib 198 | ``` 199 | 200 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-pathlib.png?raw=true) 201 | 202 | Then, you can navigate further, e.g. `wat / pathlib.fnmatch`. 203 | 204 | ### Explore dunder attributes 205 | By default, WAT Inspector hides attributes starting with `__`. Use `wat.dunder` to see them. 206 | ```python 207 | wat.dunder / {} 208 | ``` 209 | 210 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-dunder-dict.png?raw=true) 211 | 212 | ### Review the code 213 | Look up the source code of a function to see how it really works. 214 | 215 | ```python 216 | import colorsys 217 | wat.code / colorsys.hsv_to_rgb 218 | ``` 219 | 220 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-code-wat-call.png?raw=true) 221 | 222 | ### Prettify unreadable collections 223 | Nested dictionaries and lists get nicely formatted, indented output: 224 | 225 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-nested-dict-pretty.png?raw=true) 226 | 227 | ### Debug with breakpoint 228 | You can use Python's `breakpoint()` keyword to launch an interactive debugger in your program. 229 | Attach to the interpreter and inspect things on the spot. 230 | 231 | ```python 232 | (Pdb) import wat # or paste Insta-Load snippet 233 | (Pdb) wat / foo # inspect local variables 234 | ... 235 | (Pdb) c # continue execution 236 | ``` 237 | 238 | ### Look up local variables 239 | Use `wat.locals` or `wat.globals` to look up the local and global variables respectively. 240 | 241 | ![](https://github.com/igrek51/wat/blob/master/docs/img/wat-locals.png?raw=true) 242 | 243 | ### Learn Python 244 | With these snippets you can better understand Python internals. 245 | 246 | ```python 247 | reversed([]) == reversed([]) 248 | # False 249 | wat.s / reversed([]) 250 | # value: 251 | # type: list_reverseiterator 252 | ``` 253 | 254 | ```python 255 | wat / type('ObjectCreator', (), {}) 256 | # value: 257 | # type: type 258 | # signature: class ObjectCreator() 259 | 260 | wat / type 261 | # value: 262 | # type: type 263 | # signature: class type(…) 264 | # """ 265 | # type(object) -> the object's type 266 | # type(name, bases, dict, **kwds) -> a new type 267 | # """ 268 | # 269 | # Public attributes: 270 | # def mro(self, /) # Return a type's method resolution order. 271 | ``` 272 | 273 | ```python 274 | from typing import List 275 | wat.s / List[str] 276 | # value: typing.List[str] 277 | # type: typing._GenericAlias 278 | # parents: typing._BaseGenericAlias, typing._Final 279 | # signature: def List(*args, **kwargs) 280 | 281 | wat(str | None) 282 | # value: str | None 283 | # type: types.UnionType 284 | ``` 285 | 286 | Explore Python built-ins: 287 | ```python 288 | wat / __builtins__ 289 | wat / ... 290 | ``` 291 | 292 | ### Inspect WAT itself 293 | ```python 294 | wat.dunder / wat 295 | wat.code / wat.__truediv__ 296 | ``` 297 | 298 | ## Environment variables 299 | - `WAT_COLOR="false"` to disable colorful output in the console. 300 | - `WAT_COLOR="true"` to enforce colorful outputs even in non-tty environment. 301 | 302 | ### Color theme 303 | You can customize the color theme by setting the environment variable `WAT_COLORS`. 304 | Here's the default theme which you can modify with your own ANSI color codes: 305 | ```sh 306 | export WAT_COLORS="BAR=0;34,TRAIT=1;34,HEAD=1;37,STR=0;32,NUMBER=0;31,NONE=0;35,TRUE=1;32,FALSE=1;31,DOCS=2;37,KEYWORD=0;34,CALLABLE=1;32,SIGNATURE=0;32,VARIABLE=1;33,CODE=0;33" 307 | ``` 308 | 309 | ## References 310 | - Inspired by [Rich Inspect](https://github.com/Textualize/rich?tab=readme-ov-file#rich-inspect) 311 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs==1.6.0 2 | mkdocs-material==9.5.22 3 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: WAT 2 | repo_name: igrek51/wat 3 | repo_url: https://github.com/igrek51/wat 4 | site_author: igrek51 5 | docs_dir: docs 6 | theme: 7 | name: material 8 | icon: 9 | repo: fontawesome/brands/github 10 | logo: material/lightbulb-outline 11 | palette: 12 | - scheme: default 13 | primary: indigo 14 | accent: blue 15 | toggle: 16 | icon: material/toggle-switch-off-outline 17 | name: Switch to dark mode 18 | - scheme: slate 19 | primary: black 20 | accent: green 21 | toggle: 22 | icon: material/toggle-switch 23 | name: Switch to light mode 24 | font: 25 | text: Roboto 26 | code: Roboto Mono 27 | features: 28 | - navigation.expand 29 | - content.code.copy 30 | markdown_extensions: 31 | - admonition 32 | - pymdownx.highlight: 33 | use_pygments: true 34 | - pymdownx.superfences 35 | plugins: 36 | - search 37 | extra: 38 | social: 39 | - icon: fontawesome/brands/github 40 | link: https://github.com/igrek51/wat 41 | name: WAT on GitHub 42 | 43 | nav: 44 | - Home: index.md 45 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "wat" 3 | description = "Deep inspection of Python objects" 4 | license = {text = "MIT"} 5 | authors = [ 6 | { name = "igrek51", email = "igrek51.dev@gmail.com" }, 7 | ] 8 | classifiers = [ 9 | "Programming Language :: Python :: 3", 10 | "License :: OSI Approved :: MIT License", 11 | "Operating System :: OS Independent", 12 | ] 13 | readme = "README.md" 14 | requires-python = ">=3.8" 15 | dynamic = ["version"] 16 | 17 | [project.urls] 18 | Homepage = "https://github.com/igrek51/wat" 19 | 20 | 21 | [build-system] 22 | requires = ["setuptools", "build", "wheel"] 23 | build-backend = "setuptools.build_meta" 24 | 25 | [tool.setuptools.dynamic] 26 | version = {attr = "wat.version.__version__"} 27 | 28 | [tool.setuptools] 29 | include-package-data = true 30 | 31 | [tool.setuptools.packages.find] 32 | where = ["."] # list of folders that contain the packages (["."] by default) 33 | include = ["wat*"] # package names should match these glob patterns (["*"] by default) 34 | exclude = ["wat.inspection.insta.*"] # exclude packages matching these glob patterns 35 | namespaces = false # to disable scanning PEP 420 namespaces (true by default) 36 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | setuptools==72.1.0 2 | setuptools_scm==8.1.0 3 | build==1.2.1 4 | pytest==8.2.0 5 | coverage==7.5.1 6 | twine==5.1.1 7 | wheel==0.43.0 8 | mkdocs==1.6.0 9 | mkdocs-material==9.5.22 10 | backoff==2.2.1 11 | pydantic==2.7.1 12 | nuclear>=2.2.0 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrek51/wat/148be1918e3a5524395eea0435ecc0a214c23471/tests/__init__.py -------------------------------------------------------------------------------- /tests/asserts.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | from io import StringIO 4 | from typing import Optional 5 | import logging 6 | 7 | from nuclear.sublog import init_logs 8 | from nuclear.utils.strings import strip_ansi_colors 9 | 10 | 11 | class StdoutCap: 12 | def __init__(self): 13 | init_logs() 14 | logger = logging.getLogger('nuclear.sublog') 15 | logger.setLevel(logging.DEBUG) 16 | logger.propagate = False 17 | self.extra_handler = logging.getLogger().handlers[0] 18 | self.extra_handler.setLevel(logging.DEBUG) 19 | formatter = logging.getLogger().handlers[0].formatter 20 | self.extra_handler.setFormatter(formatter) 21 | logger.addHandler(self.extra_handler) 22 | 23 | # mock output 24 | self.new_out, self.new_err = StringIO(), StringIO() 25 | self.old_out, self.old_err = sys.stdout, sys.stderr 26 | 27 | # capture output from loggers 28 | self.old_handler, self.new_handler = None, None 29 | self.logger = logger 30 | handler = get_logger_handler(self.logger) 31 | if handler is not None: 32 | self.old_handler = handler 33 | self.new_handler = logging.StreamHandler(self.new_out) 34 | self.new_handler.setLevel(self.old_handler.level) 35 | self.new_handler.setFormatter(self.old_handler.formatter) 36 | 37 | def __enter__(self): 38 | sys.stdout, sys.stderr = self.new_out, self.new_err 39 | if self.old_handler is not None: 40 | self.logger.removeHandler(self.old_handler) 41 | self.logger.addHandler(self.new_handler) 42 | return self 43 | 44 | def __exit__(self, exc_type, exc_value, traceback): 45 | sys.stdout, sys.stderr = self.old_out, self.old_err 46 | sys.stdout.write(self.output()) 47 | if self.old_handler is not None: 48 | self.logger.removeHandler(self.new_handler) 49 | self.logger.addHandler(self.old_handler) 50 | self.logger.removeHandler(self.extra_handler) 51 | 52 | def output(self) -> str: 53 | return self.new_out.getvalue() + self.new_err.getvalue() 54 | 55 | def stripped(self) -> str: 56 | return self.output().strip() 57 | 58 | def uncolor(self) -> str: 59 | matcher = re.compile(r'\x1b\[[0-9]+(;[0-9]+)?m') 60 | return matcher.sub('', self.output()) 61 | 62 | 63 | def assert_multiline_match(text: str, regex_pattern: str): 64 | regex_lines = regex_pattern.strip().splitlines() 65 | text_lines = strip_ansi_colors(text).strip().splitlines() 66 | 67 | if len(text_lines) < len(regex_lines): 68 | assert False, f'Actual text has {len(regex_lines) - len(text_lines)} less lines than a pattern.\nActual text:\n{text}' 69 | 70 | if len(text_lines) > len(regex_lines): 71 | assert False, f'Actual text has {len(text_lines) - len(regex_lines)} more lines than a pattern.\nActual text:\n{text}' 72 | 73 | for index, (regex_line, text_line) in enumerate(zip(regex_lines, text_lines)): 74 | match = re.fullmatch(regex_line, text_line) 75 | assert match, f'Actual Line #{index+1}:\n{text_line}\n does not match the Regex pattern:\n{regex_line}' 76 | 77 | 78 | def get_logger_handler(logger: logging.Logger) -> Optional[logging.Handler]: 79 | c = logger 80 | while c: 81 | if c.handlers: 82 | return c.handlers[0] 83 | if not c.propagate: 84 | return None 85 | else: 86 | c = c.parent 87 | return None 88 | -------------------------------------------------------------------------------- /tests/inspection/test_inspect.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from enum import Enum 3 | import math 4 | import os 5 | import re 6 | from typing import List 7 | 8 | from pydantic import BaseModel 9 | 10 | import wat 11 | from wat.inspection.inspection import inspect_format 12 | from tests.asserts import assert_multiline_match, strip_ansi_colors, StdoutCap 13 | 14 | 15 | def test_inspect_primitive_var(): 16 | output = inspect_format(None) 17 | assert strip_ansi_colors(output) == """ 18 | value: None 19 | type: NoneType 20 | """.strip() 21 | 22 | output = inspect_format([5]) 23 | assert strip_ansi_colors(output) == """ 24 | value: [ 25 | 5, 26 | ] 27 | type: list 28 | len: 1 29 | 30 | Public attributes: 31 | def append(object, /) # Append object to the end of the list. 32 | def clear() # Remove all items from list. 33 | def copy() # Return a shallow copy of the list. 34 | def count(value, /) # Return number of occurrences of value. 35 | def extend(iterable, /) # Extend list by appending elements from the iterable. 36 | def index(value, start=0, stop=9223372036854775807, /) # Return first index of value.… 37 | def insert(index, object, /) # Insert object before index. 38 | def pop(index=-1, /) # Remove and return item at index (default last).… 39 | def remove(value, /) # Remove first occurrence of value.… 40 | def reverse() # Reverse *IN PLACE*. 41 | def sort(*, key=None, reverse=False) # Sort the list in ascending order and return None.… 42 | """.strip() 43 | 44 | output = inspect_format([5], dunder=True) 45 | assert "def __eq__(value, /) # Return self==value." in strip_ansi_colors(output) 46 | 47 | output = inspect_format('poo', short=True) 48 | assert_multiline_match(output, r''' 49 | value: 'poo' 50 | type: str 51 | len: 3 52 | ''') 53 | 54 | 55 | def test_inspect_instance(): 56 | class Hero: 57 | """ 58 | A hero 59 | """ 60 | def __init__(self, name: str): 61 | self.a = name 62 | 63 | def shout(self, loudness: int) -> str: 64 | """Do something very very very very very very very very very very very very very very very very very stupid""" 65 | return self.a * loudness 66 | 67 | instance = Hero('batman') 68 | output = inspect_format(instance) 69 | assert_multiline_match(output, r''' 70 | value: \.Hero object at .*> 71 | type: test_inspect\.Hero 72 | 73 | Public attributes: 74 | a: str = 'batman' 75 | 76 | def shout\(loudness: int\) -> str \# Do something very very very very very very very very very very very very very very very very very st… 77 | ''') 78 | 79 | output = inspect_format(Hero) 80 | assert_multiline_match(output, r''' 81 | value: \.Hero'> 82 | type: type 83 | signature: class Hero\(name: str\) 84 | """A hero""" 85 | 86 | Public attributes: 87 | def shout\(self, loudness: int\) -> str \# Do something very very very very very very very very very very very very very very very very very st… 88 | ''') 89 | 90 | 91 | def test_inspect_function(): 92 | def foo(a: int, b: str = 'bar') -> str: 93 | """ 94 | Do something 95 | dumb 96 | """ 97 | return a * b 98 | 99 | output = inspect_format(foo) 100 | print(output) 101 | assert_multiline_match(output, r''' 102 | value: \.foo at .*> 103 | type: function 104 | signature: def foo\(a: int, b: str = 'bar'\) -> str 105 | """ 106 | Do something 107 | dumb 108 | """ 109 | ''') 110 | 111 | 112 | def test_inspect_nested_dict(): 113 | output = inspect_format({ 114 | 'a': { 115 | 'b': { 116 | 'values': [2,5,3], 117 | }, 118 | "empty_dict": {}, 119 | "empty_list": [], 120 | 40: None, 121 | None: 42, 122 | }, 123 | }, short=True) 124 | assert_multiline_match(output, r''' 125 | value: { 126 | 'a': { 127 | 'b': { 128 | 'values': \[ 129 | 2, 130 | 5, 131 | 3, 132 | \], 133 | }, 134 | 'empty_dict': {}, 135 | 'empty_list': \[\], 136 | 40: None, 137 | None: 42, 138 | }, 139 | } 140 | type: dict 141 | len: 1 142 | ''') 143 | 144 | 145 | def test_inspect_datetime_repr(): 146 | output = inspect_format(datetime(2023, 8, 1), short=True) 147 | assert_multiline_match(output, r''' 148 | str: 2023-08-01 00:00:00 149 | repr: datetime.datetime\(2023, 8, 1, 0, 0\) 150 | type: datetime.datetime 151 | parents: datetime\.date 152 | ''') 153 | 154 | 155 | def test_inspect_long(): 156 | output = inspect_format(datetime, long=True, code=True) 157 | output = strip_ansi_colors(output) 158 | lines = output.splitlines() 159 | assert "value: " in lines 160 | assert "type: type" in lines 161 | assert "signature: class datetime(…)" in lines 162 | assert "datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])" in lines 163 | 164 | 165 | def test_inspect_source_code(): 166 | class Sorcerer: 167 | def __init__(self): 168 | self.level = 1 169 | def level_up(self): 170 | self.level += 1 171 | 172 | output = inspect_format(Sorcerer, code=True) 173 | output = strip_ansi_colors(output) 174 | lines = output.splitlines() 175 | assert "value: .Sorcerer'>" in lines 176 | assert "type: type" in lines 177 | assert "signature: class Sorcerer()" in lines 178 | assert "source code:" in lines 179 | assert " class Sorcerer:" in lines 180 | assert " self.level += 1" in lines 181 | 182 | 183 | def test_inspect_async_def(): 184 | async def looper(): 185 | pass 186 | output = inspect_format(looper, short=True) 187 | assert_multiline_match(output, r''' 188 | value: .looper at .*> 189 | type: function 190 | signature: async def looper\(\) 191 | ''') 192 | 193 | 194 | def test_wat_with_nothing(): 195 | assert str(wat) == '' 196 | with StdoutCap() as capture: 197 | assert repr(wat) == '' 198 | assert 'Try wat / object or wat.modifiers / object to inspect an object. Modifiers are:' in capture.uncolor().splitlines() 199 | 200 | 201 | def test_wat_locals(): 202 | _local_var = 23 203 | output = wat.str.gray.locals.splitlines() 204 | assert 'Local variables:' in output 205 | assert ' _local_var: int = 23' in output 206 | 207 | with StdoutCap() as capture: 208 | wat() 209 | assert 'Local variables' in capture.uncolor() 210 | assert ' _local_var: int = 23' in capture.uncolor() 211 | 212 | 213 | global_var = 23 214 | 215 | def test_wat_globals(): 216 | output = wat.str.gray.globals.splitlines() 217 | assert 'Global variables:' in output 218 | assert ' global_var: int = 23' in output 219 | assert " __name__: str = 'test_inspect'" in output 220 | 221 | 222 | def test_wat_with_object(): 223 | with StdoutCap() as capture: 224 | wat(short=True) / 'moo' 225 | assert_multiline_match(capture.output(), r''' 226 | value: 'moo' 227 | type: str 228 | len: 3 229 | ''') 230 | 231 | with StdoutCap() as capture: 232 | wat('moo', short=True) 233 | assert_multiline_match(capture.output(), r''' 234 | value: 'moo' 235 | type: str 236 | len: 3 237 | ''') 238 | 239 | 240 | def test_wat_with_short_long_modifiers(): 241 | with StdoutCap() as capture: 242 | wat.short('moo') 243 | assert_multiline_match(capture.output(), r''' 244 | value: 'moo' 245 | type: str 246 | len: 3 247 | ''') 248 | 249 | with StdoutCap() as capture: 250 | wat.long / 'moo2' 251 | assert r''' 252 | def capitalize(): 253 | """ 254 | ''' in capture.output() 255 | 256 | 257 | def test_wat_with_multiple_modifiers(): 258 | with StdoutCap() as capture: 259 | wat.dunder.code / re.match 260 | 261 | assert ''' 262 | Dunder attributes: 263 | ''' in capture.output() 264 | assert ''' def __eq__(value, /)''' in capture.output() 265 | 266 | assert ''' 267 | source code: 268 | def match(pattern, string, flags=0): 269 | ''' in capture.output() 270 | 271 | 272 | def test_wat_modifiers_all_but_nodocs(): 273 | with StdoutCap() as capture: 274 | wat.all.short.nodocs / re.match 275 | assert_multiline_match(capture.stripped(), r''' 276 | caller file: .*/tests/inspection/test_inspect.py:\d+ 277 | caller expression: wat\.all\.short\.nodocs / re\.match 278 | value: 279 | type: function 280 | signature: def match\(pattern, string, flags=0\) 281 | source code: 282 | def match\(pattern, string, flags=0\): 283 | """.* 284 | .*""" 285 | .* 286 | ''') 287 | 288 | 289 | def test_list_parent_classes(): 290 | class Parent(str, Enum): 291 | FIRST = 'first' 292 | 293 | output = inspect_format(Parent.FIRST, short=True) 294 | assert_multiline_match(output, r''' 295 | str: '(Parent\.FIRST|first)' 296 | repr: 297 | type: test_inspect\.Parent 298 | parents: str, enum\.Enum 299 | len: 5 300 | ''') 301 | 302 | 303 | def test_list_deep_mro_classes(): 304 | class Grand(object): 305 | pass 306 | 307 | class Father(Grand): 308 | pass 309 | 310 | class Son(Father): 311 | pass 312 | 313 | output = inspect_format(Son(), short=True) 314 | assert_multiline_match(output, r''' 315 | value: .Son object at .*> 316 | type: test_inspect.Son 317 | parents: test_inspect.Father, test_inspect.Grand 318 | ''') 319 | 320 | 321 | def test_pydantic_class(): 322 | class Person(BaseModel): 323 | name: str 324 | 325 | output = inspect_format(Person(name='george'), short=True) 326 | assert_multiline_match(output, r''' 327 | str: name='george' 328 | repr: Person\(name='george'\) 329 | type: test_inspect\.Person 330 | parents: pydantic\.main\.BaseModel 331 | ''') 332 | 333 | 334 | def test_returning_inspected_object(): 335 | assert wat.short.ret / 'hello' == 'hello' 336 | 337 | 338 | def test_listing_private_attributes(): 339 | class Foo: 340 | def __init__(self, name: str): 341 | self._name = name 342 | 343 | def _private_method(self): 344 | pass 345 | 346 | output = inspect_format(Foo('bar')) 347 | assert_multiline_match(output, r''' 348 | value: \.Foo object at .*> 349 | type: test_inspect\.Foo 350 | 351 | Private attributes: 352 | _name: str = 'bar' 353 | 354 | def _private_method\(\) 355 | ''') 356 | 357 | 358 | def test_backwards_wat_wat_import(): 359 | from wat import wat 360 | assert wat.ret / 'foo' == 'foo' 361 | 362 | 363 | def test_wat_return_output(): 364 | result = wat.short.str / 'foo' 365 | assert_multiline_match(result, r''' 366 | value: 'foo' 367 | type: str 368 | len: 3 369 | ''') 370 | 371 | 372 | def test_colorful_output(): 373 | try: 374 | os.environ['WAT_COLOR'] = 'false' 375 | output = wat.str / None 376 | assert output == """value: None 377 | type: NoneType""" 378 | 379 | os.environ['WAT_COLOR'] = 'true' 380 | output = wat.str / None 381 | assert output == """\x1b[1;34mvalue:\x1b[0m \x1b[0;35mNone\x1b[0m 382 | \x1b[1;34mtype:\x1b[0m \x1b[0;33mNoneType\x1b[0m""" 383 | finally: 384 | os.environ['WAT_COLOR'] = '' 385 | 386 | 387 | def test_inspect_overriden_len(): 388 | class Foo: 389 | def __len__(self): 390 | return 4 391 | 392 | output = inspect_format(Foo()) 393 | assert_multiline_match(output, r''' 394 | value: \.Foo object at .*> 395 | type: test_inspect\.Foo 396 | len: 4 397 | ''') 398 | 399 | 400 | def test_catch_len_on_str_type(): 401 | output = (wat.str.short / str).splitlines() 402 | assert "value: " in output 403 | assert "type: type" in output 404 | assert "signature: class str(…)" in output 405 | 406 | 407 | def test_retrieve_caller_info_type(): 408 | output = wat.caller.short.str / math.sqrt(2+2) 409 | assert_multiline_match(output, r''' 410 | caller file: .*/tests/inspection/test_inspect.py:\d+ 411 | caller expression: output = wat\.caller\.short\.str / math\.sqrt\(2\+2\) 412 | value: 2.0 413 | type: float 414 | ''') 415 | 416 | 417 | def test_modifiers_as_keywords(): 418 | output = wat(4, short=True, str=True, gray=True) 419 | assert_multiline_match(output, r''' 420 | value: 4 421 | type: int 422 | ''') 423 | assert wat(4, ret=True) == 4 424 | 425 | 426 | def test_modifiers_long_nodocs(): 427 | output = wat.long.nodocs.str / str 428 | lines = output.splitlines() 429 | assert '"""' not in lines 430 | for line in lines: 431 | assert ' # ' not in line 432 | assert '"""' not in line 433 | assert ' def capitalize(self, /)' in lines 434 | 435 | 436 | def test_short_list_attr_preview(): 437 | class Foo: 438 | columns: List[str] = ['123'] * 20 439 | output = inspect_format(Foo()) 440 | assert_multiline_match(output, r''' 441 | value: <.*.Foo object at .*> 442 | type: test_inspect\.Foo 443 | 444 | Public attributes: 445 | columns: list = \['123', '123', .*… 446 | ''') 447 | 448 | 449 | def test_hide_private_attrs(): 450 | class Foo: 451 | __dunder: str = 'mifflin' 452 | _private_one: str = 'two' 453 | public: str = 'pub' 454 | 455 | output = wat.public.str / Foo 456 | assert_multiline_match(output, r''' 457 | value: \.Foo'> 458 | type: type 459 | signature: class Foo\(\) 460 | 461 | Public attributes: 462 | public: str = 'pub' 463 | ''') 464 | -------------------------------------------------------------------------------- /tests/inspection/test_instaload.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import zlib 3 | from pathlib import Path 4 | 5 | from tests.asserts import assert_multiline_match, StdoutCap 6 | 7 | 8 | def test_load_instaload_snippet(): 9 | code = Path('utils/insta/instaload.txt').read_text() 10 | snippet = zlib.decompress(base64.b64decode(code)).decode() 11 | assert 'wat=Wat()' in snippet.splitlines() 12 | 13 | exec(snippet, globals()) 14 | with StdoutCap() as capture: 15 | wat.short / 'moo' 16 | assert_multiline_match(capture.output(), r''' 17 | value: 'moo' 18 | type: str 19 | len: 3 20 | ''') 21 | 22 | 23 | def test_load_from_magic_glyph(): 24 | glyph = Path('utils/insta/magic_glyph.md').read_text() 25 | exec(zlib.decompress(bytes(ord(c)&255 for c in glyph[1:])).decode(), globals()) 26 | assert wat.short.str / 'WAT is going on?' == '''value: 'WAT is going on?' 27 | type: str 28 | len: 16''' 29 | -------------------------------------------------------------------------------- /utils/demo/.gitignore: -------------------------------------------------------------------------------- 1 | wat-demo.mp4 2 | -------------------------------------------------------------------------------- /utils/demo/livekey.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | 4 | from nuclear import shell, logger 5 | 6 | 7 | def key_type(text, key_delay: float): 8 | text = text.replace('"', '\\"') 9 | for character in text: 10 | shell(f'xdotool key type "{character}"') 11 | time.sleep(key_delay * random.uniform(0.5, 1)) 12 | 13 | 14 | def key_enter(): 15 | shell('xdotool key Return') 16 | 17 | 18 | def type_line(line: str, key_delay: float): 19 | key_type(line.strip(), key_delay) 20 | time.sleep(key_delay * 3) 21 | key_enter() 22 | 23 | 24 | def countdown(): 25 | for i in range(3): 26 | logger.info(f'Starting in {3-i}...') 27 | time.sleep(1) 28 | logger.info('Action!') 29 | 30 | 31 | def animate_commands(commands: list[str], key_delay: float = 0.2, line_delay: float = 1.5): 32 | countdown() 33 | for command in commands: 34 | type_line(command, key_delay) 35 | time.sleep(line_delay) 36 | logger.info('Cut!') 37 | -------------------------------------------------------------------------------- /utils/demo/run-demo-inspect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from livekey import animate_commands 3 | 4 | 5 | commands = [ 6 | 'python', 7 | 'import wat', 8 | 'wat', 9 | 'wat / {7}', 10 | 'wat.short / (1,)', 11 | 'wat.s / {}', 12 | 'import re', 13 | 'wat / re', 14 | 'wat / re.match', 15 | 'wat.locals', 16 | ] 17 | 18 | 19 | if __name__ == '__main__': 20 | animate_commands(commands, key_delay=0.1, line_delay=1.0) 21 | 22 | """ 23 | - Run script: python utils/demo/run-demo-inspect.py 24 | - Record with OBS recorder 25 | - Upload MP4 to Github issue: https://github.com/igrek51/wat/issues/1 26 | - Get source URL like: 27 |