├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ └── pytest.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Pipfile ├── Pipfile.lock ├── README.md ├── demos ├── example.ipynb └── sandbox.zsh.ipynb ├── docs ├── build-install-publish.md ├── readme.md ├── reference.md └── todo.md ├── pyproject.toml ├── screenshots ├── completion.png ├── console.png ├── example.png └── inspection.png ├── tests ├── __init__.py ├── fun_test.py ├── msgspec_v5.py ├── pexpect_zsh_test.py └── zsh_kernel_test.py ├── tools ├── Dockerfile ├── docker.zsh ├── install-pip.sh ├── install-pipenv.sh ├── jupyter-rm-zsh.zsh ├── run-lab.sh └── tag-git.zsh └── zsh_jupyter_kernel ├── __init__.py ├── __main__.py ├── banner.txt ├── capture.zsh ├── config.py ├── fun.py ├── install.py ├── kernel.py ├── logo-32x32.png ├── logo-64x64.png ├── logo.png └── version.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: danoak 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '45 18 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | name: zsh jupyter kernel test 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: [ "3.10" ] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: set up python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v4 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - run: sudo apt-get install zsh 18 | - name: install pipenv 19 | run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python 20 | - run: pipenv install --dev 21 | - name: install zsh jupyter kernel 22 | run: pipenv run python -m zsh_jupyter_kernel.install --sys-prefix 23 | - name: test with pytest 24 | run: pipenv run python -m pytest 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | 3 | /build/ 4 | /dist/ 5 | 6 | .env 7 | /.venv/ 8 | 9 | .ipynb_checkpoints/ 10 | 11 | .pytest_cache/ 12 | __pycache__ 13 | *.egg-info 14 | 15 | .idea/ 16 | *.iml 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # changelog 2 | 3 | ## [3.5.1] - 2024-12-31 4 | ### fixed 5 | - issues with logo copying 6 | - various minor fixes 7 | 8 | ## [3.5.0] - 2022-07-25 9 | ### added 10 | - installation options to change display name and improved options help 11 | 12 | ## [3.4.1] - 2022-07-22 13 | ### removed 14 | - removed logging for production build. this fixes failed installation when log files and directory cannot be created because of access permissions 15 | 16 | ## [3.4] - 2021-07-19 17 | ### fixed 18 | - running `set` command which was previously stuck on execution. 19 | - fixed an error when installing without specifying a `prefix` 20 | - fixed a command to retrieve man pages 21 | 22 | ## [3.2] - 2019-09-05 23 | ### fixed 24 | - fixed issue when zsh did not initialize if host used `add-zsh-hook` to 25 | customize prompts. 26 | - fixed issue when the kernel was timing out when a command did not 27 | responded within 5 seconds. 28 | - fixed issue when the kernel was hanging on continuation prompt 29 | (incomplete input). 30 | 31 | ## [3.0] - 2019-07-07 32 | ### changed 33 | - now package is named `zsh-jupyter-kernel` for specificity 34 | and released at https://pypi.org/project/zsh-jupyter-kernel/ 35 | 36 | ## [2.3] - 2019-06-30 37 | ### added 38 | - ⏹ kernel interruption 39 | 40 | ## [2.0] - 2019-06-29 41 | ### added 42 | - multiline support 43 | - this baby is on pypi now: https://pypi.org/project/zsh-kernel/ 44 | 45 | ## [1.0] - 2019-06-25 46 | ### added 47 | - required basic kernel functionality for singe line z shell commands. 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [keep a changelog](https://keepachangelog.com). 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # GNU GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. 6 | 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | ## Preamble 12 | 13 | The GNU General Public License is a free, copyleft license for 14 | software and other kinds of works. 15 | 16 | The licenses for most software and other practical works are designed 17 | to take away your freedom to share and change the works. By contrast, 18 | the GNU General Public License is intended to guarantee your freedom 19 | to share and change all versions of a program--to make sure it remains 20 | free software for all its users. We, the Free Software Foundation, use 21 | the GNU General Public License for most of our software; it applies 22 | also to any other work released this way by its authors. You can apply 23 | it to your programs, too. 24 | 25 | When we speak of free software, we are referring to freedom, not 26 | price. Our General Public Licenses are designed to make sure that you 27 | have the freedom to distribute copies of free software (and charge for 28 | them if you wish), that you receive source code or can get it if you 29 | want it, that you can change the software or use pieces of it in new 30 | free programs, and that you know you can do these things. 31 | 32 | To protect your rights, we need to prevent others from denying you 33 | these rights or asking you to surrender the rights. Therefore, you 34 | have certain responsibilities if you distribute copies of the 35 | software, or if you modify it: responsibilities to respect the freedom 36 | of others. 37 | 38 | For example, if you distribute copies of such a program, whether 39 | gratis or for a fee, you must pass on to the recipients the same 40 | freedoms that you received. You must make sure that they, too, receive 41 | or can get the source code. And you must show them these terms so they 42 | know their rights. 43 | 44 | Developers that use the GNU GPL protect your rights with two steps: 45 | (1) assert copyright on the software, and (2) offer you this License 46 | giving you legal permission to copy, distribute and/or modify it. 47 | 48 | For the developers' and authors' protection, the GPL clearly explains 49 | that there is no warranty for this free software. For both users' and 50 | authors' sake, the GPL requires that modified versions be marked as 51 | changed, so that their problems will not be attributed erroneously to 52 | authors of previous versions. 53 | 54 | Some devices are designed to deny users access to install or run 55 | modified versions of the software inside them, although the 56 | manufacturer can do so. This is fundamentally incompatible with the 57 | aim of protecting users' freedom to change the software. The 58 | systematic pattern of such abuse occurs in the area of products for 59 | individuals to use, which is precisely where it is most unacceptable. 60 | Therefore, we have designed this version of the GPL to prohibit the 61 | practice for those products. If such problems arise substantially in 62 | other domains, we stand ready to extend this provision to those 63 | domains in future versions of the GPL, as needed to protect the 64 | freedom of users. 65 | 66 | Finally, every program is threatened constantly by software patents. 67 | States should not allow patents to restrict development and use of 68 | software on general-purpose computers, but in those that do, we wish 69 | to avoid the special danger that patents applied to a free program 70 | could make it effectively proprietary. To prevent this, the GPL 71 | assures that patents cannot be used to render the program non-free. 72 | 73 | The precise terms and conditions for copying, distribution and 74 | modification follow. 75 | 76 | ## TERMS AND CONDITIONS 77 | 78 | ### 0. Definitions. 79 | 80 | "This License" refers to version 3 of the GNU General Public License. 81 | 82 | "Copyright" also means copyright-like laws that apply to other kinds 83 | of works, such as semiconductor masks. 84 | 85 | "The Program" refers to any copyrightable work licensed under this 86 | License. Each licensee is addressed as "you". "Licensees" and 87 | "recipients" may be individuals or organizations. 88 | 89 | To "modify" a work means to copy from or adapt all or part of the work 90 | in a fashion requiring copyright permission, other than the making of 91 | an exact copy. The resulting work is called a "modified version" of 92 | the earlier work or a work "based on" the earlier work. 93 | 94 | A "covered work" means either the unmodified Program or a work based 95 | on the Program. 96 | 97 | To "propagate" a work means to do anything with it that, without 98 | permission, would make you directly or secondarily liable for 99 | infringement under applicable copyright law, except executing it on a 100 | computer or modifying a private copy. Propagation includes copying, 101 | distribution (with or without modification), making available to the 102 | public, and in some countries other activities as well. 103 | 104 | To "convey" a work means any kind of propagation that enables other 105 | parties to make or receive copies. Mere interaction with a user 106 | through a computer network, with no transfer of a copy, is not 107 | conveying. 108 | 109 | An interactive user interface displays "Appropriate Legal Notices" to 110 | the extent that it includes a convenient and prominently visible 111 | feature that (1) displays an appropriate copyright notice, and (2) 112 | tells the user that there is no warranty for the work (except to the 113 | extent that warranties are provided), that licensees may convey the 114 | work under this License, and how to view a copy of this License. If 115 | the interface presents a list of user commands or options, such as a 116 | menu, a prominent item in the list meets this criterion. 117 | 118 | ### 1. Source Code. 119 | 120 | The "source code" for a work means the preferred form of the work for 121 | making modifications to it. "Object code" means any non-source form of 122 | a work. 123 | 124 | A "Standard Interface" means an interface that either is an official 125 | standard defined by a recognized standards body, or, in the case of 126 | interfaces specified for a particular programming language, one that 127 | is widely used among developers working in that language. 128 | 129 | The "System Libraries" of an executable work include anything, other 130 | than the work as a whole, that (a) is included in the normal form of 131 | packaging a Major Component, but which is not part of that Major 132 | Component, and (b) serves only to enable use of the work with that 133 | Major Component, or to implement a Standard Interface for which an 134 | implementation is available to the public in source code form. A 135 | "Major Component", in this context, means a major essential component 136 | (kernel, window system, and so on) of the specific operating system 137 | (if any) on which the executable work runs, or a compiler used to 138 | produce the work, or an object code interpreter used to run it. 139 | 140 | The "Corresponding Source" for a work in object code form means all 141 | the source code needed to generate, install, and (for an executable 142 | work) run the object code and to modify the work, including scripts to 143 | control those activities. However, it does not include the work's 144 | System Libraries, or general-purpose tools or generally available free 145 | programs which are used unmodified in performing those activities but 146 | which are not part of the work. For example, Corresponding Source 147 | includes interface definition files associated with source files for 148 | the work, and the source code for shared libraries and dynamically 149 | linked subprograms that the work is specifically designed to require, 150 | such as by intimate data communication or control flow between those 151 | subprograms and other parts of the work. 152 | 153 | The Corresponding Source need not include anything that users can 154 | regenerate automatically from other parts of the Corresponding Source. 155 | 156 | The Corresponding Source for a work in source code form is that same 157 | work. 158 | 159 | ### 2. Basic Permissions. 160 | 161 | All rights granted under this License are granted for the term of 162 | copyright on the Program, and are irrevocable provided the stated 163 | conditions are met. This License explicitly affirms your unlimited 164 | permission to run the unmodified Program. The output from running a 165 | covered work is covered by this License only if the output, given its 166 | content, constitutes a covered work. This License acknowledges your 167 | rights of fair use or other equivalent, as provided by copyright law. 168 | 169 | You may make, run and propagate covered works that you do not convey, 170 | without conditions so long as your license otherwise remains in force. 171 | You may convey covered works to others for the sole purpose of having 172 | them make modifications exclusively for you, or provide you with 173 | facilities for running those works, provided that you comply with the 174 | terms of this License in conveying all material for which you do not 175 | control copyright. Those thus making or running the covered works for 176 | you must do so exclusively on your behalf, under your direction and 177 | control, on terms that prohibit them from making any copies of your 178 | copyrighted material outside their relationship with you. 179 | 180 | Conveying under any other circumstances is permitted solely under the 181 | conditions stated below. Sublicensing is not allowed; section 10 makes 182 | it unnecessary. 183 | 184 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 185 | 186 | No covered work shall be deemed part of an effective technological 187 | measure under any applicable law fulfilling obligations under article 188 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 189 | similar laws prohibiting or restricting circumvention of such 190 | measures. 191 | 192 | When you convey a covered work, you waive any legal power to forbid 193 | circumvention of technological measures to the extent such 194 | circumvention is effected by exercising rights under this License with 195 | respect to the covered work, and you disclaim any intention to limit 196 | operation or modification of the work as a means of enforcing, against 197 | the work's users, your or third parties' legal rights to forbid 198 | circumvention of technological measures. 199 | 200 | ### 4. Conveying Verbatim Copies. 201 | 202 | You may convey verbatim copies of the Program's source code as you 203 | receive it, in any medium, provided that you conspicuously and 204 | appropriately publish on each copy an appropriate copyright notice; 205 | keep intact all notices stating that this License and any 206 | non-permissive terms added in accord with section 7 apply to the code; 207 | keep intact all notices of the absence of any warranty; and give all 208 | recipients a copy of this License along with the Program. 209 | 210 | You may charge any price or no price for each copy that you convey, 211 | and you may offer support or warranty protection for a fee. 212 | 213 | ### 5. Conveying Modified Source Versions. 214 | 215 | You may convey a work based on the Program, or the modifications to 216 | produce it from the Program, in the form of source code under the 217 | terms of section 4, provided that you also meet all of these 218 | conditions: 219 | 220 | - a) The work must carry prominent notices stating that you modified 221 | it, and giving a relevant date. 222 | - b) The work must carry prominent notices stating that it is 223 | released under this License and any conditions added under 224 | section 7. This requirement modifies the requirement in section 4 225 | to "keep intact all notices". 226 | - c) You must license the entire work, as a whole, under this 227 | License to anyone who comes into possession of a copy. This 228 | License will therefore apply, along with any applicable section 7 229 | additional terms, to the whole of the work, and all its parts, 230 | regardless of how they are packaged. This License gives no 231 | permission to license the work in any other way, but it does not 232 | invalidate such permission if you have separately received it. 233 | - d) If the work has interactive user interfaces, each must display 234 | Appropriate Legal Notices; however, if the Program has interactive 235 | interfaces that do not display Appropriate Legal Notices, your 236 | work need not make them do so. 237 | 238 | A compilation of a covered work with other separate and independent 239 | works, which are not by their nature extensions of the covered work, 240 | and which are not combined with it such as to form a larger program, 241 | in or on a volume of a storage or distribution medium, is called an 242 | "aggregate" if the compilation and its resulting copyright are not 243 | used to limit the access or legal rights of the compilation's users 244 | beyond what the individual works permit. Inclusion of a covered work 245 | in an aggregate does not cause this License to apply to the other 246 | parts of the aggregate. 247 | 248 | ### 6. Conveying Non-Source Forms. 249 | 250 | You may convey a covered work in object code form under the terms of 251 | sections 4 and 5, provided that you also convey the machine-readable 252 | Corresponding Source under the terms of this License, in one of these 253 | ways: 254 | 255 | - a) Convey the object code in, or embodied in, a physical product 256 | (including a physical distribution medium), accompanied by the 257 | Corresponding Source fixed on a durable physical medium 258 | customarily used for software interchange. 259 | - b) Convey the object code in, or embodied in, a physical product 260 | (including a physical distribution medium), accompanied by a 261 | written offer, valid for at least three years and valid for as 262 | long as you offer spare parts or customer support for that product 263 | model, to give anyone who possesses the object code either (1) a 264 | copy of the Corresponding Source for all the software in the 265 | product that is covered by this License, on a durable physical 266 | medium customarily used for software interchange, for a price no 267 | more than your reasonable cost of physically performing this 268 | conveying of source, or (2) access to copy the Corresponding 269 | Source from a network server at no charge. 270 | - c) Convey individual copies of the object code with a copy of the 271 | written offer to provide the Corresponding Source. This 272 | alternative is allowed only occasionally and noncommercially, and 273 | only if you received the object code with such an offer, in accord 274 | with subsection 6b. 275 | - d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | - e) Convey the object code using peer-to-peer transmission, 288 | provided you inform other peers where the object code and 289 | Corresponding Source of the work are being offered to the general 290 | public at no charge under subsection 6d. 291 | 292 | A separable portion of the object code, whose source code is excluded 293 | from the Corresponding Source as a System Library, need not be 294 | included in conveying the object code work. 295 | 296 | A "User Product" is either (1) a "consumer product", which means any 297 | tangible personal property which is normally used for personal, 298 | family, or household purposes, or (2) anything designed or sold for 299 | incorporation into a dwelling. In determining whether a product is a 300 | consumer product, doubtful cases shall be resolved in favor of 301 | coverage. For a particular product received by a particular user, 302 | "normally used" refers to a typical or common use of that class of 303 | product, regardless of the status of the particular user or of the way 304 | in which the particular user actually uses, or expects or is expected 305 | to use, the product. A product is a consumer product regardless of 306 | whether the product has substantial commercial, industrial or 307 | non-consumer uses, unless such uses represent the only significant 308 | mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to 312 | install and execute modified versions of a covered work in that User 313 | Product from a modified version of its Corresponding Source. The 314 | information must suffice to ensure that the continued functioning of 315 | the modified object code is in no case prevented or interfered with 316 | solely because modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or 331 | updates for a work that has been modified or installed by the 332 | recipient, or for the User Product in which it has been modified or 333 | installed. Access to a network may be denied when the modification 334 | itself materially and adversely affects the operation of the network 335 | or violates the rules and protocols for communication across the 336 | network. 337 | 338 | Corresponding Source conveyed, and Installation Information provided, 339 | in accord with this section must be in a format that is publicly 340 | documented (and with an implementation available to the public in 341 | source code form), and must require no special password or key for 342 | unpacking, reading or copying. 343 | 344 | ### 7. Additional Terms. 345 | 346 | "Additional permissions" are terms that supplement the terms of this 347 | License by making exceptions from one or more of its conditions. 348 | Additional permissions that are applicable to the entire Program shall 349 | be treated as though they were included in this License, to the extent 350 | that they are valid under applicable law. If additional permissions 351 | apply only to part of the Program, that part may be used separately 352 | under those permissions, but the entire Program remains governed by 353 | this License without regard to the additional permissions. 354 | 355 | When you convey a copy of a covered work, you may at your option 356 | remove any additional permissions from that copy, or from any part of 357 | it. (Additional permissions may be written to require their own 358 | removal in certain cases when you modify the work.) You may place 359 | additional permissions on material, added by you to a covered work, 360 | for which you have or can give appropriate copyright permission. 361 | 362 | Notwithstanding any other provision of this License, for material you 363 | add to a covered work, you may (if authorized by the copyright holders 364 | of that material) supplement the terms of this License with terms: 365 | 366 | - a) Disclaiming warranty or limiting liability differently from the 367 | terms of sections 15 and 16 of this License; or 368 | - b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | - c) Prohibiting misrepresentation of the origin of that material, 372 | or requiring that modified versions of such material be marked in 373 | reasonable ways as different from the original version; or 374 | - d) Limiting the use for publicity purposes of names of licensors 375 | or authors of the material; or 376 | - e) Declining to grant rights under trademark law for use of some 377 | trade names, trademarks, or service marks; or 378 | - f) Requiring indemnification of licensors and authors of that 379 | material by anyone who conveys the material (or modified versions 380 | of it) with contractual assumptions of liability to the recipient, 381 | for any liability that these contractual assumptions directly 382 | impose on those licensors and authors. 383 | 384 | All other non-permissive additional terms are considered "further 385 | restrictions" within the meaning of section 10. If the Program as you 386 | received it, or any part of it, contains a notice stating that it is 387 | governed by this License along with a term that is a further 388 | restriction, you may remove that term. If a license document contains 389 | a further restriction but permits relicensing or conveying under this 390 | License, you may add to a covered work material governed by the terms 391 | of that license document, provided that the further restriction does 392 | not survive such relicensing or conveying. 393 | 394 | If you add terms to a covered work in accord with this section, you 395 | must place, in the relevant source files, a statement of the 396 | additional terms that apply to those files, or a notice indicating 397 | where to find the applicable terms. 398 | 399 | Additional terms, permissive or non-permissive, may be stated in the 400 | form of a separately written license, or stated as exceptions; the 401 | above requirements apply either way. 402 | 403 | ### 8. Termination. 404 | 405 | You may not propagate or modify a covered work except as expressly 406 | provided under this License. Any attempt otherwise to propagate or 407 | modify it is void, and will automatically terminate your rights under 408 | this License (including any patent licenses granted under the third 409 | paragraph of section 11). 410 | 411 | However, if you cease all violation of this License, then your license 412 | from a particular copyright holder is reinstated (a) provisionally, 413 | unless and until the copyright holder explicitly and finally 414 | terminates your license, and (b) permanently, if the copyright holder 415 | fails to notify you of the violation by some reasonable means prior to 416 | 60 days after the cessation. 417 | 418 | Moreover, your license from a particular copyright holder is 419 | reinstated permanently if the copyright holder notifies you of the 420 | violation by some reasonable means, this is the first time you have 421 | received notice of violation of this License (for any work) from that 422 | copyright holder, and you cure the violation prior to 30 days after 423 | your receipt of the notice. 424 | 425 | Termination of your rights under this section does not terminate the 426 | licenses of parties who have received copies or rights from you under 427 | this License. If your rights have been terminated and not permanently 428 | reinstated, you do not qualify to receive new licenses for the same 429 | material under section 10. 430 | 431 | ### 9. Acceptance Not Required for Having Copies. 432 | 433 | You are not required to accept this License in order to receive or run 434 | a copy of the Program. Ancillary propagation of a covered work 435 | occurring solely as a consequence of using peer-to-peer transmission 436 | to receive a copy likewise does not require acceptance. However, 437 | nothing other than this License grants you permission to propagate or 438 | modify any covered work. These actions infringe copyright if you do 439 | not accept this License. Therefore, by modifying or propagating a 440 | covered work, you indicate your acceptance of this License to do so. 441 | 442 | ### 10. Automatic Licensing of Downstream Recipients. 443 | 444 | Each time you convey a covered work, the recipient automatically 445 | receives a license from the original licensors, to run, modify and 446 | propagate that work, subject to this License. You are not responsible 447 | for enforcing compliance by third parties with this License. 448 | 449 | An "entity transaction" is a transaction transferring control of an 450 | organization, or substantially all assets of one, or subdividing an 451 | organization, or merging organizations. If propagation of a covered 452 | work results from an entity transaction, each party to that 453 | transaction who receives a copy of the work also receives whatever 454 | licenses to the work the party's predecessor in interest had or could 455 | give under the previous paragraph, plus a right to possession of the 456 | Corresponding Source of the work from the predecessor in interest, if 457 | the predecessor has it or can get it with reasonable efforts. 458 | 459 | You may not impose any further restrictions on the exercise of the 460 | rights granted or affirmed under this License. For example, you may 461 | not impose a license fee, royalty, or other charge for exercise of 462 | rights granted under this License, and you may not initiate litigation 463 | (including a cross-claim or counterclaim in a lawsuit) alleging that 464 | any patent claim is infringed by making, using, selling, offering for 465 | sale, or importing the Program or any portion of it. 466 | 467 | ### 11. Patents. 468 | 469 | A "contributor" is a copyright holder who authorizes use under this 470 | License of the Program or a work on which the Program is based. The 471 | work thus licensed is called the contributor's "contributor version". 472 | 473 | A contributor's "essential patent claims" are all patent claims owned 474 | or controlled by the contributor, whether already acquired or 475 | hereafter acquired, that would be infringed by some manner, permitted 476 | by this License, of making, using, or selling its contributor version, 477 | but do not include claims that would be infringed only as a 478 | consequence of further modification of the contributor version. For 479 | purposes of this definition, "control" includes the right to grant 480 | patent sublicenses in a manner consistent with the requirements of 481 | this License. 482 | 483 | Each contributor grants you a non-exclusive, worldwide, royalty-free 484 | patent license under the contributor's essential patent claims, to 485 | make, use, sell, offer for sale, import and otherwise run, modify and 486 | propagate the contents of its contributor version. 487 | 488 | In the following three paragraphs, a "patent license" is any express 489 | agreement or commitment, however denominated, not to enforce a patent 490 | (such as an express permission to practice a patent or covenant not to 491 | sue for patent infringement). To "grant" such a patent license to a 492 | party means to make such an agreement or commitment not to enforce a 493 | patent against the party. 494 | 495 | If you convey a covered work, knowingly relying on a patent license, 496 | and the Corresponding Source of the work is not available for anyone 497 | to copy, free of charge and under the terms of this License, through a 498 | publicly available network server or other readily accessible means, 499 | then you must either (1) cause the Corresponding Source to be so 500 | available, or (2) arrange to deprive yourself of the benefit of the 501 | patent license for this particular work, or (3) arrange, in a manner 502 | consistent with the requirements of this License, to extend the patent 503 | license to downstream recipients. "Knowingly relying" means you have 504 | actual knowledge that, but for the patent license, your conveying the 505 | covered work in a country, or your recipient's use of the covered work 506 | in a country, would infringe one or more identifiable patents in that 507 | country that you have reason to believe are valid. 508 | 509 | If, pursuant to or in connection with a single transaction or 510 | arrangement, you convey, or propagate by procuring conveyance of, a 511 | covered work, and grant a patent license to some of the parties 512 | receiving the covered work authorizing them to use, propagate, modify 513 | or convey a specific copy of the covered work, then the patent license 514 | you grant is automatically extended to all recipients of the covered 515 | work and works based on it. 516 | 517 | A patent license is "discriminatory" if it does not include within the 518 | scope of its coverage, prohibits the exercise of, or is conditioned on 519 | the non-exercise of one or more of the rights that are specifically 520 | granted under this License. You may not convey a covered work if you 521 | are a party to an arrangement with a third party that is in the 522 | business of distributing software, under which you make payment to the 523 | third party based on the extent of your activity of conveying the 524 | work, and under which the third party grants, to any of the parties 525 | who would receive the covered work from you, a discriminatory patent 526 | license (a) in connection with copies of the covered work conveyed by 527 | you (or copies made from those copies), or (b) primarily for and in 528 | connection with specific products or compilations that contain the 529 | covered work, unless you entered into that arrangement, or that patent 530 | license was granted, prior to 28 March 2007. 531 | 532 | Nothing in this License shall be construed as excluding or limiting 533 | any implied license or other defenses to infringement that may 534 | otherwise be available to you under applicable patent law. 535 | 536 | ### 12. No Surrender of Others' Freedom. 537 | 538 | If conditions are imposed on you (whether by court order, agreement or 539 | otherwise) that contradict the conditions of this License, they do not 540 | excuse you from the conditions of this License. If you cannot convey a 541 | covered work so as to satisfy simultaneously your obligations under 542 | this License and any other pertinent obligations, then as a 543 | consequence you may not convey it at all. For example, if you agree to 544 | terms that obligate you to collect a royalty for further conveying 545 | from those to whom you convey the Program, the only way you could 546 | satisfy both those terms and this License would be to refrain entirely 547 | from conveying the Program. 548 | 549 | ### 13. Use with the GNU Affero General Public License. 550 | 551 | Notwithstanding any other provision of this License, you have 552 | permission to link or combine any covered work with a work licensed 553 | under version 3 of the GNU Affero General Public License into a single 554 | combined work, and to convey the resulting work. The terms of this 555 | License will continue to apply to the part which is the covered work, 556 | but the special requirements of the GNU Affero General Public License, 557 | section 13, concerning interaction through a network will apply to the 558 | combination as such. 559 | 560 | ### 14. Revised Versions of this License. 561 | 562 | The Free Software Foundation may publish revised and/or new versions 563 | of the GNU General Public License from time to time. Such new versions 564 | will be similar in spirit to the present version, but may differ in 565 | detail to address new problems or concerns. 566 | 567 | Each version is given a distinguishing version number. If the Program 568 | specifies that a certain numbered version of the GNU General Public 569 | License "or any later version" applies to it, you have the option of 570 | following the terms and conditions either of that numbered version or 571 | of any later version published by the Free Software Foundation. If the 572 | Program does not specify a version number of the GNU General Public 573 | License, you may choose any version ever published by the Free 574 | Software Foundation. 575 | 576 | If the Program specifies that a proxy can decide which future versions 577 | of the GNU General Public License can be used, that proxy's public 578 | statement of acceptance of a version permanently authorizes you to 579 | choose that version for the Program. 580 | 581 | Later license versions may give you additional or different 582 | permissions. However, no additional obligations are imposed on any 583 | author or copyright holder as a result of your choosing to follow a 584 | later version. 585 | 586 | ### 15. Disclaimer of Warranty. 587 | 588 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 589 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 590 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT 591 | WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT 592 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 593 | A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND 594 | PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 595 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR 596 | CORRECTION. 597 | 598 | ### 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR 602 | CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 603 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES 604 | ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT 605 | NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR 606 | LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM 607 | TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER 608 | PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 609 | 610 | ### 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | ## How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these 626 | terms. 627 | 628 | To do so, attach the following notices to the program. It is safest to 629 | attach them to the start of each source file to most effectively state 630 | the exclusion of warranty; and each file should have at least the 631 | "copyright" line and a pointer to where the full notice is found. 632 | 633 | 634 | Copyright (C) 635 | 636 | This program is free software: you can redistribute it and/or modify 637 | it under the terms of the GNU General Public License as published by 638 | the Free Software Foundation, either version 3 of the License, or 639 | (at your option) any later version. 640 | 641 | This program is distributed in the hope that it will be useful, 642 | but WITHOUT ANY WARRANTY; without even the implied warranty of 643 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 644 | GNU General Public License for more details. 645 | 646 | You should have received a copy of the GNU General Public License 647 | along with this program. If not, see . 648 | 649 | Also add information on how to contact you by electronic and paper 650 | mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands \`show w' and \`show c' should show the 661 | appropriate parts of the General Public License. Of course, your 662 | program's commands might be different; for a GUI interface, you would 663 | use an "about box". 664 | 665 | You should also get your employer (if you work as a programmer) or 666 | school, if any, to sign a "copyright disclaimer" for the program, if 667 | necessary. For more information on this, and how to apply and follow 668 | the GNU GPL, see . 669 | 670 | The GNU General Public License does not permit incorporating your 671 | program into proprietary programs. If your program is a subroutine 672 | library, you may consider it more useful to permit linking proprietary 673 | applications with the library. If this is what you want to do, use the 674 | GNU Lesser General Public License instead of this License. But first, 675 | please read . 676 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [packages] 7 | zsh-jupyter-kernel = { editable = true, path = "." } 8 | jupyterlab = "*" 9 | jsonschema = "*" # testing message specs (msgspec_v5.py) 10 | pytest = "*" 11 | 12 | [requires] 13 | python_version = "3.10" 14 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "d7a50ee2d4a48ac214c9e8fe821b6f29297c7884a7e0fff355323d142666d266" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "anyio": { 20 | "hashes": [ 21 | "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", 22 | "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352" 23 | ], 24 | "markers": "python_version >= '3.9'", 25 | "version": "==4.7.0" 26 | }, 27 | "argon2-cffi": { 28 | "hashes": [ 29 | "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", 30 | "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea" 31 | ], 32 | "markers": "python_version >= '3.7'", 33 | "version": "==23.1.0" 34 | }, 35 | "argon2-cffi-bindings": { 36 | "hashes": [ 37 | "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670", 38 | "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f", 39 | "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583", 40 | "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194", 41 | "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", 42 | "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a", 43 | "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", 44 | "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5", 45 | "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", 46 | "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7", 47 | "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", 48 | "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", 49 | "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", 50 | "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", 51 | "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", 52 | "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", 53 | "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d", 54 | "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", 55 | "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb", 56 | "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", 57 | "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351" 58 | ], 59 | "markers": "python_version >= '3.6'", 60 | "version": "==21.2.0" 61 | }, 62 | "arrow": { 63 | "hashes": [ 64 | "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", 65 | "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85" 66 | ], 67 | "markers": "python_version >= '3.8'", 68 | "version": "==1.3.0" 69 | }, 70 | "asttokens": { 71 | "hashes": [ 72 | "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", 73 | "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2" 74 | ], 75 | "markers": "python_version >= '3.8'", 76 | "version": "==3.0.0" 77 | }, 78 | "async-lru": { 79 | "hashes": [ 80 | "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627", 81 | "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224" 82 | ], 83 | "markers": "python_version >= '3.8'", 84 | "version": "==2.0.4" 85 | }, 86 | "attrs": { 87 | "hashes": [ 88 | "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", 89 | "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" 90 | ], 91 | "markers": "python_version >= '3.8'", 92 | "version": "==24.3.0" 93 | }, 94 | "babel": { 95 | "hashes": [ 96 | "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", 97 | "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316" 98 | ], 99 | "markers": "python_version >= '3.8'", 100 | "version": "==2.16.0" 101 | }, 102 | "beautifulsoup4": { 103 | "hashes": [ 104 | "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", 105 | "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed" 106 | ], 107 | "markers": "python_full_version >= '3.6.0'", 108 | "version": "==4.12.3" 109 | }, 110 | "bleach": { 111 | "hashes": [ 112 | "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", 113 | "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f" 114 | ], 115 | "markers": "python_version >= '3.9'", 116 | "version": "==6.2.0" 117 | }, 118 | "certifi": { 119 | "hashes": [ 120 | "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", 121 | "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" 122 | ], 123 | "markers": "python_version >= '3.6'", 124 | "version": "==2024.12.14" 125 | }, 126 | "cffi": { 127 | "hashes": [ 128 | "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", 129 | "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", 130 | "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", 131 | "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", 132 | "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", 133 | "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", 134 | "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", 135 | "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", 136 | "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", 137 | "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", 138 | "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", 139 | "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", 140 | "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", 141 | "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", 142 | "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", 143 | "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", 144 | "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", 145 | "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", 146 | "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", 147 | "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", 148 | "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", 149 | "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", 150 | "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", 151 | "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", 152 | "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", 153 | "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", 154 | "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", 155 | "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", 156 | "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", 157 | "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", 158 | "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", 159 | "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", 160 | "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", 161 | "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", 162 | "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", 163 | "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", 164 | "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", 165 | "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", 166 | "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", 167 | "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", 168 | "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", 169 | "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", 170 | "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", 171 | "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", 172 | "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", 173 | "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", 174 | "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", 175 | "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", 176 | "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", 177 | "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", 178 | "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", 179 | "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", 180 | "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", 181 | "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", 182 | "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", 183 | "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", 184 | "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", 185 | "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", 186 | "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", 187 | "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", 188 | "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", 189 | "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", 190 | "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", 191 | "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", 192 | "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", 193 | "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", 194 | "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" 195 | ], 196 | "markers": "python_version >= '3.8'", 197 | "version": "==1.17.1" 198 | }, 199 | "charset-normalizer": { 200 | "hashes": [ 201 | "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", 202 | "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", 203 | "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", 204 | "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", 205 | "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", 206 | "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", 207 | "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", 208 | "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", 209 | "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", 210 | "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", 211 | "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", 212 | "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", 213 | "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", 214 | "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", 215 | "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", 216 | "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", 217 | "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", 218 | "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", 219 | "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", 220 | "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", 221 | "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", 222 | "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", 223 | "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", 224 | "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", 225 | "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", 226 | "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", 227 | "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", 228 | "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", 229 | "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", 230 | "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", 231 | "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", 232 | "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", 233 | "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", 234 | "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", 235 | "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", 236 | "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", 237 | "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", 238 | "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", 239 | "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", 240 | "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", 241 | "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", 242 | "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", 243 | "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", 244 | "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", 245 | "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", 246 | "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", 247 | "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", 248 | "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", 249 | "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", 250 | "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", 251 | "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", 252 | "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", 253 | "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", 254 | "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", 255 | "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", 256 | "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", 257 | "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", 258 | "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", 259 | "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", 260 | "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", 261 | "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", 262 | "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", 263 | "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", 264 | "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", 265 | "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", 266 | "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", 267 | "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", 268 | "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", 269 | "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", 270 | "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", 271 | "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", 272 | "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", 273 | "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", 274 | "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", 275 | "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", 276 | "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", 277 | "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", 278 | "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", 279 | "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", 280 | "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", 281 | "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", 282 | "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", 283 | "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", 284 | "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", 285 | "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", 286 | "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", 287 | "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", 288 | "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", 289 | "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", 290 | "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", 291 | "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", 292 | "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" 293 | ], 294 | "markers": "python_version >= '3.7'", 295 | "version": "==3.4.1" 296 | }, 297 | "comm": { 298 | "hashes": [ 299 | "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", 300 | "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3" 301 | ], 302 | "markers": "python_version >= '3.8'", 303 | "version": "==0.2.2" 304 | }, 305 | "debugpy": { 306 | "hashes": [ 307 | "sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920", 308 | "sha256:116bf8342062246ca749013df4f6ea106f23bc159305843491f64672a55af2e5", 309 | "sha256:189058d03a40103a57144752652b3ab08ff02b7595d0ce1f651b9acc3a3a35a0", 310 | "sha256:23dc34c5e03b0212fa3c49a874df2b8b1b8fda95160bd79c01eb3ab51ea8d851", 311 | "sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b", 312 | "sha256:2b26fefc4e31ff85593d68b9022e35e8925714a10ab4858fb1b577a8a48cb8cd", 313 | "sha256:32db46ba45849daed7ccf3f2e26f7a386867b077f39b2a974bb5c4c2c3b0a280", 314 | "sha256:40499a9979c55f72f4eb2fc38695419546b62594f8af194b879d2a18439c97a9", 315 | "sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1", 316 | "sha256:52c3cf9ecda273a19cc092961ee34eb9ba8687d67ba34cc7b79a521c1c64c4c0", 317 | "sha256:52d8a3166c9f2815bfae05f386114b0b2d274456980d41f320299a8d9a5615a7", 318 | "sha256:61bc8b3b265e6949855300e84dc93d02d7a3a637f2aec6d382afd4ceb9120c9f", 319 | "sha256:654130ca6ad5de73d978057eaf9e582244ff72d4574b3e106fb8d3d2a0d32458", 320 | "sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57", 321 | "sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e", 322 | "sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308", 323 | "sha256:85de8474ad53ad546ff1c7c7c89230db215b9b8a02754d41cb5a76f70d0be296", 324 | "sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3", 325 | "sha256:8da1db4ca4f22583e834dcabdc7832e56fe16275253ee53ba66627b86e304da1", 326 | "sha256:8ffc382e4afa4aee367bf413f55ed17bd91b191dcaf979890af239dda435f2a1", 327 | "sha256:987bce16e86efa86f747d5151c54e91b3c1e36acc03ce1ddb50f9d09d16ded0e", 328 | "sha256:ad7efe588c8f5cf940f40c3de0cd683cc5b76819446abaa50dc0829a30c094db", 329 | "sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28", 330 | "sha256:c928bbf47f65288574b78518449edaa46c82572d340e2750889bbf8cd92f3737", 331 | "sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768", 332 | "sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1" 333 | ], 334 | "markers": "python_version >= '3.8'", 335 | "version": "==1.8.11" 336 | }, 337 | "decorator": { 338 | "hashes": [ 339 | "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", 340 | "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" 341 | ], 342 | "markers": "python_version >= '3.5'", 343 | "version": "==5.1.1" 344 | }, 345 | "defusedxml": { 346 | "hashes": [ 347 | "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", 348 | "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" 349 | ], 350 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 351 | "version": "==0.7.1" 352 | }, 353 | "exceptiongroup": { 354 | "hashes": [ 355 | "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", 356 | "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" 357 | ], 358 | "markers": "python_version >= '3.7'", 359 | "version": "==1.2.2" 360 | }, 361 | "executing": { 362 | "hashes": [ 363 | "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", 364 | "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab" 365 | ], 366 | "markers": "python_version >= '3.8'", 367 | "version": "==2.1.0" 368 | }, 369 | "fastjsonschema": { 370 | "hashes": [ 371 | "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", 372 | "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667" 373 | ], 374 | "version": "==2.21.1" 375 | }, 376 | "fqdn": { 377 | "hashes": [ 378 | "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", 379 | "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014" 380 | ], 381 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 382 | "version": "==1.5.1" 383 | }, 384 | "h11": { 385 | "hashes": [ 386 | "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", 387 | "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" 388 | ], 389 | "markers": "python_version >= '3.7'", 390 | "version": "==0.14.0" 391 | }, 392 | "httpcore": { 393 | "hashes": [ 394 | "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", 395 | "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd" 396 | ], 397 | "markers": "python_version >= '3.8'", 398 | "version": "==1.0.7" 399 | }, 400 | "httpx": { 401 | "hashes": [ 402 | "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", 403 | "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" 404 | ], 405 | "markers": "python_version >= '3.8'", 406 | "version": "==0.28.1" 407 | }, 408 | "idna": { 409 | "hashes": [ 410 | "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", 411 | "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" 412 | ], 413 | "markers": "python_version >= '3.6'", 414 | "version": "==3.10" 415 | }, 416 | "iniconfig": { 417 | "hashes": [ 418 | "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", 419 | "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" 420 | ], 421 | "markers": "python_version >= '3.7'", 422 | "version": "==2.0.0" 423 | }, 424 | "ipykernel": { 425 | "hashes": [ 426 | "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", 427 | "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215" 428 | ], 429 | "markers": "python_version >= '3.8'", 430 | "version": "==6.29.5" 431 | }, 432 | "ipython": { 433 | "hashes": [ 434 | "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6", 435 | "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b" 436 | ], 437 | "markers": "python_version >= '3.10'", 438 | "version": "==8.31.0" 439 | }, 440 | "isoduration": { 441 | "hashes": [ 442 | "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", 443 | "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042" 444 | ], 445 | "markers": "python_version >= '3.7'", 446 | "version": "==20.11.0" 447 | }, 448 | "jedi": { 449 | "hashes": [ 450 | "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", 451 | "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9" 452 | ], 453 | "markers": "python_version >= '3.6'", 454 | "version": "==0.19.2" 455 | }, 456 | "jinja2": { 457 | "hashes": [ 458 | "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", 459 | "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" 460 | ], 461 | "markers": "python_version >= '3.7'", 462 | "version": "==3.1.5" 463 | }, 464 | "json5": { 465 | "hashes": [ 466 | "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa", 467 | "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559" 468 | ], 469 | "markers": "python_full_version >= '3.8.0'", 470 | "version": "==0.10.0" 471 | }, 472 | "jsonpointer": { 473 | "hashes": [ 474 | "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", 475 | "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef" 476 | ], 477 | "markers": "python_version >= '3.7'", 478 | "version": "==3.0.0" 479 | }, 480 | "jsonschema": { 481 | "extras": [ 482 | "format-nongpl" 483 | ], 484 | "hashes": [ 485 | "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", 486 | "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566" 487 | ], 488 | "index": "pypi", 489 | "markers": "python_version >= '3.8'", 490 | "version": "==4.23.0" 491 | }, 492 | "jsonschema-specifications": { 493 | "hashes": [ 494 | "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", 495 | "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf" 496 | ], 497 | "markers": "python_version >= '3.9'", 498 | "version": "==2024.10.1" 499 | }, 500 | "jupyter-client": { 501 | "hashes": [ 502 | "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", 503 | "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f" 504 | ], 505 | "markers": "python_version >= '3.8'", 506 | "version": "==8.6.3" 507 | }, 508 | "jupyter-core": { 509 | "hashes": [ 510 | "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", 511 | "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9" 512 | ], 513 | "markers": "python_version >= '3.8'", 514 | "version": "==5.7.2" 515 | }, 516 | "jupyter-events": { 517 | "hashes": [ 518 | "sha256:36399b41ce1ca45fe8b8271067d6a140ffa54cec4028e95491c93b78a855cacf", 519 | "sha256:c0bc56a37aac29c1fbc3bcfbddb8c8c49533f9cf11f1c4e6adadba936574ab90" 520 | ], 521 | "markers": "python_version >= '3.9'", 522 | "version": "==0.11.0" 523 | }, 524 | "jupyter-lsp": { 525 | "hashes": [ 526 | "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da", 527 | "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001" 528 | ], 529 | "markers": "python_version >= '3.8'", 530 | "version": "==2.2.5" 531 | }, 532 | "jupyter-server": { 533 | "hashes": [ 534 | "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3", 535 | "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084" 536 | ], 537 | "markers": "python_version >= '3.9'", 538 | "version": "==2.15.0" 539 | }, 540 | "jupyter-server-terminals": { 541 | "hashes": [ 542 | "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", 543 | "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269" 544 | ], 545 | "markers": "python_version >= '3.8'", 546 | "version": "==0.5.3" 547 | }, 548 | "jupyterlab": { 549 | "hashes": [ 550 | "sha256:b754c2601c5be6adf87cb5a1d8495d653ffb945f021939f77776acaa94dae952", 551 | "sha256:f0bb9b09a04766e3423cccc2fc23169aa2ffedcdf8713e9e0fb33cac0b6859d0" 552 | ], 553 | "index": "pypi", 554 | "markers": "python_version >= '3.8'", 555 | "version": "==4.3.4" 556 | }, 557 | "jupyterlab-pygments": { 558 | "hashes": [ 559 | "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", 560 | "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780" 561 | ], 562 | "markers": "python_version >= '3.8'", 563 | "version": "==0.3.0" 564 | }, 565 | "jupyterlab-server": { 566 | "hashes": [ 567 | "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", 568 | "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4" 569 | ], 570 | "markers": "python_version >= '3.8'", 571 | "version": "==2.27.3" 572 | }, 573 | "markupsafe": { 574 | "hashes": [ 575 | "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", 576 | "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", 577 | "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", 578 | "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", 579 | "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", 580 | "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", 581 | "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", 582 | "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", 583 | "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", 584 | "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", 585 | "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", 586 | "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", 587 | "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", 588 | "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", 589 | "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", 590 | "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", 591 | "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", 592 | "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", 593 | "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", 594 | "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", 595 | "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", 596 | "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", 597 | "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", 598 | "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", 599 | "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", 600 | "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", 601 | "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", 602 | "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", 603 | "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", 604 | "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", 605 | "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", 606 | "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", 607 | "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", 608 | "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", 609 | "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", 610 | "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", 611 | "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", 612 | "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", 613 | "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", 614 | "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", 615 | "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", 616 | "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", 617 | "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", 618 | "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", 619 | "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", 620 | "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", 621 | "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", 622 | "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", 623 | "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", 624 | "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", 625 | "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", 626 | "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", 627 | "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", 628 | "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", 629 | "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", 630 | "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", 631 | "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", 632 | "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", 633 | "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", 634 | "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", 635 | "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" 636 | ], 637 | "markers": "python_version >= '3.9'", 638 | "version": "==3.0.2" 639 | }, 640 | "matplotlib-inline": { 641 | "hashes": [ 642 | "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", 643 | "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca" 644 | ], 645 | "markers": "python_version >= '3.8'", 646 | "version": "==0.1.7" 647 | }, 648 | "mistune": { 649 | "hashes": [ 650 | "sha256:b05198cf6d671b3deba6c87ec6cf0d4eb7b72c524636eddb6dbf13823b52cee1", 651 | "sha256:dbcac2f78292b9dc066cd03b7a3a26b62d85f8159f2ea5fd28e55df79908d667" 652 | ], 653 | "markers": "python_version >= '3.8'", 654 | "version": "==3.1.0" 655 | }, 656 | "nbclient": { 657 | "hashes": [ 658 | "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", 659 | "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193" 660 | ], 661 | "markers": "python_full_version >= '3.9.0'", 662 | "version": "==0.10.2" 663 | }, 664 | "nbconvert": { 665 | "hashes": [ 666 | "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3", 667 | "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4" 668 | ], 669 | "markers": "python_version >= '3.8'", 670 | "version": "==7.16.4" 671 | }, 672 | "nbformat": { 673 | "hashes": [ 674 | "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", 675 | "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b" 676 | ], 677 | "markers": "python_version >= '3.8'", 678 | "version": "==5.10.4" 679 | }, 680 | "nest-asyncio": { 681 | "hashes": [ 682 | "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", 683 | "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c" 684 | ], 685 | "markers": "python_version >= '3.5'", 686 | "version": "==1.6.0" 687 | }, 688 | "notebook-shim": { 689 | "hashes": [ 690 | "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", 691 | "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb" 692 | ], 693 | "markers": "python_version >= '3.7'", 694 | "version": "==0.2.4" 695 | }, 696 | "overrides": { 697 | "hashes": [ 698 | "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", 699 | "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49" 700 | ], 701 | "markers": "python_version >= '3.6'", 702 | "version": "==7.7.0" 703 | }, 704 | "packaging": { 705 | "hashes": [ 706 | "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", 707 | "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" 708 | ], 709 | "markers": "python_version >= '3.8'", 710 | "version": "==24.2" 711 | }, 712 | "pandocfilters": { 713 | "hashes": [ 714 | "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", 715 | "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc" 716 | ], 717 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 718 | "version": "==1.5.1" 719 | }, 720 | "parso": { 721 | "hashes": [ 722 | "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", 723 | "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d" 724 | ], 725 | "markers": "python_version >= '3.6'", 726 | "version": "==0.8.4" 727 | }, 728 | "pexpect": { 729 | "hashes": [ 730 | "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", 731 | "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" 732 | ], 733 | "version": "==4.9.0" 734 | }, 735 | "platformdirs": { 736 | "hashes": [ 737 | "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", 738 | "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" 739 | ], 740 | "markers": "python_version >= '3.8'", 741 | "version": "==4.3.6" 742 | }, 743 | "pluggy": { 744 | "hashes": [ 745 | "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", 746 | "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" 747 | ], 748 | "markers": "python_version >= '3.8'", 749 | "version": "==1.5.0" 750 | }, 751 | "prometheus-client": { 752 | "hashes": [ 753 | "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", 754 | "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301" 755 | ], 756 | "markers": "python_version >= '3.8'", 757 | "version": "==0.21.1" 758 | }, 759 | "prompt-toolkit": { 760 | "hashes": [ 761 | "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", 762 | "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e" 763 | ], 764 | "markers": "python_full_version >= '3.7.0'", 765 | "version": "==3.0.48" 766 | }, 767 | "psutil": { 768 | "hashes": [ 769 | "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca", 770 | "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", 771 | "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468", 772 | "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", 773 | "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603", 774 | "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac", 775 | "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303", 776 | "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4", 777 | "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", 778 | "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8", 779 | "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", 780 | "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030", 781 | "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777", 782 | "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", 783 | "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", 784 | "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", 785 | "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8" 786 | ], 787 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 788 | "version": "==6.1.1" 789 | }, 790 | "ptyprocess": { 791 | "hashes": [ 792 | "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", 793 | "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" 794 | ], 795 | "version": "==0.7.0" 796 | }, 797 | "pure-eval": { 798 | "hashes": [ 799 | "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", 800 | "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42" 801 | ], 802 | "version": "==0.2.3" 803 | }, 804 | "pycparser": { 805 | "hashes": [ 806 | "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", 807 | "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" 808 | ], 809 | "markers": "python_version >= '3.8'", 810 | "version": "==2.22" 811 | }, 812 | "pygments": { 813 | "hashes": [ 814 | "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", 815 | "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" 816 | ], 817 | "markers": "python_version >= '3.8'", 818 | "version": "==2.18.0" 819 | }, 820 | "pytest": { 821 | "hashes": [ 822 | "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", 823 | "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761" 824 | ], 825 | "index": "pypi", 826 | "markers": "python_version >= '3.8'", 827 | "version": "==8.3.4" 828 | }, 829 | "python-dateutil": { 830 | "hashes": [ 831 | "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", 832 | "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" 833 | ], 834 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", 835 | "version": "==2.9.0.post0" 836 | }, 837 | "python-json-logger": { 838 | "hashes": [ 839 | "sha256:8eb0554ea17cb75b05d2848bc14fb02fbdbd9d6972120781b974380bfa162008", 840 | "sha256:cdc17047eb5374bd311e748b42f99d71223f3b0e186f4206cc5d52aefe85b090" 841 | ], 842 | "markers": "python_version >= '3.8'", 843 | "version": "==3.2.1" 844 | }, 845 | "pyyaml": { 846 | "hashes": [ 847 | "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", 848 | "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", 849 | "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", 850 | "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", 851 | "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", 852 | "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", 853 | "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", 854 | "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", 855 | "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", 856 | "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", 857 | "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", 858 | "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", 859 | "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", 860 | "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", 861 | "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", 862 | "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", 863 | "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", 864 | "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", 865 | "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", 866 | "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", 867 | "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", 868 | "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", 869 | "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", 870 | "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", 871 | "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", 872 | "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", 873 | "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", 874 | "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", 875 | "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", 876 | "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", 877 | "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", 878 | "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", 879 | "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", 880 | "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", 881 | "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", 882 | "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", 883 | "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", 884 | "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", 885 | "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", 886 | "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", 887 | "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", 888 | "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", 889 | "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", 890 | "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", 891 | "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", 892 | "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", 893 | "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", 894 | "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", 895 | "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", 896 | "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", 897 | "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", 898 | "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", 899 | "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" 900 | ], 901 | "markers": "python_version >= '3.8'", 902 | "version": "==6.0.2" 903 | }, 904 | "pyzmq": { 905 | "hashes": [ 906 | "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", 907 | "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a", 908 | "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9", 909 | "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f", 910 | "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37", 911 | "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc", 912 | "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed", 913 | "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097", 914 | "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d", 915 | "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52", 916 | "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6", 917 | "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6", 918 | "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2", 919 | "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282", 920 | "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3", 921 | "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732", 922 | "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5", 923 | "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18", 924 | "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", 925 | "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f", 926 | "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3", 927 | "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b", 928 | "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277", 929 | "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a", 930 | "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797", 931 | "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca", 932 | "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c", 933 | "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f", 934 | "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5", 935 | "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a", 936 | "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44", 937 | "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20", 938 | "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4", 939 | "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8", 940 | "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780", 941 | "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386", 942 | "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5", 943 | "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2", 944 | "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", 945 | "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971", 946 | "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b", 947 | "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50", 948 | "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c", 949 | "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f", 950 | "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231", 951 | "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c", 952 | "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08", 953 | "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5", 954 | "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6", 955 | "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073", 956 | "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e", 957 | "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4", 958 | "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317", 959 | "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3", 960 | "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072", 961 | "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad", 962 | "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a", 963 | "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb", 964 | "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd", 965 | "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f", 966 | "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef", 967 | "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5", 968 | "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187", 969 | "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711", 970 | "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988", 971 | "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640", 972 | "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c", 973 | "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764", 974 | "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1", 975 | "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1", 976 | "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289", 977 | "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb", 978 | "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a", 979 | "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218", 980 | "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c", 981 | "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf", 982 | "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7", 983 | "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8", 984 | "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726", 985 | "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9", 986 | "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93", 987 | "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88", 988 | "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115", 989 | "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6", 990 | "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672", 991 | "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2", 992 | "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea", 993 | "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc", 994 | "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b", 995 | "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa", 996 | "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003", 997 | "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797", 998 | "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940", 999 | "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db", 1000 | "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc", 1001 | "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27", 1002 | "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3", 1003 | "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e", 1004 | "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98", 1005 | "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b", 1006 | "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629", 1007 | "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9", 1008 | "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6", 1009 | "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec", 1010 | "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951", 1011 | "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae", 1012 | "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4", 1013 | "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6", 1014 | "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919" 1015 | ], 1016 | "markers": "python_version >= '3.7'", 1017 | "version": "==26.2.0" 1018 | }, 1019 | "referencing": { 1020 | "hashes": [ 1021 | "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", 1022 | "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de" 1023 | ], 1024 | "markers": "python_version >= '3.8'", 1025 | "version": "==0.35.1" 1026 | }, 1027 | "requests": { 1028 | "hashes": [ 1029 | "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", 1030 | "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" 1031 | ], 1032 | "markers": "python_version >= '3.8'", 1033 | "version": "==2.32.3" 1034 | }, 1035 | "rfc3339-validator": { 1036 | "hashes": [ 1037 | "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", 1038 | "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa" 1039 | ], 1040 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1041 | "version": "==0.1.4" 1042 | }, 1043 | "rfc3986-validator": { 1044 | "hashes": [ 1045 | "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", 1046 | "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055" 1047 | ], 1048 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1049 | "version": "==0.1.1" 1050 | }, 1051 | "rpds-py": { 1052 | "hashes": [ 1053 | "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", 1054 | "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", 1055 | "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", 1056 | "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", 1057 | "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9", 1058 | "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543", 1059 | "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2", 1060 | "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a", 1061 | "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d", 1062 | "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", 1063 | "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d", 1064 | "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", 1065 | "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", 1066 | "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", 1067 | "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99", 1068 | "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", 1069 | "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd", 1070 | "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe", 1071 | "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", 1072 | "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", 1073 | "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f", 1074 | "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3", 1075 | "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca", 1076 | "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d", 1077 | "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e", 1078 | "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", 1079 | "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea", 1080 | "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", 1081 | "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", 1082 | "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", 1083 | "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff", 1084 | "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723", 1085 | "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e", 1086 | "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493", 1087 | "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6", 1088 | "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", 1089 | "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091", 1090 | "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", 1091 | "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", 1092 | "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1", 1093 | "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728", 1094 | "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", 1095 | "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c", 1096 | "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", 1097 | "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7", 1098 | "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a", 1099 | "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", 1100 | "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967", 1101 | "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", 1102 | "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24", 1103 | "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055", 1104 | "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d", 1105 | "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0", 1106 | "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e", 1107 | "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", 1108 | "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c", 1109 | "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", 1110 | "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", 1111 | "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652", 1112 | "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8", 1113 | "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11", 1114 | "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", 1115 | "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96", 1116 | "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64", 1117 | "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b", 1118 | "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e", 1119 | "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c", 1120 | "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9", 1121 | "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec", 1122 | "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb", 1123 | "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37", 1124 | "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad", 1125 | "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", 1126 | "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c", 1127 | "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf", 1128 | "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", 1129 | "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f", 1130 | "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d", 1131 | "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09", 1132 | "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", 1133 | "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566", 1134 | "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74", 1135 | "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338", 1136 | "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", 1137 | "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c", 1138 | "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648", 1139 | "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", 1140 | "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3", 1141 | "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123", 1142 | "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520", 1143 | "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831", 1144 | "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", 1145 | "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", 1146 | "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", 1147 | "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", 1148 | "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", 1149 | "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", 1150 | "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", 1151 | "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", 1152 | "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5", 1153 | "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d", 1154 | "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00", 1155 | "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e" 1156 | ], 1157 | "markers": "python_version >= '3.9'", 1158 | "version": "==0.22.3" 1159 | }, 1160 | "send2trash": { 1161 | "hashes": [ 1162 | "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", 1163 | "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf" 1164 | ], 1165 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 1166 | "version": "==1.8.3" 1167 | }, 1168 | "setuptools": { 1169 | "hashes": [ 1170 | "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6", 1171 | "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d" 1172 | ], 1173 | "markers": "python_version >= '3.9'", 1174 | "version": "==75.6.0" 1175 | }, 1176 | "six": { 1177 | "hashes": [ 1178 | "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", 1179 | "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" 1180 | ], 1181 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", 1182 | "version": "==1.17.0" 1183 | }, 1184 | "sniffio": { 1185 | "hashes": [ 1186 | "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", 1187 | "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" 1188 | ], 1189 | "markers": "python_version >= '3.7'", 1190 | "version": "==1.3.1" 1191 | }, 1192 | "soupsieve": { 1193 | "hashes": [ 1194 | "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", 1195 | "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9" 1196 | ], 1197 | "markers": "python_version >= '3.8'", 1198 | "version": "==2.6" 1199 | }, 1200 | "stack-data": { 1201 | "hashes": [ 1202 | "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", 1203 | "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695" 1204 | ], 1205 | "version": "==0.6.3" 1206 | }, 1207 | "terminado": { 1208 | "hashes": [ 1209 | "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", 1210 | "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e" 1211 | ], 1212 | "markers": "python_version >= '3.8'", 1213 | "version": "==0.18.1" 1214 | }, 1215 | "tinycss2": { 1216 | "hashes": [ 1217 | "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", 1218 | "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289" 1219 | ], 1220 | "markers": "python_version >= '3.8'", 1221 | "version": "==1.4.0" 1222 | }, 1223 | "tomli": { 1224 | "hashes": [ 1225 | "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", 1226 | "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", 1227 | "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", 1228 | "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", 1229 | "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", 1230 | "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", 1231 | "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", 1232 | "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", 1233 | "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", 1234 | "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", 1235 | "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", 1236 | "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", 1237 | "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", 1238 | "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", 1239 | "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", 1240 | "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", 1241 | "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", 1242 | "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", 1243 | "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", 1244 | "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", 1245 | "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", 1246 | "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", 1247 | "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", 1248 | "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", 1249 | "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", 1250 | "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", 1251 | "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", 1252 | "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", 1253 | "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", 1254 | "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", 1255 | "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", 1256 | "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7" 1257 | ], 1258 | "markers": "python_version >= '3.8'", 1259 | "version": "==2.2.1" 1260 | }, 1261 | "tornado": { 1262 | "hashes": [ 1263 | "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", 1264 | "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", 1265 | "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", 1266 | "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", 1267 | "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", 1268 | "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", 1269 | "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", 1270 | "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", 1271 | "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", 1272 | "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", 1273 | "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1" 1274 | ], 1275 | "markers": "python_version >= '3.8'", 1276 | "version": "==6.4.2" 1277 | }, 1278 | "traitlets": { 1279 | "hashes": [ 1280 | "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", 1281 | "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f" 1282 | ], 1283 | "markers": "python_version >= '3.8'", 1284 | "version": "==5.14.3" 1285 | }, 1286 | "types-python-dateutil": { 1287 | "hashes": [ 1288 | "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", 1289 | "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53" 1290 | ], 1291 | "markers": "python_version >= '3.8'", 1292 | "version": "==2.9.0.20241206" 1293 | }, 1294 | "typing-extensions": { 1295 | "hashes": [ 1296 | "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", 1297 | "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" 1298 | ], 1299 | "markers": "python_version >= '3.8'", 1300 | "version": "==4.12.2" 1301 | }, 1302 | "uri-template": { 1303 | "hashes": [ 1304 | "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", 1305 | "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363" 1306 | ], 1307 | "markers": "python_version >= '3.7'", 1308 | "version": "==1.3.0" 1309 | }, 1310 | "urllib3": { 1311 | "hashes": [ 1312 | "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", 1313 | "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" 1314 | ], 1315 | "markers": "python_version >= '3.9'", 1316 | "version": "==2.3.0" 1317 | }, 1318 | "wcwidth": { 1319 | "hashes": [ 1320 | "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", 1321 | "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" 1322 | ], 1323 | "version": "==0.2.13" 1324 | }, 1325 | "webcolors": { 1326 | "hashes": [ 1327 | "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", 1328 | "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6" 1329 | ], 1330 | "markers": "python_version >= '3.9'", 1331 | "version": "==24.11.1" 1332 | }, 1333 | "webencodings": { 1334 | "hashes": [ 1335 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 1336 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 1337 | ], 1338 | "version": "==0.5.1" 1339 | }, 1340 | "websocket-client": { 1341 | "hashes": [ 1342 | "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", 1343 | "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da" 1344 | ], 1345 | "markers": "python_version >= '3.8'", 1346 | "version": "==1.8.0" 1347 | }, 1348 | "zsh-jupyter-kernel": { 1349 | "editable": true, 1350 | "path": "." 1351 | } 1352 | }, 1353 | "develop": {} 1354 | } 1355 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zsh kernel for Jupyter 2 | 3 | ![](https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/master/screenshots/example.png) 4 | 5 | a simple Z Shell Jupyter kernel powered by Python, IPyKernel, Pexpect, 6 | and enthusiasm — turn your scripts into notebooks! 7 | 8 | ## installation 9 | 10 | [PyPi](https://pypi.org/project/zsh-jupyter-kernel/): 11 | 12 | Pip 13 | ```sh 14 | python -m pip install zsh_jupyter_kernel 15 | ``` 16 | 17 | Pipenv 18 | ```sh 19 | pipenv install zsh_jupyter_kernel 20 | ``` 21 | 22 | ### install kernel file 23 | 24 | see the help command for details 25 | ```sh 26 | python -m zsh_jupyter_kernel.install --help 27 | ``` 28 | -------------------------------------------------------------------------------- /demos/example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | " ___ _ ___ _ ____ ___ \n", 13 | " / / |__ (_)_ __ / / |__ __ _ ___| |__ | ___|/ _ \\ \n", 14 | " / /| '_ \\| | '_ \\ / /| '_ \\ / _` / __| '_ \\ |___ \\ (_) |\n", 15 | " / / | |_) | | | | |/ / | |_) | (_| \\__ \\ | | | ___) \\__, |\n", 16 | "/_/ |_.__/|_|_| |_/_/ |_.__/ \\__,_|___/_| |_| |____(_)/_/ \n", 17 | " \n" 18 | ] 19 | } 20 | ], 21 | "source": [ 22 | "figlet $SHELL $ZSH_VERSION" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | "--------------------------------------------------------------------------------\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "function hr { printf \"%-*s\\n\" $(tput cols) | tr ' ' '-' }\n", 40 | "hr" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 3, 46 | "metadata": {}, 47 | "outputs": [ 48 | { 49 | "name": "stdout", 50 | "output_type": "stream", 51 | "text": [ 52 | "\u001b[33m47817e2\u001b[m\u001b[33m (\u001b[m\u001b[1;36mHEAD\u001b[m\u001b[33m -> \u001b[m\u001b[1;32mmaster\u001b[m\u001b[33m)\u001b[m major cleanup\n", 53 | "\u001b[33m3a49d9c\u001b[m minor reformat refactor\n", 54 | "\u001b[33m1bb6fe6\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/master\u001b[m\u001b[33m, \u001b[m\u001b[1;31morigin/HEAD\u001b[m\u001b[33m)\u001b[m Fix bug on copying logo files\n", 55 | "--------------------------------------------------------------------------------\n", 56 | " \u001b[31mM\u001b[m ../.gitignore\n", 57 | " \u001b[31mM\u001b[m ../CONTRIBUTING.md\n", 58 | " \u001b[31mM\u001b[m ../Pipfile.lock\n", 59 | " \u001b[31mM\u001b[m ../README.md\n", 60 | "\u001b[32mR\u001b[m ../build-publish.md -> ../docs/build-install-publish.md\n", 61 | "\u001b[32mA\u001b[m\u001b[31mM\u001b[m ../docs/readme.md\n", 62 | "\u001b[32mR\u001b[m\u001b[31mM\u001b[m ../todo.md -> ../docs/todo.md\n", 63 | " \u001b[31mD\u001b[m ../example.ipynb\n", 64 | " \u001b[31mM\u001b[m ../pyproject.toml\n", 65 | " \u001b[31mD\u001b[m ../tests/sandbox.zsh.ipynb\n", 66 | " \u001b[31mM\u001b[m ../tests/zsh_kernel_test.py\n", 67 | " \u001b[31mM\u001b[m ../tools/install-pipenv.sh\n", 68 | "\u001b[32mD\u001b[m ../zsh_jupyter_kernel/LICENSE.txt\n", 69 | "\u001b[32mD\u001b[m ../zsh_jupyter_kernel/README.md\n", 70 | " \u001b[31mM\u001b[m ../zsh_jupyter_kernel/__init__.py\n", 71 | " \u001b[31mM\u001b[m ../zsh_jupyter_kernel/config.py\n", 72 | " \u001b[31mM\u001b[m ../zsh_jupyter_kernel/fun.py\n", 73 | " \u001b[31mM\u001b[m ../zsh_jupyter_kernel/install.py\n", 74 | " \u001b[31mM\u001b[m ../zsh_jupyter_kernel/kernel.py\n", 75 | " \u001b[31mM\u001b[m ../zsh_jupyter_kernel/reference.md\n", 76 | "\u001b[32mD\u001b[m ../zsh_jupyter_kernel/version\n", 77 | "\u001b[32mA\u001b[m\u001b[31mM\u001b[m ../zsh_jupyter_kernel/version.txt\n", 78 | "\u001b[31m??\u001b[m ./\n", 79 | "--------------------------------------------------------------------------------\n", 80 | "\u001b[1;33mexample.ipynb\u001b[0m \u001b[1;33msandbox.zsh.ipynb\u001b[0m\n" 81 | ] 82 | } 83 | ], 84 | "source": [ 85 | "git --no-pager log --oneline ...HEAD~3\n", 86 | "hr\n", 87 | "git status --short\n", 88 | "hr\n", 89 | "git ls-files src/zsh_jupyter_kernel | xargs exa" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 4, 95 | "metadata": { 96 | "tags": [] 97 | }, 98 | "outputs": [ 99 | { 100 | "name": "stdout", 101 | "output_type": "stream", 102 | "text": [ 103 | "Weather report: Chernivtsi\n", 104 | "\n", 105 | " \u001b[38;5;226m \\ / \u001b[0m Sunny\n", 106 | " \u001b[38;5;226m .-. \u001b[0m \u001b[38;5;049m+3\u001b[0m(\u001b[38;5;049m2\u001b[0m) °C\u001b[0m \n", 107 | " \u001b[38;5;226m ― ( ) ― \u001b[0m \u001b[1m↗\u001b[0m \u001b[38;5;118m4\u001b[0m km/h\u001b[0m \n", 108 | " \u001b[38;5;226m `-’ \u001b[0m 10 km\u001b[0m \n", 109 | " \u001b[38;5;226m / \\ \u001b[0m 0.0 mm\u001b[0m \n" 110 | ] 111 | } 112 | ], 113 | "source": [ 114 | "curl -s wttr.in/Chernivtsi | head -7" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [] 123 | } 124 | ], 125 | "metadata": { 126 | "kernelspec": { 127 | "display_name": "Z shell (dev)", 128 | "language": "zsh", 129 | "name": "zsh-dev" 130 | }, 131 | "language_info": { 132 | "codemirror_mode": "shell", 133 | "file_extension": ".zsh", 134 | "mimetype": "text/x-zsh", 135 | "name": "zsh", 136 | "pygments_lexer": "shell", 137 | "version": "5.7.1" 138 | } 139 | }, 140 | "nbformat": 4, 141 | "nbformat_minor": 4 142 | } 143 | -------------------------------------------------------------------------------- /demos/sandbox.zsh.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "598ef7eb-8877-4586-a7f1-add8de665038", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "name": "stdout", 11 | "output_type": "stream", 12 | "text": [ 13 | "1\n", 14 | "2\n", 15 | "3\n" 16 | ] 17 | } 18 | ], 19 | "source": [ 20 | "print 1\n", 21 | "echo 2\n", 22 | "<<< 3" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "id": "eb3060f6-21aa-4947-94de-233fef1481ea", 29 | "metadata": {}, 30 | "outputs": [ 31 | { 32 | "name": "stdout", 33 | "output_type": "stream", 34 | "text": [ 35 | "example.ipynb sandbox.zsh.ipynb\n" 36 | ] 37 | } 38 | ], 39 | "source": [ 40 | "ls" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "id": "ba302c08-17ba-4863-a97d-3bc8236a4f83", 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [] 50 | } 51 | ], 52 | "metadata": { 53 | "kernelspec": { 54 | "display_name": "Z shell (dev)", 55 | "language": "zsh", 56 | "name": "zsh-dev" 57 | }, 58 | "language_info": { 59 | "codemirror_mode": "shell", 60 | "file_extension": ".zsh", 61 | "mimetype": "text/x-zsh", 62 | "name": "zsh", 63 | "pygments_lexer": "shell", 64 | "version": "5.7.1" 65 | } 66 | }, 67 | "nbformat": 4, 68 | "nbformat_minor": 5 69 | } 70 | -------------------------------------------------------------------------------- /docs/build-install-publish.md: -------------------------------------------------------------------------------- 1 | https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools 2 | 3 | ```zsh 4 | python -m pip install setuptools 5 | python -m pip install build 6 | python -m pip install twine 7 | 8 | python -m build 9 | 10 | twine upload --repository testpypi dist/* 11 | 12 | pip install --index-url https://test.pypi.org/simple/ zsh-jupyter-kernel 13 | python -m zsh_jupyter_kernel.install --sys-prefix 14 | 15 | #version=`cat version` 16 | twine upload \ 17 | dist/zsh-jupyter-kernel-$version.tar.gz \ 18 | dist/zsh_jupyter_kernel-$version-py3-none-any.whl 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Zsh kernel for Jupyter 2 | 3 | ![](https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/master/screenshots/example.png) 4 | 5 | a simple Z Shell Jupyter kernel powered by Python, IPyKernel, Pexpect, 6 | and enthusiasm — turn your scripts into notebooks! 7 | 8 | ## technical details 9 | 10 | the kernel launches Zsh as if it was a regular process launched from your 11 | terminal with a few minor settings to make sure it works with Jupyter. there is 12 | slight chance it wont work with super complicated zshrc setups, but it works 13 | with majority of configs including oh-my-zsh. 14 | 15 | ### code execution 16 | 17 | the kernel configures Zsh prompt string to its own custom value. 18 | when a user requests a cell execution, the code is sent to the kernel. 19 | then the kernel puts the frontend on hold, sends the code to Zsh process, and 20 | waits for the prompt string to release the frontend and let the user request 21 | more code execution. 22 | 23 | ### code completion 24 | 25 | ![](https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/master/screenshots/completion.png) 26 | 27 | code completion is powered by quite a non-trivial script that involves multiple 28 | steps, including spawning another temporary Zsh process and capturing completion 29 | options into a data structure that jupyter frontend understands. 30 | 31 | ### code inspection 32 | 33 | ![](https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/master/screenshots/inspection.png) 34 | 35 | code inspection is done by `man --pager ul` which sends the whole man page to 36 | the frontend. 37 | 38 | ### code completeness 39 | 40 | code completeness is checked with the temporary Zsh process and help of `EXEC` 41 | Zsh option, which allows switching off code execution and simply check if the 42 | code is complete using the exit code of the Zsh process itself. 43 | 44 | ### stderr 45 | 46 | stderr content is just sent to the front-end as regular stdout the same way it 47 | is in a terminal. 48 | 49 | ### stdin 50 | 51 | stdin is not supported because of the execution system when a process spawned by 52 | a user waits for stdin, there is no way to detect it. 53 | 54 | jupyter is a request-reply system, and Zsh as a shell that constantly receives 55 | input and prints whatever current processes want to output. there is no clear 56 | start and end of a code execution in a shell unlike in jupyter system: a 57 | front-end sends a code from a cell to a kernel and waits until the kernel sends 58 | the full output back. 59 | 60 | because of these two different ways of interacting with user Zsh jupyter kernel 61 | cannot process stdin in a way python kernel does on `input()`, meaning you will 62 | not be able to enter a sudo password or answer y/n to prompts or use a pager 63 | like less. when a spawned program waits for a user input, you will need to 64 | interrupt the kernel and use any options which do not require input. 65 | -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | ## Reference 2 | - pexpect-spawn: https://pexpect.readthedocs.io/en/stable/api/pexpect.html#spawn-class 3 | - pexpect-spawn-timeout: https://pexpect.readthedocs.io/en/stable/api/pexpect.html?highlight=timeout#spawn-class 4 | - codecs: https://docs.python.org/3/library/codecs.html 5 | - logging: https://docs.python-guide.org/writing/logging/ 6 | - zsh-options: https://linux.die.net/man/1/zshoptions 7 | - zsh-functions: http://zsh.sourceforge.net/Doc/Release/Functions.html 8 | - zsh-bracketed-paste: https://archive.zhimingwang.org/blog/2015-09-21-zsh-51-and-bracketed-paste.html 9 | - zsh-prompts: https://jlk.fjfi.cvut.cz/arch/manpages/man/zshparam.1#PARAMETERS_USED_BY_THE_SHELL 10 | - zsh-hooks: http://zsh.sourceforge.net/Doc/Release/User-Contributions.html 11 | - Section "Manipulating Hook Functions" 12 | Different plugins (e.g. oh-my-zsh with custom themes) use `precmd`/`preexec` hooks to set prompts. `add-zsh-hook -D ` allows to delete all assosiated functions from a hook. 13 | - interrupt: https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-interrupt 14 | - kernel-specs: https://jupyter-client.readthedocs.io/en/latest/kernels.html#kernelspecs 15 | -------------------------------------------------------------------------------- /docs/todo.md: -------------------------------------------------------------------------------- 1 | - history (for frontend) 2 | - rich html output for things like images and tables 3 | - stored and referenceable outputs 4 | - there are some setup zsh commands which need to be executed on kernel startup and they are polluting user zsh history 5 | there might be a way to exclude them from the history record 6 | - logos with transparency 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "zsh-jupyter-kernel" 3 | dynamic = ["version"] 4 | requires-python = ">=3.10" 5 | description = "Z shell kernel for Project Jupyter" 6 | readme = "README.md" 7 | license = { file = "LICENSE.md" } 8 | authors = [{ name = "Danylo Dubinin", email = "danylo.dubinin@gmail.com" }] 9 | keywords = ["jupyter", "zsh", "shell"] 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Framework :: Jupyter", 13 | "Intended Audience :: Developers", 14 | "Intended Audience :: Science/Research", 15 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 16 | "Operating System :: OS Independent", 17 | "Programming Language :: Python :: 3.12", 18 | "Programming Language :: Unix Shell", 19 | "Topic :: System :: Shells", 20 | ] 21 | dependencies = ["ipykernel", "jupyter_client", "pexpect"] 22 | [project.urls] 23 | "Homepage" = "https://github.com/dahn-zk/zsh-jupyter-kernel" 24 | "Repository" = "https://github.com/dahn-zk/zsh-jupyter-kernel.git" 25 | "Changelog" = "https://github.com/dahn-zk/zsh-jupyter-kernel/blob/master/CHANGELOG.md" 26 | 27 | [build-system] 28 | requires = ["setuptools >= 61.0"] 29 | build-backend = "setuptools.build_meta" 30 | [tool.setuptools] 31 | packages = ["zsh_jupyter_kernel"] 32 | [tool.setuptools.dynamic] 33 | version = { file = "zsh_jupyter_kernel/version.txt" } 34 | [tool.setuptools.package-data] 35 | zsh_jupyter_kernel = ["version.txt", "capture.zsh", "banner.txt", "logo.png", 36 | "logo-32x32.png", "logo-64x64.png"] -------------------------------------------------------------------------------- /screenshots/completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/e591c27c791c9c6f8ba7296074fa9a316690426f/screenshots/completion.png -------------------------------------------------------------------------------- /screenshots/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/e591c27c791c9c6f8ba7296074fa9a316690426f/screenshots/console.png -------------------------------------------------------------------------------- /screenshots/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/e591c27c791c9c6f8ba7296074fa9a316690426f/screenshots/example.png -------------------------------------------------------------------------------- /screenshots/inspection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/e591c27c791c9c6f8ba7296074fa9a316690426f/screenshots/inspection.png -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/e591c27c791c9c6f8ba7296074fa9a316690426f/tests/__init__.py -------------------------------------------------------------------------------- /tests/fun_test.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase, main 2 | 3 | from zsh_jupyter_kernel.fun import find_word_at_pos 4 | 5 | class fun_test(TestCase): 6 | 7 | def test_get_word_at_pos(self): 8 | text = "non! autem~42? [ut]" 9 | t = [ 10 | "non", "non", "non", "non", 11 | "", 12 | "autem", "autem", "autem", "autem", "autem", "autem", 13 | "42?", "42?", "42?", "42?" 14 | "", "", 15 | "ut", "ut", 16 | ] 17 | for i, a in enumerate(t): 18 | self.assertEqual(a, find_word_at_pos(text, pos = i), f"pos = {i}") 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /tests/msgspec_v5.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from jsonschema import Draft4Validator, ValidationError 4 | 5 | protocol_version = (5, 1) 6 | 7 | # These fragments will be wrapped in the boilerplate for a valid JSON schema. 8 | # We also add a default 'required' containing all keys. 9 | schema_fragments = {} 10 | 11 | 12 | def get_msg_content_validator(msg_type, version_minor): 13 | frag = schema_fragments[msg_type] 14 | schema = { 15 | "$schema": "http://json-schema.org/draft-04/schema#", 16 | "description": f"{msg_type} message contents schema", 17 | "type": "object", 18 | "properties": {}, 19 | "additionalProperties": version_minor > protocol_version[1], 20 | } 21 | schema.update(frag) 22 | if "required" not in schema: 23 | # Require all keys by default 24 | schema["required"] = sorted(schema["properties"].keys()) 25 | 26 | return Draft4Validator(schema) 27 | 28 | 29 | header_part = { 30 | "type": "object", 31 | "properties": { 32 | "msg_id": {"type": "string"}, 33 | "username": {"type": "string"}, 34 | "session": {"type": "string"}, 35 | # TODO - this is parsed to a datetime before we get it: 36 | "date": {}, # {"type": "string"}, 37 | "msg_type": {"type": "string"}, 38 | "version": {"type": "string"}, 39 | }, 40 | "required": ["msg_id", "username", "session", "date", "msg_type", "version"], 41 | } 42 | 43 | msg_schema = { 44 | "$schema": "http://json-schema.org/draft-04/schema#", 45 | "description": "Jupyter message structure schema", 46 | "type": "object", 47 | "properties": { 48 | "header": header_part, 49 | "parent_header": {"type": "object"}, 50 | "metadata": {"type": "object"}, 51 | "content": {"type": "object"}, # Checked separately 52 | "buffers": {"type": "array"}, 53 | }, 54 | "required": ["header", "parent_header", "metadata", "content"], 55 | } 56 | msg_structure_validator = Draft4Validator(msg_schema) 57 | 58 | 59 | def get_error_reply_validator(version_minor): 60 | return Draft4Validator( 61 | { 62 | "$schema": "http://json-schema.org/draft-04/schema#", 63 | "description": "Jupyter 'error' reply schema", 64 | "type": "object", 65 | "properties": { 66 | "status": {"const": "error"}, 67 | "ename": {"type": "string"}, 68 | "evalue": {"type": "string"}, 69 | "traceback": {"type": "array", "items": {"type": "string"}}, 70 | }, 71 | "required": ["status", "ename", "evalue", "traceback"], 72 | "additionalProperties": version_minor > protocol_version[1], 73 | } 74 | ) 75 | 76 | 77 | def get_abort_reply_validator(version_minor): 78 | return Draft4Validator( 79 | { 80 | "$schema": "http://json-schema.org/draft-04/schema#", 81 | "description": "Jupyter 'abort' reply schema", 82 | "type": "object", 83 | "properties": { 84 | "status": {"const": "error"}, 85 | "ename": {"type": "string"}, 86 | "evalue": {"type": "string"}, 87 | "traceback": {"type": "list", "items": {"type": "string"}}, 88 | }, 89 | "required": ["status", "ename", "evalue", "traceback"], 90 | "additionalProperties": version_minor > protocol_version[1], 91 | } 92 | ) 93 | 94 | 95 | reply_msgs_using_status = { 96 | "execute_reply", 97 | "inspect_reply", 98 | "complete_reply", 99 | "history_reply", 100 | "connect_reply", 101 | "comm_info_reply", 102 | "kernel_info_reply", 103 | "shutdown_reply", 104 | "interrupt_reply", 105 | } 106 | 107 | 108 | def validate_message(msg, msg_type=None, parent_id=None): 109 | msg_structure_validator.validate(msg) 110 | 111 | msg_version_s = msg["header"]["version"] 112 | m = re.match(r"(\d+)\.(\d+)", msg_version_s) 113 | if not m: 114 | raise ValidationError("Version {} not like 'x.y'") 115 | version_minor = int(m.group(2)) 116 | 117 | if msg_type is not None: 118 | if msg["header"]["msg_type"] != msg_type: 119 | raise ValidationError( 120 | "Message type {!r} != {!r}".format(msg["header"]["msg_type"], msg_type) 121 | ) 122 | else: 123 | msg_type = msg["header"]["msg_type"] 124 | 125 | # Check for unexpected fields, unless it's a newer protocol version 126 | if version_minor <= protocol_version[1]: 127 | unx_top = set(msg) - set(msg_schema["properties"]) 128 | if unx_top: 129 | raise ValidationError(f"Unexpected keys: {unx_top}") 130 | 131 | unx_header = set(msg["header"]) - set(header_part["properties"]) 132 | if unx_header: 133 | raise ValidationError(f"Unexpected keys in header: {unx_header}") 134 | 135 | # Check the parent id 136 | if "reply" in msg_type and parent_id and msg["parent_header"]["msg_id"] != parent_id: 137 | raise ValidationError("Parent header does not match expected") 138 | 139 | if msg_type in reply_msgs_using_status: 140 | # Most _reply messages have common 'error' and 'abort' structures 141 | try: 142 | status = msg["content"]["status"] 143 | except KeyError as e: 144 | raise ValidationError(str(e)) 145 | if status == "error": 146 | content_vdor = get_error_reply_validator(version_minor) 147 | elif status == "abort": 148 | content_vdor = get_abort_reply_validator(version_minor) 149 | elif status == "ok": 150 | content_vdor = get_msg_content_validator(msg_type, version_minor) 151 | else: 152 | raise ValidationError(f"status {status!r} should be ok/error/abort") 153 | else: 154 | content_vdor = get_msg_content_validator(msg_type, version_minor) 155 | 156 | content_vdor.validate(msg["content"]) 157 | 158 | 159 | # Shell messages ---------------------------------------------- 160 | 161 | schema_fragments["execute_request"] = { 162 | "properties": { 163 | "code": {"type": "string"}, 164 | "silent": {"type": "boolean"}, 165 | "store_history": {"type": "boolean"}, 166 | "user_expressions": {"type": "object"}, 167 | "allow_stdin": {"type": "boolean"}, 168 | "stop_on_error": {"type": "boolean"}, 169 | } 170 | } 171 | 172 | schema_fragments["execute_reply"] = { 173 | "properties": { 174 | # statuses 'error' and 'abort' change the structure, so check separately 175 | "status": {"const": "ok"}, 176 | "execution_count": {"type": "number"}, 177 | "payload": { 178 | "type": "array", 179 | "items": { 180 | "type": "object", 181 | "properties": {"source": {"type": "string"}}, 182 | "additionalProperties": True, 183 | }, 184 | }, 185 | "user_expressions": {"type": "object"}, 186 | }, 187 | "required": ["status", "execution_count"], 188 | } 189 | 190 | schema_fragments["inspect_request"] = { 191 | "properties": { 192 | "code": {"type": "string"}, 193 | "cursor_pos": {"type": "number"}, 194 | "detail_level": {"enum": [0, 1]}, 195 | } 196 | } 197 | 198 | schema_fragments["inspect_reply"] = { 199 | "properties": { 200 | # statuses 'error' and 'abort' change the structure, so check separately 201 | "status": {"const": "ok"}, 202 | "found": {"type": "boolean"}, 203 | "data": {"type": "object"}, 204 | "metadata": {"type": "object"}, 205 | } 206 | } 207 | 208 | schema_fragments["complete_request"] = { 209 | "properties": { 210 | "code": {"type": "string"}, 211 | "cursor_pos": {"type": "number"}, 212 | } 213 | } 214 | 215 | schema_fragments["complete_reply"] = { 216 | "properties": { 217 | # statuses 'error' and 'abort' change the structure, so check separately 218 | "status": {"const": "ok"}, 219 | "matches": {"type": "array", "items": {"type": "string"}}, 220 | "cursor_start": {"type": "number"}, 221 | "cursor_end": {"type": "number"}, 222 | "metadata": {"type": "object"}, 223 | } 224 | } 225 | 226 | schema_fragments["history_request"] = { 227 | "properties": { 228 | "output": {"type": "boolean"}, 229 | "raw": {"type": "boolean"}, 230 | "hist_access_type": {"enum": ["range", "tail", "search"]}, 231 | "session": {"type": "number"}, 232 | "start": {"type": "number"}, 233 | "stop": {"type": "number"}, 234 | "n": {"type": "number"}, 235 | "pattern": {"type": "string"}, 236 | "unique": {"type": "boolean"}, 237 | }, 238 | "required": ["output", "raw", "hist_access_type"], 239 | } 240 | 241 | schema_fragments["history_reply"] = { 242 | "properties": { 243 | "status": {"const": "ok"}, 244 | "history": {"type": "array", "items": {"minItems": 3, "maxItems": 3}}, 245 | } 246 | } 247 | 248 | schema_fragments["is_complete_request"] = { 249 | "properties": { 250 | "code": {"type": "string"}, 251 | } 252 | } 253 | 254 | schema_fragments["is_complete_reply"] = { 255 | "properties": { 256 | "status": {"enum": ["complete", "incomplete", "invalid", "unknown"]}, 257 | "indent": {"type": "string"}, 258 | }, 259 | "required": ["status"], 260 | } 261 | 262 | # NB connect_request is deprecated 263 | schema_fragments["connect_request"] = {"properties": {}} 264 | 265 | schema_fragments["connect_reply"] = { 266 | "properties": { 267 | "shell_port": {"type": "number"}, 268 | "iopub_port": {"type": "number"}, 269 | "stdin_port": {"type": "number"}, 270 | "hb_port": {"type": "number"}, 271 | "control_port": {"type": "number"}, 272 | } 273 | } 274 | 275 | schema_fragments["comm_info_request"] = { 276 | "properties": { 277 | "target_name": {"type": "string"}, 278 | }, 279 | "required": [], 280 | } 281 | 282 | schema_fragments["comm_info_reply"] = { 283 | "properties": { 284 | # statuses 'error' and 'abort' change the structure, so check separately 285 | "status": {"const": "ok"}, 286 | "comms": {"type": "object"}, 287 | } 288 | } 289 | 290 | schema_fragments["kernel_info_request"] = {"properties": {}} 291 | 292 | schema_fragments["kernel_info_reply"] = { 293 | "properties": { 294 | # statuses 'error' and 'abort' change the structure, so check separately 295 | "status": {"const": "ok"}, 296 | "protocol_version": {"type": "string"}, 297 | "implementation": {"type": "string"}, 298 | "implementation_version": {"type": "string"}, 299 | "language_info": {"type": "object"}, 300 | "banner": {"type": "string"}, 301 | "debugger": {"type": "boolean"}, 302 | "help_links": { 303 | "type": "array", 304 | "items": { 305 | "type": "object", 306 | "properties": {"text": {"type": "string"}, "url": {"type": "string"}}, 307 | }, 308 | }, 309 | }, 310 | "required": ["status", "protocol_version", "implementation", "language_info", "banner"], 311 | } 312 | 313 | schema_fragments["shutdown_request"] = { 314 | "properties": { 315 | "restart": {"type": "boolean"}, 316 | } 317 | } 318 | 319 | schema_fragments["shutdown_reply"] = { 320 | "properties": { 321 | # statuses 'error' and 'abort' change the structure, so check separately 322 | "status": {"const": "ok"}, 323 | "restart": {"type": "boolean"}, 324 | } 325 | } 326 | 327 | schema_fragments["interrupt_request"] = {"properties": {}} 328 | schema_fragments["interrupt_reply"] = { 329 | "properties": { 330 | # statuses 'error' and 'abort' change the structure, so check separately 331 | "status": {"const": "ok"}, 332 | } 333 | } 334 | 335 | # IOPub messages ---------------------------------------------- 336 | 337 | mime_data = { 338 | "type": "object", 339 | "patternProperties": {r"^[\w\-\+\.]+/[\w\-\+\.]+$": {}}, 340 | "additionalProperties": False, 341 | } 342 | 343 | schema_fragments["stream"] = { 344 | "properties": { 345 | "name": {"enum": ["stdout", "stderr"]}, 346 | "text": {"type": "string"}, 347 | } 348 | } 349 | 350 | schema_fragments["display_data"] = { 351 | "properties": { 352 | "data": mime_data, 353 | "metadata": {"type": "object"}, 354 | "transient": {"type": "object"}, 355 | }, 356 | "required": ["data", "metadata"], 357 | } 358 | 359 | schema_fragments["update_display_data"] = { 360 | "properties": { 361 | "data": mime_data, 362 | "metadata": {"type": "object"}, 363 | "transient": {"type": "object"}, 364 | } 365 | } 366 | 367 | schema_fragments["execute_result"] = { 368 | "properties": { 369 | "execution_count": {"type": "number"}, 370 | "data": mime_data, 371 | "metadata": {"type": "object"}, 372 | "transient": {"type": "object"}, 373 | }, 374 | "required": ["execution_count", "data", "metadata"], 375 | } 376 | 377 | schema_fragments["clear_output"] = { 378 | "properties": { 379 | "wait": {"type": "boolean"}, 380 | } 381 | } 382 | 383 | schema_fragments["execute_input"] = { 384 | "properties": { 385 | "code": {"type": "string"}, 386 | "execution_count": {"type": "number"}, 387 | } 388 | } 389 | 390 | schema_fragments["error"] = { 391 | "properties": { 392 | "ename": {"type": "string"}, 393 | "evalue": {"type": "string"}, 394 | "traceback": {"type": "array", "items": {"type": "string"}}, 395 | } 396 | } 397 | 398 | schema_fragments["status"] = { 399 | "properties": { 400 | "execution_state": {"enum": ["busy", "idle", "starting"]}, 401 | } 402 | } 403 | 404 | # Stdin messages --------------------------------------------- 405 | 406 | schema_fragments["input_request"] = { 407 | "properties": { 408 | "prompt": {"type": "string"}, 409 | "password": {"type": "number"}, 410 | } 411 | } 412 | 413 | schema_fragments["input_reply"] = { 414 | "properties": { 415 | "value": {"type": "string"}, 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /tests/pexpect_zsh_test.py: -------------------------------------------------------------------------------- 1 | import io 2 | from unittest import TestCase, main 3 | 4 | import pexpect 5 | 6 | class Pexpect_ZSH_Test(TestCase): 7 | 8 | def test_echo(self): 9 | p = pexpect.spawn('zsh +o INTERACTIVE') 10 | p.logfile = io.BytesIO() 11 | p.sendline("echo 1") 12 | p.expect("1") # timeouts if something is wrong 13 | self.assertEqual(p.logfile.getvalue(), b"echo 1\necho 1\r\n1\r\n") 14 | 15 | if __name__ == '__main__': 16 | main() 17 | -------------------------------------------------------------------------------- /tests/zsh_kernel_test.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import queue 3 | import unittest 4 | 5 | import jupyter_client 6 | import jupyter_client.utils 7 | 8 | from . import msgspec_v5 9 | 10 | TIMEOUT = 1 11 | 12 | class zsh_kernel_tests(unittest.TestCase): 13 | km: jupyter_client.KernelManager 14 | kc: jupyter_client.BlockingKernelClient 15 | 16 | @classmethod 17 | def setUpClass(cls): 18 | cls.km, cls.kc = jupyter_client.manager.start_new_kernel(kernel_name = "zsh") 19 | 20 | @classmethod 21 | def tearDownClass(cls): 22 | cls.kc.stop_channels() 23 | cls.km.shutdown_kernel() 24 | 25 | def setUp(self): 26 | print() 27 | 28 | @staticmethod 29 | def run_sync(func): 30 | if inspect.iscoroutinefunction(func): 31 | return jupyter_client.utils.run_sync(func) 32 | return func 33 | 34 | def flush_channels(self): 35 | for channel in (self.kc.shell_channel, self.kc.iopub_channel): 36 | while True: 37 | try: 38 | msg = self.run_sync(channel.get_msg)(timeout = 0.1) 39 | except TypeError: 40 | msg = channel.get_msg(timeout = 0.1) 41 | except queue.Empty: 42 | break 43 | else: 44 | msgspec_v5.validate_message(msg) 45 | 46 | def execute(self, code: str, timeout = TIMEOUT, silent = False, 47 | store_history = True, stop_on_error = True): 48 | msg_id = self.kc.execute(code = code, silent = silent, 49 | store_history = store_history, stop_on_error = stop_on_error) 50 | reply = self.get_non_kernel_info_reply(timeout = timeout) 51 | msgspec_v5.validate_message(reply, "execute_reply", msg_id) 52 | busy_msg = self.run_sync(self.kc.iopub_channel.get_msg)(timeout = 1) 53 | msgspec_v5.validate_message(busy_msg, "status", msg_id) 54 | self.assertEqual(busy_msg["content"]["execution_state"], "busy") 55 | output_msgs = [] 56 | while True: 57 | msg = self.run_sync(self.kc.iopub_channel.get_msg)(timeout = 0.1) 58 | msgspec_v5.validate_message(msg, msg["msg_type"], msg_id) 59 | if msg["msg_type"] == "status": 60 | self.assertEqual(msg["content"]["execution_state"], "idle") 61 | break 62 | elif msg["msg_type"] == "execute_input": 63 | self.assertEqual(msg["content"]["code"], code) 64 | continue 65 | output_msgs.append(msg) 66 | return reply, output_msgs 67 | 68 | def get_non_kernel_info_reply(self, timeout = None): 69 | while True: 70 | reply = self.kc.get_shell_msg(timeout = timeout) 71 | if reply["header"]["msg_type"] != "kernel_info_reply": 72 | return reply 73 | 74 | def check_is_complete(self, sample, status): 75 | msg_id = self.kc.is_complete(sample) 76 | reply = self.get_non_kernel_info_reply() 77 | msgspec_v5.validate_message(reply, "is_complete_reply", msg_id) 78 | if reply["content"]["status"] != status: 79 | msg = "for code sample\n {!r}\nexpected {!r}, got {!r}." 80 | raise AssertionError(msg.format(sample, status, reply["content"]["status"])) 81 | 82 | def get_history(self, execute_first, timeout = TIMEOUT, **histargs): 83 | self.flush_channels() 84 | for code in execute_first: 85 | reply, output_msgs = self.execute(code) 86 | self.flush_channels() 87 | msg_id = self.kc.history(**histargs) 88 | reply = self.get_non_kernel_info_reply(timeout = timeout) 89 | msgspec_v5.validate_message(reply, "history_reply", msg_id) 90 | return reply 91 | 92 | def test_kernel_info(self): 93 | self.flush_channels() 94 | msg_id = self.kc.kernel_info() 95 | reply = self.kc.get_shell_msg(timeout = TIMEOUT) 96 | msgspec_v5.validate_message(reply, "kernel_info_reply", msg_id) 97 | self.assertEqual(reply["content"]["language_info"]["name"], "zsh") 98 | self.assertEqual(reply["content"]["language_info"]["file_extension"], ".zsh") 99 | 100 | def test_hello_world_stdout(self): 101 | self.flush_channels() 102 | reply, output_msgs = self.execute(code = "<<< 'hello, world'") 103 | self.assertEqual(reply["content"]["status"], "ok") 104 | self.assertGreaterEqual(len(output_msgs), 1) 105 | for msg in output_msgs: 106 | if (msg["msg_type"] == "stream") and (msg["content"]["name"] == "stdout"): 107 | self.assertIn("hello, world", msg["content"]["text"]) 108 | break 109 | else: 110 | self.assertTrue(False, "expected one output message of type 'stream' and 'content.name'='stdout'") 111 | 112 | def test_hello_world_stderr(self): 113 | self.flush_channels() 114 | reply, output_msgs = self.execute(code = ">&2 print 'hello, world'") 115 | self.assertEqual(reply["content"]["status"], "ok") 116 | self.assertGreaterEqual(len(output_msgs), 1) 117 | for msg in output_msgs: 118 | if (msg["msg_type"] == "stream") and (msg["content"]["name"] == "stdout"): 119 | self.assertIn("hello, world", msg["content"]["text"]) 120 | break 121 | else: 122 | self.assertTrue(False, "expected one output message of type 'stream' and 'content.name'='stdout'") 123 | 124 | def test_completion(self): 125 | samples = [ 126 | { 127 | "text": "prin", 128 | "matches": [ 129 | "printafm", 130 | "printf", 131 | "printenv", 132 | "printf", 133 | "print", 134 | ], 135 | } 136 | ] 137 | for sample in samples: 138 | text = sample["text"] 139 | with self.subTest(text = text): 140 | msg_id = self.kc.complete(text) 141 | reply = self.get_non_kernel_info_reply(timeout = TIMEOUT) 142 | msgspec_v5.validate_message(reply, "complete_reply", msg_id) 143 | if "matches" in sample: 144 | self.assertEqual(reply["content"]["matches"], sample["matches"]) 145 | 146 | def test_is_complete(self): 147 | self.flush_channels() 148 | with self.subTest(status = "complete"): 149 | for sample in [ 150 | 'print complete code sample; echo "100% guarantee"', 151 | '123' 152 | ]: 153 | self.check_is_complete(sample, "complete") 154 | with self.subTest(status = "incomplete"): 155 | for sample in [ 156 | "1()", 157 | "echo $((2 + 2)", 158 | # "something with open single quote '", # fixme: this fails. need to handle special chars 159 | ]: 160 | self.check_is_complete(sample, "incomplete") 161 | -------------------------------------------------------------------------------- /tools/Dockerfile: -------------------------------------------------------------------------------- 1 | # minimal-notebook version from Feb 8, 2021 2 | FROM jupyter/minimal-notebook:016833b15ceb 3 | 4 | USER root 5 | 6 | RUN apt update 7 | RUN apt upgrade --quiet --assume-yes --no-install-recommends 8 | RUN apt install --quiet --assume-yes --no-install-recommends zsh 9 | # uncomment to test oh-my-zsh 10 | # RUN sh -c "$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" 11 | 12 | RUN python3 -m pip install zsh_jupyter_kernel 13 | RUN python3 -m zsh_jupyter_kernel.install --sys-prefix 14 | 15 | RUN apt install --quiet --assume-yes --no-install-recommends figlet 16 | 17 | ENV JUPYTER_RUNTIME_DIR "/tmp" 18 | CMD [ "jupyter", "notebook", "--allow-root" ] 19 | -------------------------------------------------------------------------------- /tools/docker.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | v=$(<./src/zsh_jupyter_kernel/version) 4 | t=zsh-jupyter-kernel:$v 5 | 6 | case $1 in 7 | build) 8 | docker build --tag $t . 9 | ;; 10 | run) 11 | docker run --rm --interactive --tty \ 12 | --publish=8889:8888 \ 13 | --volume=$PWD/test:/home/jovyan \ 14 | --name=zsh-jupyter-kernel \ 15 | $t 16 | ;; 17 | esac 18 | -------------------------------------------------------------------------------- /tools/install-pip.sh: -------------------------------------------------------------------------------- 1 | # deactivate conda environment if any 2 | conda deactivate 3 | 4 | # create virtual environment 5 | python3 -m venv venv 6 | 7 | # activate it 8 | . ./venv/bin/activate 9 | 10 | # install jupyter dependencies and notebook 11 | pip install jupyter_protocol jupyter_kernel_test jupyter-console notebook jupyterlab 12 | 13 | # install zsh kernel as editable package 14 | pip install -e ./dist/pypi/setuptools 15 | 16 | # install the kernel itself 17 | python -m zsh_jupyter_kernel.install --sys-prefix 18 | 19 | # quick test 20 | jupyter console --kernel=zsh 21 | -------------------------------------------------------------------------------- /tools/install-pipenv.sh: -------------------------------------------------------------------------------- 1 | # deactivate conda environments if any 2 | conda deactivate 3 | 4 | # remove any pipenvs 5 | pipenv --rm 6 | 7 | # check pipenv 8 | pipenv --version; which pipenv 9 | 10 | # create environment 11 | pipenv --python 3.8 12 | 13 | # install dependencies and kernel 14 | pipenv update 15 | pipenv run python -m zsh_jupyter_kernel.install --name zsh --display-name "Z shell (dev)" --sys-prefix 16 | 17 | # quick test 18 | pipenv run jupyter console --kernel=zsh 19 | 20 | # notes: 21 | # - `pipenv uninstall --all` removes some packages which are necessary for further functioning, so don't clean environment such way 22 | -------------------------------------------------------------------------------- /tools/jupyter-rm-zsh.zsh: -------------------------------------------------------------------------------- 1 | jupyter kernelspec list --json | jq -r '.kernelspecs | keys[]' | grep zsh | xargs jupyter kernelspec remove -y 2 | -------------------------------------------------------------------------------- /tools/run-lab.sh: -------------------------------------------------------------------------------- 1 | pipenv run jupyter lab 2 | -------------------------------------------------------------------------------- /tools/tag-git.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | version=`cat zsh_jupyter_kernel/version.txt` 3 | git tag -a $version -m $version 4 | git push --follow-tags 5 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | 'config', 3 | 'kernel', 4 | ] 5 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/__main__.py: -------------------------------------------------------------------------------- 1 | from ipykernel.kernelapp import launch_new_instance 2 | 3 | from .kernel import ZshKernel 4 | 5 | launch_new_instance(kernel_class = ZshKernel) 6 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/banner.txt: -------------------------------------------------------------------------------- 1 | _____ _ _ _ 2 | |__ / ___| |__ ___| | | 3 | / / / __| '_ \ / _ \ | | 4 | / /_ \__ \ | | | __/ | | 5 | /____| |___/_| |_|\___|_|_| 6 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/capture.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | # original source: https://github.com/Valodim/zsh-capture-completion 4 | 5 | zmodload zsh/zpty || { echo 'error: missing module zsh/zpty' >&2; exit 1 } 6 | 7 | # spawn shell 8 | zpty z zsh -f -i 9 | 10 | # line buffer for pty output 11 | local line 12 | 13 | setopt rcquotes 14 | () { 15 | zpty -w z source $1 16 | repeat 4; do 17 | zpty -r z line 18 | [[ $line == ok* ]] && return 19 | done 20 | echo 'error initializing.' >&2 21 | exit 2 22 | } =( <<< ' 23 | # no prompt! 24 | PROMPT= 25 | 26 | # load completion system 27 | autoload compinit 28 | compinit -d ~/.zcompdump_capture 29 | 30 | # never run a command 31 | bindkey ''^M'' undefined 32 | bindkey ''^J'' undefined 33 | bindkey ''^I'' complete-word 34 | 35 | # send a line with null-byte at the end before and after completions are output 36 | null-line () { 37 | echo -E - $''\0'' 38 | } 39 | compprefuncs=( null-line ) 40 | comppostfuncs=( null-line exit ) 41 | 42 | # never group stuff! 43 | zstyle '':completion:*'' list-grouped false 44 | # don''t insert tab when attempting completion on empty line 45 | zstyle '':completion:*'' insert-tab false 46 | # no list separator, this saves some stripping later on 47 | zstyle '':completion:*'' list-separator '''' 48 | 49 | # we use zparseopts 50 | zmodload zsh/zutil 51 | 52 | # override compadd (this our hook) 53 | compadd () { 54 | 55 | # check if any of -O, -A or -D are given 56 | if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then 57 | # if that is the case, just delegate and leave 58 | builtin compadd "$@" 59 | return $? 60 | fi 61 | 62 | # ok, this concerns us! 63 | # echo -E - got this: "$@" 64 | 65 | # be careful with namespacing here, we don''t want to mess with stuff that 66 | # should be passed to compadd! 67 | typeset -a __hits __dscr __tmp 68 | 69 | # do we have a description parameter? 70 | # note we don''t use zparseopts here because of combined option parameters 71 | # with arguments like -default- confuse it. 72 | if (( $@[(I)-d] )); then # kind of a hack, $+@[(r)-d] doesn''t work because of line noise overload 73 | # next param after -d 74 | __tmp=${@[$[${@[(i)-d]}+1]]} 75 | # description can be given as an array parameter name, or inline () array 76 | if [[ $__tmp == \(* ]]; then 77 | eval "__dscr=$__tmp" 78 | else 79 | __dscr=( "${(@P)__tmp}" ) 80 | fi 81 | fi 82 | 83 | # capture completions by injecting -A parameter into the compadd call. 84 | # this takes care of matching for us. 85 | builtin compadd -A __hits -D __dscr "$@" 86 | 87 | # JESUS CHRIST IT TOOK ME FOREVER TO FIGURE OUT THIS OPTION WAS SET AND WAS MESSING WITH MY SHIT HERE 88 | setopt localoptions norcexpandparam extendedglob 89 | 90 | # extract prefixes and suffixes from compadd call. we can''t do zsh''s cool 91 | # -r remove-func magic, but it''s better than nothing. 92 | typeset -A apre hpre hsuf asuf 93 | zparseopts -E P:=apre p:=hpre S:=asuf s:=hsuf 94 | 95 | # append / to directories? we are only emulating -f in a half-assed way 96 | # here, but it''s better than nothing. 97 | integer dirsuf=0 98 | # don''t be fooled by -default- >.> 99 | if [[ -z $hsuf && "${${@//-default-/}% -# *}" == *-[[:alnum:]]#f* ]]; then 100 | dirsuf=1 101 | fi 102 | 103 | # just drop 104 | [[ -n $__hits ]] || return 105 | 106 | # this is the point where we have all matches in $__hits and all 107 | # descriptions in $__dscr! 108 | 109 | # display all matches 110 | local dsuf dscr 111 | for i in {1..$#__hits}; do 112 | 113 | # add a dir suffix? 114 | (( dirsuf )) && [[ -d $__hits[$i] ]] && dsuf=/ || dsuf= 115 | # description to be displayed afterwards 116 | (( $#__dscr >= $i )) && dscr=" -- ${${__dscr[$i]}##$__hits[$i] #}" || dscr= 117 | 118 | echo -E - $IPREFIX$apre$hpre$__hits[$i]$dsuf$hsuf$asuf$dscr 119 | 120 | done 121 | 122 | } 123 | 124 | # signal success! 125 | echo ok') 126 | 127 | zpty -w z "$*"$'\t' 128 | 129 | integer tog=0 130 | # read from the pty, and parse linewise 131 | while zpty -r z; do :; done | while IFS= read -r line; do 132 | if [[ $line == *$'\0\r' ]]; then 133 | (( tog++ )) && return 0 || continue 134 | fi 135 | # display between toggles 136 | (( tog )) && echo -E - $line 137 | done 138 | 139 | return 2 140 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/config.py: -------------------------------------------------------------------------------- 1 | __all__ = ['config'] 2 | 3 | import os 4 | from pathlib import Path 5 | from typing import Any 6 | 7 | config: dict[str, Any] = {} 8 | 9 | path = Path(__file__) 10 | 11 | version = path.with_name('version.txt').read_text() 12 | 13 | logging_enabled = os.environ.get("ZSH_JUPYTER_KERNEL_LOGGING_ENABLED", "0") == "1" 14 | config["logging_enabled"] = logging_enabled 15 | logging_level = os.environ.get("ZSH_JUPYTER_KERNEL_LOGGING_LEVEL", "INFO") 16 | config["logging_level"] = logging_level 17 | logging_dir_path = Path(os.environ.get("ZSH_JUPYTER_KERNEL_LOGGING_PATH", path.parent / "../logs")) 18 | config["logging_dir_path"] = str(logging_dir_path) 19 | if logging_enabled: 20 | logging_dir_path.mkdir(parents = True, exist_ok = True) 21 | logging_file_path = logging_dir_path / "kernel.log" 22 | config["logging_file_path"] = str(logging_file_path) 23 | config["logging_formatter"] = "%(asctime)s | %(name)-10s | %(levelname)-6s | %(message)s" 24 | 25 | config["pexpect"] = { 26 | "encoding": "utf-8", 27 | "codec_errors": "replace", # [codecs] 28 | "timeout": None, # [pexpect-spawn-timeout] 29 | "logging_file_path": str(logging_dir_path / "pexpect.log"), 30 | } 31 | 32 | config["zsh"] = { 33 | "init_cmds": [ 34 | "autoload -Uz add-zsh-hook", 35 | "add-zsh-hook -D precmd \*", 36 | "add-zsh-hook -D preexec \*", 37 | # [zsh-hooks] 38 | "precmd() {}", 39 | "preexec() {}", 40 | # [zsh-functions] 41 | ], 42 | "config_cmds": [ 43 | "unset zle_bracketed_paste", # [zsh-bracketed-paste] 44 | "zle_highlight=(none)", # https://linux.die.net/man/1/zshzle 45 | ], 46 | } 47 | 48 | config["kernel"] = { 49 | "code_completion": {"cmd": str(path.with_name("capture.zsh")) + " {}"}, 50 | "info": { 51 | "protocol_version": "5.3", 52 | "implementation": "ZshKernel", 53 | "implementation_version": version, 54 | "language_info": { 55 | "name": "zsh", 56 | "version": "5.7.1", 57 | "mimetype": "text/x-zsh", 58 | "file_extension": ".zsh", 59 | "pygments_lexer": "shell", 60 | "codemirror_mode": "shell", 61 | }, 62 | "banner": path.with_name("banner.txt").read_text(), 63 | }, 64 | } 65 | 66 | if __name__ == "__main__": 67 | import json 68 | print(json.dumps(config, indent = 4)) 69 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/fun.py: -------------------------------------------------------------------------------- 1 | def find_word_at_pos(text : str, pos : int) -> str: 2 | cw = "" 3 | p = 0 4 | ic = '\n\t"' + r" |;,!@#$()<>/\'`~{}[]=+&^" 5 | 6 | while p < len(text) and p < pos: 7 | c = text[p] 8 | if c in ic: 9 | cw = "" 10 | else: 11 | cw += c 12 | p += 1 13 | 14 | while p < len(text): 15 | c = text[p] 16 | if c in ic: 17 | break 18 | else: 19 | cw += c 20 | p += 1 21 | 22 | return cw 23 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/install.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | from pathlib import Path 5 | import shutil 6 | import sys 7 | import traceback 8 | from tempfile import TemporaryDirectory 9 | from typing import Literal 10 | 11 | import jupyter_client.kernelspec 12 | 13 | def main(argv = None): 14 | args = parse_args(argv) 15 | if args.prefix: 16 | install(args.name, args.display_name, custom_path_prefix = args.prefix) 17 | elif args.sys_prefix: 18 | install(args.name, args.display_name, path_prefix = 'sys') 19 | elif args.user: 20 | install(args.name, args.display_name, path_prefix = 'user') 21 | else: 22 | install(args.name, args.display_name) 23 | 24 | def install( 25 | kernel_name: str = "zsh", 26 | display_name: str = "zsh", 27 | path_prefix: Literal['user', 'sys', 'default'] = 'default', 28 | custom_path_prefix: str = None, 29 | ): 30 | try: 31 | with TemporaryDirectory() as tempd: 32 | install_(kernel_name, display_name, path_prefix, custom_path_prefix, tempd) 33 | except Exception as e: 34 | traceback.print_exception(e) 35 | print() 36 | print("sorry, an unhandled error occured.") 37 | 38 | def install_(kernel_name, display_name, path_prefix, custom_path_prefix, tempd): 39 | os.chmod(tempd, 0o755) # starts off as 700, not user readable 40 | # [kernel-specs] 41 | with open(Path(tempd) / 'kernel.json', 'w') as f: 42 | d = { 43 | "argv": [sys.executable, "-m", 'zsh_jupyter_kernel', "-f", "{connection_file}"], 44 | "display_name": display_name, 45 | "language": "zsh", 46 | "interrupt_mode": "signal", 47 | } 48 | # noinspection PyTypeChecker 49 | json.dump(d, f, indent = 4) 50 | # logos 51 | for logof in ('logo-32x32.png', 'logo-64x64.png'): 52 | src = Path(__file__).with_name(logof) 53 | dst = Path(tempd) / logof 54 | shutil.copyfile(src, dst) 55 | # 56 | user = path_prefix == 'user' 57 | prefix = None 58 | if custom_path_prefix is not None: 59 | prefix = custom_path_prefix 60 | elif path_prefix == 'sys': 61 | prefix = sys.prefix 62 | try: 63 | jupyter_client.kernelspec.install_kernel_spec( 64 | source_dir = tempd, 65 | kernel_name = kernel_name, 66 | user = user and not (custom_path_prefix is not None), 67 | prefix = prefix, 68 | ) 69 | except PermissionError as e: 70 | print(e) 71 | print('sorry, you do not have appropriate permissions to install kernel in the specified location.\n' 72 | 'if you want to install in the default system-wide location, use elevated priviliges or login' 73 | 'as an administrator.\n' 74 | 'otherwise try installing in a current python environment location using --sys-prefix.\n' 75 | 'use --help to read more.') 76 | else: 77 | spec = jupyter_client.kernelspec.get_kernel_spec(kernel_name = kernel_name) 78 | print( 79 | f"installed Z shell jupyter kernel spec in {spec.resource_dir}:\n" 80 | f"""{json.dumps(dict( 81 | argv = spec.argv, 82 | env = spec.env, 83 | display_name = spec.display_name, 84 | language = spec.language, 85 | interrupt_mode = spec.interrupt_mode, 86 | metadata = spec.metadata, 87 | ), indent = 4)}""" 88 | ) 89 | 90 | def install_logos(tempd): 91 | for logof in ('logo-32x32.png', 'logo-64x64.png'): 92 | src = Path(__file__).with_name(logof) 93 | dst = Path(tempd) / logof 94 | shutil.copyfile(src, dst) 95 | 96 | def is_root() -> bool: 97 | try: 98 | return os.geteuid() == 0 99 | except AttributeError: 100 | return False # assume not an admin on non-Unix platforms 101 | 102 | class ArgumentFormatter( 103 | argparse.ArgumentDefaultsHelpFormatter, 104 | argparse.RawDescriptionHelpFormatter): 105 | pass 106 | 107 | def parse_args(argv) -> argparse.Namespace: 108 | ksm = jupyter_client.kernelspec.KernelSpecManager() 109 | name = "${NAME}" 110 | # noinspection PyProtectedMember 111 | user_dir = ksm._get_destination_dir(name, user = True) 112 | # noinspection PyProtectedMember 113 | sys_dir = ksm._get_destination_dir(name, prefix = sys.prefix) 114 | # noinspection PyProtectedMember 115 | root_dir = ksm._get_destination_dir(name) 116 | ap = argparse.ArgumentParser( 117 | description = "install zsh jupyter kernel", 118 | epilog = f"the kernel will be installed in one of the locations:\n" 119 | f" by default: {root_dir}\n" 120 | f" using --sys-prefix: {sys_dir}\n" 121 | f" using --user: {user_dir}\n", 122 | formatter_class = ArgumentFormatter, 123 | ) 124 | ap.add_argument( 125 | '--name', 126 | default = 'zsh', 127 | help = "directory name in the kernelspec repository. use this to specify a unique location for the kernel" 128 | " if you use more than one version, otherwise just skip it and use the default 'zsh' value." 129 | " since kernelspecs show up in urls and other places, a kernelspec is required to have a simple name," 130 | " only containing ascii letters, ascii numbers, and the simple separators:" 131 | " - hyphen, . period, _ underscore.", 132 | ) 133 | ap.add_argument( 134 | '--display-name', 135 | default = 'Z shell', 136 | help = "the kernel’s name as it should be displayed in the ui. unlike the --name used in the api," 137 | " this can contain arbitrary unicode characters.", 138 | ) 139 | ap.add_argument( 140 | '--user', 141 | action = 'store_true', 142 | help = "install to the per-user kernels registry. default if not root. ignored if --prefix is specified", 143 | ) 144 | ap.add_argument( 145 | '--sys-prefix', 146 | action = 'store_true', 147 | help = "install to sys.prefix (e.g. a virtualenv, pipenv or conda env)", 148 | ) 149 | ap.add_argument( 150 | '--prefix', 151 | help = "install to the given prefix.", 152 | ) 153 | return ap.parse_args(argv) 154 | 155 | if __name__ == '__main__': 156 | main(sys.argv[1:]) 157 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/kernel.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import logging.handlers 4 | import os 5 | import re 6 | from collections import OrderedDict as odict 7 | from typing import IO 8 | 9 | import pexpect 10 | from ipykernel.kernelbase import Kernel 11 | 12 | from .config import config 13 | from .fun import find_word_at_pos 14 | 15 | class ZshKernel(Kernel): 16 | implementation = config["kernel"]["info"]["implementation"] 17 | implementation_version = config["kernel"]["info"]["implementation_version"] 18 | banner = config["kernel"]["info"]["banner"] 19 | language_info = config["kernel"]["info"]["language_info"] 20 | 21 | protocol_version = config["kernel"]["info"]["protocol_version"] 22 | 23 | p: pexpect.spawn # [spawn] 24 | 25 | ps = odict([ 26 | ("PS1", "PEXPECT_PS1 > "), 27 | ("PS2", "PEXPECT_PS2 + "), 28 | ("PS3", "PEXPECT_PS3 : "), 29 | ]) 30 | ps_re = odict([ 31 | ("PS1", "^PEXPECT_PS1 > "), 32 | ("PS2", r"^PEXPECT_PS2 \+ "), 33 | ("PS3", "^PEXPECT_PS3 : "), 34 | ]) 35 | 36 | pexpect_logfile: IO = None 37 | 38 | log_enabled: bool 39 | 40 | def _init_log_(self, **kwargs): 41 | self.log_enabled = config["logging_enabled"] 42 | if self.log_enabled: 43 | handler = logging.handlers.WatchedFileHandler(config["logging_file_path"]) 44 | formatter = logging.Formatter(config["logging_formatter"]) 45 | handler.setFormatter(formatter) 46 | self.log.setLevel(config["logging_level"]) 47 | self.log.addHandler(handler) 48 | 49 | def _init_spawn_(self, **kwargs): 50 | args = [ 51 | "-o", "INTERACTIVE", # just to make sure 52 | "-o", "NO_ZLE", # no need for zsh line editor 53 | "-o", "NO_BEEP", 54 | "-o", "TRANSIENT_RPROMPT", 55 | "-o", "NO_PROMPT_CR", 56 | "-o", "INTERACTIVE_COMMENTS", 57 | ] # [zsh-options] 58 | if not kwargs.get("rcs", False): 59 | args.extend(["-o", "NO_RCS"]) 60 | if self.log_enabled: 61 | self.pexpect_logfile = open(config["pexpect"]["logging_file_path"], "a") 62 | self.p = pexpect.spawn( 63 | "zsh", 64 | args, 65 | echo = False, 66 | encoding = config["pexpect"]["encoding"], 67 | codec_errors = config["pexpect"]["codec_errors"], 68 | timeout = config["pexpect"]["timeout"], 69 | logfile = self.pexpect_logfile, 70 | ) 71 | 72 | def _init_zsh_(self, **kwargs): 73 | init_cmds = [ 74 | *config["zsh"]["init_cmds"], 75 | *map(lambda kv: "{}='{}'".format(*kv), self.ps.items()), 76 | ] 77 | self.p.sendline("; ".join(init_cmds)) 78 | self.p.expect_exact(self.ps["PS1"]) 79 | config_cmds = [ 80 | *config["zsh"]["config_cmds"], 81 | ] 82 | self.p.sendline("; ".join(config_cmds)) 83 | self.p.expect_exact(self.ps["PS1"]) 84 | 85 | def __init__(self, **kwargs): 86 | super().__init__(**kwargs) 87 | self._init_log_() 88 | if self.log_enabled: 89 | self.log.debug("initializing %s", json.dumps(config, indent = 4)) 90 | self._init_spawn_() 91 | self._init_zsh_(**kwargs) 92 | # self.p.sendline("tty") 93 | # self.p.expect_exact(self.ps['PS1']) 94 | if self.log_enabled: 95 | self.log.debug("initialized") 96 | if self.log_enabled: 97 | self.log.debug("kwargs:" + str(kwargs)) 98 | 99 | def __del__(self): 100 | try: 101 | self.pexpect_logfile.close() 102 | except AttributeError: 103 | pass 104 | 105 | def kernel_info_request(self, stream, ident, parent): 106 | content = {"status": "ok"} 107 | content.update(self.kernel_info) 108 | content.update({"protocol_version": self.protocol_version}) 109 | msg = self.session.send(stream, "kernel_info_reply", content, parent, ident) 110 | if self.log_enabled: 111 | self.log.debug("info request sent: %s", msg) 112 | 113 | def _execute_line(self, line, silent): 114 | if self.log_enabled: 115 | self.log.debug("code: %s", line) 116 | self.p.sendline(line) 117 | actual = self.p.expect(list(self.ps_re.values()) + [os.linesep]) 118 | if actual == 0: 119 | if self.log_enabled: 120 | self.log.debug(f"got PS1. output: {self.p.before}") 121 | if not silent: 122 | if len(self.p.before) != 0: 123 | self.send_response(self.iopub_socket, 124 | "stream", {"name": "stdout", "text": self.p.before}) 125 | else: 126 | while actual == 3: 127 | if self.log_enabled: 128 | self.log.debug(f"got linesep. output: {self.p.before}") 129 | if not silent: 130 | self.send_response(self.iopub_socket, 131 | "stream", {"name": "stdout", "text": self.p.before + os.linesep}) 132 | actual = self.p.expect(list(self.ps_re.values()) + [os.linesep]) 133 | return actual 134 | 135 | def _execute_code(self, code, silent): 136 | actual = None 137 | for line in code.splitlines(): 138 | actual = self._execute_line(line, silent) 139 | if self.log_enabled: 140 | self.log.debug(f"executed all lines. actual: {actual}") 141 | if actual in [1, 2]: 142 | self.p.sendline() 143 | # "flushing" 144 | actual = self.p.expect(list(self.ps_re.values())) 145 | while actual != 0: 146 | actual = self.p.expect(list(self.ps_re.values()) + [re.compile(".*")]) 147 | if not silent: 148 | self.send_response(self.iopub_socket, 149 | "stream", {"name": "stdout", "text": self.p.before}) 150 | raise ValueError("Continuation or selection prompts are not handled yet") 151 | 152 | def _get_error_response(self, exc): 153 | return { 154 | "execution_count": self.execution_count, 155 | "ename": exc.__class__.__name__, 156 | "evalue": exc.__class__.__name__, 157 | "traceback": [], 158 | # 'traceback': traceback.extract_stack(exc), 159 | } 160 | 161 | def do_execute(self, code: str, silent: bool, store_history = True, 162 | user_expressions: dict = None, **kwargs): 163 | try: 164 | self._execute_code(code, silent) 165 | except KeyboardInterrupt as exc: 166 | if self.log_enabled: self.log.debug("interrupted by user") 167 | self.p.sendintr() 168 | self.p.expect_exact(self.ps["PS1"]) 169 | if not silent: 170 | self.send_response(self.iopub_socket, "stream", 171 | {"name": "stdout", "text": self.p.before}) 172 | error_response = self._get_error_response(exc) 173 | return {"status": "error", **error_response} 174 | except ValueError as exc: 175 | if self.log_enabled: self.log.exception("value error") 176 | error_response = self._get_error_response(exc) 177 | self.send_response(self.iopub_socket, "error", error_response) 178 | return {"status": "error", **error_response} 179 | except pexpect.TIMEOUT as exc: 180 | if self.log_enabled: self.log.exception("timeout") 181 | error_response = self._get_error_response(exc) 182 | self.send_response(self.iopub_socket, "error", error_response) 183 | return {"status": "error", **error_response} 184 | except pexpect.EOF as exc: 185 | if self.log_enabled: self.log.exception("end of file") 186 | error_response = self._get_error_response(exc) 187 | self.send_response(self.iopub_socket, "error", error_response) 188 | return {"status": "error", **error_response} 189 | 190 | if self.log_enabled: self.log.debug(f"success {self.execution_count}") 191 | return { 192 | "status": "ok", 193 | "execution_count": self.execution_count, 194 | "payload": [], 195 | "user_expressions": {}, 196 | } 197 | 198 | def do_is_complete(self, code: str): 199 | (_, exitstatus) = pexpect.run( 200 | "zsh -nc '{}'".format(code), 201 | withexitstatus = True, 202 | ) 203 | if exitstatus == 0: 204 | return {"status": "complete"} 205 | elif exitstatus == 1: 206 | return {"status": "incomplete"} 207 | 208 | def do_inspect(self, code: str, cursor_pos: int, detail_level: int = 0, 209 | omit_sections = ()): 210 | word = find_word_at_pos(code, cursor_pos) 211 | if self.log_enabled: 212 | self.log.debug("inspecting: %s", word) 213 | cman = f"man --pager ul {word}" 214 | res = pexpect.run(cman).decode() 215 | return { 216 | "status": "ok", 217 | "found": True, 218 | "data": {"text/plain": res}, 219 | "metadata": {}, 220 | } 221 | 222 | @staticmethod 223 | def _parse_completee(code: str, cursor_pos: int) -> tuple: 224 | context = code[:cursor_pos] 225 | match = re.search(r"\S+$", context) 226 | if not match: 227 | match = re.search(r"\w+$", context) 228 | if not match: 229 | completee = "" 230 | else: 231 | completee = match.group(0) 232 | cursor_start = cursor_pos - len(completee) 233 | cursor_end = cursor_pos 234 | return context, completee, cursor_start, cursor_end 235 | 236 | def do_complete(self, code: str, cursor_pos: int): 237 | if self.log_enabled: 238 | self.log.debug("received code to complete:\n%s", code) 239 | if self.log_enabled: 240 | self.log.debug("cursor_pos=%s", cursor_pos) 241 | (context, completee, cursor_start, cursor_end) = \ 242 | self._parse_completee(code, cursor_pos) 243 | if self.log_enabled: 244 | self.log.debug("parsed completee: %s", 245 | (context, completee, cursor_start, cursor_end)) 246 | completion_cmd = config["kernel"]["code_completion"]["cmd"].format(context) 247 | self.p.sendline(completion_cmd) 248 | self.p.expect_exact(self.ps["PS1"]) 249 | raw_completions = self.p.before.strip() 250 | if self.log_enabled: 251 | self.log.debug("got completions:\n%s", raw_completions) 252 | completions = list(filter(None, raw_completions.splitlines())) 253 | if self.log_enabled: 254 | self.log.debug("array of completions: %s", completions) 255 | matches_data = list( 256 | map(lambda x: x.split(" -- "), completions) 257 | ) # [match, description] 258 | if self.log_enabled: 259 | self.log.debug("processed matches: %s", matches_data) 260 | return { 261 | "status": "ok", 262 | "matches": [x[0] for x in matches_data], 263 | "cursor_start": cursor_start, 264 | "cursor_end": cursor_end, 265 | "metadata": {}, 266 | } 267 | 268 | def send_response(self, stream, msg_or_type, content = None, ident = None, 269 | buffers = None, track = False, header = None, metadata = None, channel = None): 270 | if self.log_enabled: 271 | self.log.debug("sending response: %s", msg_or_type) 272 | super().send_response(stream, msg_or_type, content, ident, buffers, track, 273 | header, metadata, channel) 274 | -------------------------------------------------------------------------------- /zsh_jupyter_kernel/logo-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/e591c27c791c9c6f8ba7296074fa9a316690426f/zsh_jupyter_kernel/logo-32x32.png -------------------------------------------------------------------------------- /zsh_jupyter_kernel/logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/e591c27c791c9c6f8ba7296074fa9a316690426f/zsh_jupyter_kernel/logo-64x64.png -------------------------------------------------------------------------------- /zsh_jupyter_kernel/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dahn-zk/zsh-jupyter-kernel/e591c27c791c9c6f8ba7296074fa9a316690426f/zsh_jupyter_kernel/logo.png -------------------------------------------------------------------------------- /zsh_jupyter_kernel/version.txt: -------------------------------------------------------------------------------- 1 | 3.5.1 --------------------------------------------------------------------------------