├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .tool-versions ├── CHANGELOG.md ├── CONTRIBUTE.md ├── DEVELOPMENT.md ├── LICENCE ├── README.md ├── deno.json ├── deno.lock ├── glacier-logo.png ├── gleam.toml ├── import_map.json ├── manifest.toml ├── src ├── glacier.gleam ├── glacier │ └── should.gleam ├── glacier_demo │ ├── glacier_demo_module_a.gleam │ ├── glacier_demo_module_aliasing_c.gleam │ ├── glacier_demo_module_b.gleam │ ├── glacier_demo_module_c.gleam │ ├── glacier_demo_module_d.gleam │ ├── glacier_demo_module_e.gleam │ ├── glacier_demo_module_unqualifying.gleam │ ├── glacier_demo_references_a.gleam │ ├── hello.gleam │ └── other.txt ├── glacier_ffi.erl └── glacier_ffi.mjs └── test ├── glacier_demo ├── glacier_demo_module_a_test.gleam ├── glacier_demo_module_b_test.gleam ├── glacier_demo_module_c_test.gleam └── glacier_demo_module_gleeunit_should.gleam └── glacier_test.gleam /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | # Matches multiple files with brace expansion notation 9 | # Set default charset 10 | [*] 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.{rs, gleam, erl, hrl, ex, mjs, js, ts, sh}] 19 | max_line_length = 80 20 | 21 | [*.{rs, .erl, .hrl}] 22 | indent_size = 4 23 | 24 | [Makefile] 25 | indent_style = tab 26 | indent_size = 4 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3.2.0 15 | - uses: erlef/setup-beam@v1.15.0 16 | with: 17 | otp-version: "27.3.3" 18 | gleam-version: "1.9.1" 19 | rebar3-version: "3" 20 | - run: gleam format --check src test 21 | - run: gleam deps download 22 | - run: gleam test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.ez 3 | build 4 | erl_crash.dump 5 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | deno 1.42.2 2 | erlang 27.3.3 3 | gleam 1.9.1 4 | nodejs 18.20.2 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.3.2 4 | 5 | - `get_cwd_as_binary` bugfix. 6 | 7 | ## 1.3.1 8 | 9 | - updated gleam community colour to allow more recent versions of other 10 | dependencies such as gleam_json. 11 | 12 | ## 1.3.0 13 | 14 | - updated towards glacier_gleeunit 1.3.0 which uses gleeunit 1.3.0 as a basis. 15 | 16 | ## 1.1.0 17 | 18 | - Updated upstream gleeunit dependency to 1.2.0 (glacier_gleeunit 1.2.1001). 19 | - Update simplifile dep to >- 2.0.0. 20 | 21 | ## 1.0.0 22 | 23 | - Updates glacier_gleeunit dependency to up to date version. 24 | 25 | ## 0.10.0 26 | 27 | - Updates Glacier for Gleam 1.0. 28 | 29 | ## 0.9.0 30 | 31 | - Syntax updates. 32 | 33 | ## 0.8.1 34 | 35 | - Improve readme 36 | - Hide internal modules from docs 37 | - Improve docs 38 | 39 | ## 0.8.0 40 | 41 | - Upgraded to be in line with gleam 0.27.0 42 | 43 | ## 0.7.0 - skipped 44 | 45 | ## 0.6.2 46 | 47 | - Add docs on how to run glacier with deno 48 | 49 | ## 0.6.1 50 | 51 | - Attempt to fix deno testing. 52 | 53 | ## 0.6.0 54 | 55 | - With Gleam 0.26 supporting *Deno*, so does Glacier 56 | 57 | ## 0.5.0 58 | 59 | - Added fixes and work arounds so that glacier runs with Gleam 0.26.0 60 | - Glacier might run on Deno might (or might not) run once Gleam 0.26.1 is out 61 | 62 | ## 0.4.1 63 | 64 | - Fix readme. 65 | 66 | ## 0.4.0 67 | 68 | - Remove internal verbatim copy to `gleeunit` and depend on a fork: 69 | [`glacier_gleeunit`](https://hex.pm/glacier_gleeunit). 70 | 71 | ## 0.3.1 72 | 73 | - Fix Changelog. 74 | 75 | ## 0.3.0 76 | 77 | - Fix detection of unqualified imports. 78 | - Fix detection of aliased imports. 79 | - Fix detection of renames. 80 | - Files with white spaces are now ignored. 81 | - Improved readme. 82 | - Force rebased on `lpil/gleeunit` to keep all contributor history alive. 83 | - This was required because initially `Glacier` was supposed to depend on 84 | a patched `Gleeunit`, but the idea of patching `Gleeunit` a bit was 85 | rejected by the `Gleeunit` team. While this may not be strictly necessary 86 | this honors previous contributions transparently. 87 | - This was a quick and dirty process where I saved a lot of time by _not_ 88 | fixing any merges and instead just adding them without touching them 89 | including the diff noise. As a consequence single commits if checked out 90 | might not run any more. After the dirty rebase I've commited the 91 | source code state equvialent to `0.2.7` to restore functionality. 92 | - Proper BE noun instead of an AE one for the `LICENCE` file and naming. 93 | 94 | ## 0.2.7 95 | 96 | - Fix `MaxListenersExceededWarning: Possible EventEmitter memory leak detected` (#4). 97 | - Cleanup `README.md`. 98 | 99 | ## 0.2.6 100 | 101 | - Instruct to use `gleam add glacier --dev` to add as a dev dependency, only. 102 | 103 | ## 0.2.5 104 | 105 | - Readme: Explain that `gleeunit` is optional if `glacier` is installed if `gleeunit/should` is replaced by `glacier/should`. 106 | 107 | ## 0.2.4 108 | 109 | - More compatibility changes to run `glacier` alongside `gleeunit`: 110 | Added `glacier/should` which is a renamed `gleeunit/should`. 111 | 112 | ## 0.2.3 113 | 114 | - Remove code that did nothing. 115 | 116 | ## 0.2.2 117 | 118 | - Fixed `README.md`. 119 | 120 | ## 0.2.1 121 | 122 | - Fixed `README.md`. 123 | 124 | ## 0.2.0 125 | 126 | - Added `CHANGELOG.md`. 127 | - Renamed internal gleeunit fork to gleeunit2 to avoid collisions. 128 | - JavaScript: File renames are picked up by the watcher. 129 | - Replaced `glacier.run()` with `glacier.main()` as latter did not make sense before. 130 | - Fixed `README.md`. 131 | - Fixed the logo to work across hexpm and github and shields badges. 132 | 133 | ## 0.1.0 134 | 135 | - _Ex post_ rebased on `lpil/gleeunit` 136 | 137 | ### Gleeunit v0.8 138 | 139 | - `should.be_ok` and `should.be_error` now unwrap the result argument 140 | 141 | ### Gleeunit v0.7.2 - 2022-11-19 142 | 143 | - Update for Gleam v0.25.0. 144 | 145 | ### Gleeunit v0.7.1 - 2022-11-11 146 | 147 | - Fixed a bug where project names containing numbers would not run correctly on 148 | JavaScript. 149 | 150 | ### Gleeunit v0.7.0 - 2022-09-24 151 | 152 | - Line numbers are printed on JS for assertions. 153 | 154 | ### Gleeunit v0.6.2 - 2022-07-15 155 | 156 | - Fixed a bug where assertions in JavaScript tests could fail to report an 157 | error due to them being async. 158 | 159 | ### Gleeunit v0.6.1 - 2022-01-26 160 | 161 | - Fixed a bug where failed tests on the JavaScript target would crash the `main` 162 | function. 163 | - Fixed a bug where tests on the JavaScript target could succeed regardless of 164 | return value. 165 | 166 | ### Gleeunit v0.6.0 - 2022-01-09 167 | 168 | - Added support for OTP versions below 23. 169 | - Added support for running tests on the JavaScript target. 170 | 171 | ### Gleeunit v0.5.0 - 2021-12-05 172 | 173 | - Updated for Gleam v0.18.0. 174 | - Added `gleeunit/should` module containing assertions. 175 | 176 | ### Gleeunit v0.4.0 - 2021-11-01 177 | 178 | - Slightly improved failure format. 179 | 180 | ### Gleeunit v0.3.0 - 2021-10-31 181 | 182 | - Fixed Hex package which was missing some files. 183 | 184 | ### Gleeunit v0.2.0 - 2021-10-31 185 | 186 | - `gleeunit.discover_and_run_tests` removed. 187 | - `gleeunit.main` added. 188 | 189 | ### Gleeunit v0.1.0 - 2021-10-31 190 | 191 | - Initial release. 192 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Glacier · Contribution Guide 2 | 3 | This package heavily depends on a [gleeunit](https://github.com/lpil/gleeunit) fork called [glacier_gleeunit](https://github.com/inoas/gleeunit)! 4 | 5 | ## Improving Glacier 6 | 7 | ```shell 8 | git clone https://github.com/inoas/glacier.git 9 | cd glacier 10 | ``` 11 | 12 | See all [open issues](https://github.com/inoas/glacier/issues) on GitHub if you want to help out. 13 | 14 | ## How does it work? 15 | 16 | 1. `gleam test` passes through `glacier.main()` and simply executes `gleeunit.main()` as if **gleeunit** was used directly. 17 | 2. `gleam test -- test_module_a test_module_b` passes through `glacier.main()` and executes `gleeunit.test_modules(modules_list)` where `modules_list` is `["foo", "bar"]`. The given modules are checked if they exist as either `.gleam` or `.erl` test module files and then **gleeunit** runs these test modules. 18 | 3. `gleam test -- --glacier` enters `glacier.main()` and starts a file watcher: Upon changes in module files in `./test` it just passes those through as `gleam test -- changed_test_module`(so re-saving test files executes the single test), and if a module file in `./src` got changed it parses that changed module file for any imported modules and puts the module and all chained imported modules in a distinct list of modules that should be tested. Then all test module files are read and imports of those are gathered one by one and cross matched against that list. The result is a list of test modules that need to be run, which then gets done by executing a shell call similar to `gleam test -- detected_test_module_a detected_test_module_b detected_test_module_c etc`, aka jump to step `2`. 19 | 20 | ### Demo for target Erlang 21 | 22 | ```shell 23 | # Traditional test runs 24 | gleam test --target erlang 25 | gleam test --target erlang -- test/glacier_demo/glacier_demo_module_a_test.gleam 26 | 27 | # Incremental interactive test 28 | gleam test --target erlang -- --glacier 29 | # Re-save ./src/glacier_demo/glacier_demo_module_a.gleam 30 | ``` 31 | 32 | ### Demo for target JavaScript on NodeJS 33 | 34 | ```shell 35 | # Traditional test runs 36 | gleam test --target javascript --runtime node 37 | gleam test --target javascript --runtime node -- test/glacier_demo/glacier_demo_module_a_test.gleam 38 | 39 | # Incremental interactive test 40 | gleam test --target javascript --runtime node -- --glacier 41 | # Re-save ./src/glacier_demo/glacier_demo_module_a.gleam 42 | ``` 43 | 44 | Notice: You may omit `--runtime node` as it is currently set as the default runtime for target `javascript`. 45 | 46 | ### Demo for target JavaScript on Deno 47 | 48 | ```shell 49 | # Traditional test runs 50 | gleam test --target javascript --runtime deno 51 | gleam test --target javascript --runtime deno -- test/glacier_demo/glacier_demo_module_a_test.gleam 52 | 53 | # Incremental interactive test 54 | gleam test --target javascript --runtime deno -- --glacier 55 | # Re-save ./src/glacier_demo/glacier_demo_module_a.gleam 56 | ``` 57 | 58 | #### Privileges required for Deno 59 | 60 | Do not forget to edit `gleam.toml` to add deno privileges: 61 | 62 | ```toml 63 | [javascript.deno] 64 | allow_read = ["./"] 65 | allow_net = ["deno.land"] 66 | allow_run = ["gleam"] 67 | ``` 68 | 69 | ### Caveats 70 | 71 | In line with gleam module name requirements: 72 | 73 | - `./src` and `./test` source files with white spaces will not pass detection. 74 | - Behavior towards `./src` and `./test` source files with non-alphanumeric characters is not tested, and generally unsupported. 75 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Glacier · Contribution Guide 2 | 3 | This package heavily depends on a [gleeunit](https://github.com/lpil/gleeunit) fork called [glacier_gleeunit](https://github.com/inoas/gleeunit)! 4 | 5 | ## Improving Glacier 6 | 7 | ```shell 8 | git clone https://github.com/inoas/glacier.git 9 | cd glacier 10 | ``` 11 | 12 | See all [open issues](https://github.com/inoas/glacier/issues) on GitHub if you want to help out. 13 | 14 | ## How does it work? 15 | 16 | 1. `gleam test` passes through `glacier.main()` and simply executes `gleeunit.main()` as if **gleeunit** was used directly. 17 | 2. `gleam test -- test_module_a test_module_b` passes through `glacier.main()` and executes `gleeunit.test_modules(modules_list)` where `modules_list` is `["foo", "bar"]`. The given modules are checked if they exist as either `.gleam` or `.erl` test module files and then **gleeunit** runs these test modules. 18 | 3. `gleam test -- --glacier` enters `glacier.main()` and starts a file watcher: Upon changes in module files in `./test` it just passes those through as `gleam test -- changed_test_module`(so re-saving test files executes the single test), and if a module file in `./src` got changed it parses that changed module file for any imported modules and puts the module and all chained imported modules in a distinct list of modules that should be tested. Then all test module files are read and imports of those are gathered one by one and cross matched against that list. The result is a list of test modules that need to be run, which then gets done by executing a shell call similar to `gleam test -- detected_test_module_a detected_test_module_b detected_test_module_c etc`, aka jump to step `2`. 19 | 20 | ### Demo for target Erlang 21 | 22 | ```shell 23 | # Traditional test runs 24 | gleam test --target erlang 25 | gleam test --target erlang -- test/glacier_demo/glacier_demo_module_a_test.gleam 26 | 27 | # Incremental interactive test 28 | gleam test --target erlang -- --glacier 29 | # Re-save ./src/glacier_demo/glacier_demo_module_a.gleam 30 | ``` 31 | 32 | ### Demo for target JavaScript on NodeJS 33 | 34 | ```shell 35 | # Traditional test runs 36 | gleam test --target javascript --runtime node 37 | gleam test --target javascript --runtime node -- test/glacier_demo/glacier_demo_module_a_test.gleam 38 | 39 | # Incremental interactive test 40 | gleam test --target javascript --runtime node -- --glacier 41 | # Re-save ./src/glacier_demo/glacier_demo_module_a.gleam 42 | ``` 43 | 44 | Notice: You may omit `--runtime node` as it is currently set as the default runtime for target `javascript`. 45 | 46 | ### Demo for target JavaScript on Deno 47 | 48 | ```shell 49 | # Traditional test runs 50 | gleam test --target javascript --runtime deno 51 | gleam test --target javascript --runtime deno -- test/glacier_demo/glacier_demo_module_a_test.gleam 52 | 53 | # Incremental interactive test 54 | gleam test --target javascript --runtime deno -- --glacier 55 | # Re-save ./src/glacier_demo/glacier_demo_module_a.gleam 56 | ``` 57 | 58 | #### Privileges required for Deno 59 | 60 | Do not forget to edit `gleam.toml` to add deno privileges: 61 | 62 | ```toml 63 | [javascript.deno] 64 | allow_read = ["./"] 65 | allow_net = ["deno.land"] 66 | allow_run = ["gleam"] 67 | ``` 68 | 69 | ### Caveats 70 | 71 | In line with gleam module name requirements: 72 | 73 | - `./src` and `./test` source files with white spaces will not pass detection. 74 | - Behavior towards `./src` and `./test` source files with non-alphanumeric characters is not tested, and generally unsupported. 75 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glacier · Gleam Incremental Interactive Unit Testing 2 | 3 | [![Hex Package](https://img.shields.io/hexpm/v/glacier?color=ffaff3&label=%F0%9F%93%A6)](https://hex.pm/packages/glacier) 4 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3?label=%F0%9F%93%9A)](https://hexdocs.pm/glacier/) 5 | [![Licence](https://img.shields.io/hexpm/l/glacier?color=ffaff3&label=%F0%9F%93%83)](https://github.com/inoas/glacier/blob/main/LICENCE) 6 | 7 | **Glacier** brings incremental interactive unit testing to 8 | [Gleam](https://gleam.run). It is meant as a drop-in replacement for 9 | [gleeunit](https://hexdocs.pm/gleeunit) and depends on a fork of it. 10 | 11 |
12 | Glacier Logo 13 |
Glacier: «A persistent body of dense ice that is constantly moving under its own weight.»
14 |
15 | 16 | ## Installation 17 | 18 | 1. Run: 19 | 20 | ```shell 21 | gleam remove gleeunit 22 | gleam add glacier --dev 23 | gleam clean 24 | ``` 25 | 26 | 2. Open `YOUR_GlEAM_PROJECT/test/YOUR_GLEAM_PROJECT.gleam` and replace `import gleeunit` with 27 | `import glacier` and `gleeunit.main()` with `glacier.main()` (or alias glacier as gleeunit). 28 | 3. If you want to run on **Deno 1.40+** open `gleam.toml` and add: 29 | 30 | ```toml 31 | [javascript.deno] 32 | allow_read = ["./"] 33 | allow_net = ["deno.land"] 34 | allow_run = ["gleam"] 35 | ``` 36 | 37 | ## Usage 38 | 39 | 1. Run any one of these, of which some are synonyms: 40 | - `gleam test --target erlang -- --glacier` 41 | - `gleam test --target erl -- --glacier` 42 | - `gleam test --target javascript --runtime deno -- --glacier` 43 | - `gleam test --target javascript --runtime nodejs -- --glacier` 44 | - `gleam test --target js --runtime node -- --glacier` 45 | 2. Save gleam module within your projects `src` or `test` gleam directory and 46 | watch associated tests to re-run. 47 | 48 | *Notice: On Linux [**inotify**](https://en.wikipedia.org/wiki/Inotify) must be installed.* 49 | 50 | ## Upgrading Glacier 51 | 52 | Make sure to run `gleam clean` after upgrading Glacier as a dependency. 53 | 54 | ## Improvements over gleeunit 55 | 56 | **Glacier** differs from **gleeunit** insofar, that it let's you: 57 | 58 | 1. Pass in the `glacier` flag like so: `gleam test -- --glacier`, save a gleam 59 | module and only related test modules will rerun. 60 | 2. Or pass in a specific unit test modules to rerun, for example: 61 | `gleam test -- test/my_module_test.gleam`. 62 | 3. If `gleam test` is passed without any `--`-arguments it behaves the same as 63 | **gleeunit**. 64 | 4. You can still pass in target or runtime flags, such as `--target javascript` 65 | `--runtime deno`, aka: 66 | - `gleam test --target javascript --runtime deno -- --glacier` 67 | - `gleam test --target javascript --runtime deno -- test/my_module_test.gleam`. 68 | - `gleam test --target js --runtime deno -- test/my_module_test.gleam`. 69 | 70 | *Note: `gleam test` must only be executed from the base project directory!* 71 | 72 | ### Testing against Erlang, NodeJS and Deno simultaneously 73 | 74 | Run these in 3 terminals side by side: 75 | 76 | - `gleam test -- --glacier # this implies --target erlang` 77 | - `gleam test --target js --runtime node -- --glacier` 78 | - `gleam test --target js --runtime deno -- --glacier` 79 | 80 | ## Requirements & Installation 81 | 82 | ### Requirements 83 | 84 | Requires Gleam 1.0.0 or later. 85 | 86 | #### Target specific requirements 87 | 88 | - Erlang/OTP 25 (lower may or may not run) 89 | - NodeJS 18 LTS+ (lower may or may not run) 90 | - Deno v1.30.0+ (lower does not run) 91 | 92 | #### Target Erlang 93 | 94 | Development and testing only happens on very recent stable Erlang/OTP versions, and thus may or may not run on previous versions. 95 | 96 | Depends on [fs](https://hexdocs.pm/fs/): 97 | 98 | - Linux [relies](https://github.com/synrc/fs#backends) on [**inotify**](https://en.wikipedia.org/wiki/Inotify). 99 | - Mac: Should work out of the box. 100 | - Windows: Should work out of the box. 101 | 102 | #### Target JavaScript/NodeJS/Deno 103 | 104 | Development and testing only happens on very recent NodeJS LTS versions, and thus may or may not run on previous versions. 105 | 106 | Depends on [NodeJS:`fsPromises.watch`](https://nodejs.org/api/fs.html#fspromiseswatchfilename-options) or [`Deno.watchFs`](https://deno.land/api@v1.30.0?s=Deno.watchFs): 107 | 108 | - Linux: [relies](https://nodejs.org/docs/latest-v18.x/api/fs.html#fs_caveats) on [**inotify**](https://en.wikipedia.org/wiki/Inotify). 109 | - Mac: Should work out of the box. 110 | - Windows: Should work out of the box. 111 | 112 | ## Documentation 113 | 114 | Documentation can be found at . 115 | 116 | ## Contributing to Glacier 117 | 118 | See [DEVELOPMENT.md](./DEVELOPMENT.md). 119 | 120 | ## License 121 | 122 | [Apache 2.0](./LICENCE) 123 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "importMap": "./import_map.json" 3 | } 4 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2", 3 | "remote": { 4 | "https://deno.land/std@0.173.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", 5 | "https://deno.land/std@0.173.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", 6 | "https://deno.land/std@0.173.0/async/abortable.ts": "73acfb3ed7261ce0d930dbe89e43db8d34e017b063cf0eaa7d215477bf53442e", 7 | "https://deno.land/std@0.173.0/async/deadline.ts": "b98e50d2c42399af03ad13bbb8cf59dadb9f0cd5d70648cc0c3b9202d75ab565", 8 | "https://deno.land/std@0.173.0/async/debounce.ts": "adab11d04ca38d699444ac8a9d9856b4155e8dda2afd07ce78276c01ea5a4332", 9 | "https://deno.land/std@0.173.0/async/deferred.ts": "42790112f36a75a57db4a96d33974a936deb7b04d25c6084a9fa8a49f135def8", 10 | "https://deno.land/std@0.173.0/async/delay.ts": "73aa04cec034c84fc748c7be49bb15cac3dd43a57174bfdb7a4aec22c248f0dd", 11 | "https://deno.land/std@0.173.0/async/mod.ts": "f04344fa21738e5ad6bea37a6bfffd57c617c2d372bb9f9dcfd118a1b622e576", 12 | "https://deno.land/std@0.173.0/async/mux_async_iterator.ts": "70c7f2ee4e9466161350473ad61cac0b9f115cff4c552eaa7ef9d50c4cbb4cc9", 13 | "https://deno.land/std@0.173.0/async/pool.ts": "fd082bd4aaf26445909889435a5c74334c017847842ec035739b4ae637ae8260", 14 | "https://deno.land/std@0.173.0/async/retry.ts": "5efa3ba450ac0c07a40a82e2df296287b5013755d232049efd7ea2244f15b20f", 15 | "https://deno.land/std@0.173.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757", 16 | "https://deno.land/std@0.173.0/bytes/index_of_needle.ts": "65c939607df609374c4415598fa4dad04a2f14c4d98cd15775216f0aaf597f24", 17 | "https://deno.land/std@0.173.0/collections/map_values.ts": "431b78fd770c72cc978ca7bbfa08cdc0805e69c7d2b69ad19859e093467bd46d", 18 | "https://deno.land/std@0.173.0/crypto/timing_safe_equal.ts": "8d69ab611c67fe51b6127d97fcfb4d8e7d0e1b6b4f3e0cc4ab86744c3691f965", 19 | "https://deno.land/std@0.173.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1", 20 | "https://deno.land/std@0.173.0/encoding/base64url.ts": "3f1178f6446834457b16bfde8b559c1cd3481727fe384d3385e4a9995dc2d851", 21 | "https://deno.land/std@0.173.0/flags/mod.ts": "d1cdefa18472ef69858a17df5cf7c98445ed27ac10e1460183081303b0ebc270", 22 | "https://deno.land/std@0.173.0/fmt/printf.ts": "8afc5987c9a88d345cd4de25b30126b16062635f51de9496f0270b0e03b7b10f", 23 | "https://deno.land/std@0.173.0/fs/exists.ts": "b8c8a457b71e9d7f29b9d2f87aad8dba2739cbe637e8926d6ba6e92567875f8e", 24 | "https://deno.land/std@0.173.0/node/_core.ts": "8667bf9129cdcbaac6f5c14f4def45ca954928baaa697a0ffdb0ee1556d4c644", 25 | "https://deno.land/std@0.173.0/node/_events.d.ts": "1347437fd6b084d7c9a4e16b9fe7435f00b030970086482edeeb3b179d0775af", 26 | "https://deno.land/std@0.173.0/node/_events.mjs": "d4ba4e629abe3db9f1b14659fd5c282b7da8b2b95eaf13238eee4ebb142a2448", 27 | "https://deno.land/std@0.173.0/node/_fs/_fs_access.ts": "48a722db00fd34ec567c1d03c47f6b94d07658c658eeb7d9a10c6b823ebdefbd", 28 | "https://deno.land/std@0.173.0/node/_fs/_fs_appendFile.ts": "2e5230c88804f4b5bee29efa1ba723d71a53f9b0f85d5e6372509ba12e9c00c3", 29 | "https://deno.land/std@0.173.0/node/_fs/_fs_chmod.ts": "fcba6aa4fe2d9178746b5b4ae7f42a72a971007c855988f0e26ff8f694c3c212", 30 | "https://deno.land/std@0.173.0/node/_fs/_fs_chown.ts": "6a24414772d689f8e83b6f53f134420dc25d752bd5be56cade39e92f182c9c9a", 31 | "https://deno.land/std@0.173.0/node/_fs/_fs_close.ts": "8fc5819affb69fb5708f3babce49cd673133e939cebef0665099da78a0d0be7a", 32 | "https://deno.land/std@0.173.0/node/_fs/_fs_common.ts": "21caae4ab7c07c66244446c63c50291cc553d1224d3f6a0cd7bea688c6b2a815", 33 | "https://deno.land/std@0.173.0/node/_fs/_fs_constants.ts": "22ce5f8b07fa8fd7ba37718ad85f6655954b7585d21e6d0b9d73676c16ef1b15", 34 | "https://deno.land/std@0.173.0/node/_fs/_fs_copy.ts": "9074e3a1609b9ee10ca1a2d77e94836c57190e791a0878f7e03b2f0e4e0d5dfb", 35 | "https://deno.land/std@0.173.0/node/_fs/_fs_dir.ts": "26c16ef8003772c9cd2439b448530443ea09a1508a6d808a5913576c3d11882b", 36 | "https://deno.land/std@0.173.0/node/_fs/_fs_dirent.ts": "e8c30d8059336cb6b122738c487cb46c1bcfc4c99fd6d64186f04b4e1805be34", 37 | "https://deno.land/std@0.173.0/node/_fs/_fs_exists.ts": "012e8bf6a6a9b53f9e6451db6ddabf1b883a25e6aebb8aadf8958b57efffefd0", 38 | "https://deno.land/std@0.173.0/node/_fs/_fs_fdatasync.ts": "cfe9409aed4bfe707fb497fe5be449a678b4ae454c9068f3720138ff06f7a71f", 39 | "https://deno.land/std@0.173.0/node/_fs/_fs_fstat.ts": "b15968d0f0da997960f0814e52beee35aff5e04519f007c3ac1c431829a03ac4", 40 | "https://deno.land/std@0.173.0/node/_fs/_fs_fsync.ts": "902c1d4ef9b022c61a12c5f85db3ec4e14778019697cf453822313f9eab9516b", 41 | "https://deno.land/std@0.173.0/node/_fs/_fs_ftruncate.ts": "36d76a3d6b325345ba6fbef745ec1a39d6efb4472214ede8421449296fd25711", 42 | "https://deno.land/std@0.173.0/node/_fs/_fs_futimes.ts": "75b9aaa28588d94b9d8be3c5ca4b74595cde342d644afc9c5dda1e1dcc1e604f", 43 | "https://deno.land/std@0.173.0/node/_fs/_fs_link.ts": "5cfa4f02cbedf913d90618c1bf130796bc3cdd7cd0e59cf5defb05619ae10b8a", 44 | "https://deno.land/std@0.173.0/node/_fs/_fs_lstat.ts": "da6a26b4745dbb92eda21f07992d16497a6848fe2ded6a425ade4a2418262b57", 45 | "https://deno.land/std@0.173.0/node/_fs/_fs_mkdir.ts": "94e4341f9bbc3bae9f1474e86621d48101a4a863ce51fd6b1170ef244533c494", 46 | "https://deno.land/std@0.173.0/node/_fs/_fs_mkdtemp.ts": "33658ccb449f90d69305868b718f8fe8d72a2a8e2be7136ebd69ba313fd0b4a9", 47 | "https://deno.land/std@0.173.0/node/_fs/_fs_open.ts": "9f728953c07748a54a73bb9ff0013530e33556a688a359a554d5db5b4ed30d06", 48 | "https://deno.land/std@0.173.0/node/_fs/_fs_opendir.ts": "fe65a45b92b6b970da8f3acec15920cb5669c7a19fd07afa8ebcd248ec69740b", 49 | "https://deno.land/std@0.173.0/node/_fs/_fs_read.ts": "a0223081bc460a8af5d1bb01e59a44182629bf7bff7c583031912abf20ac6b04", 50 | "https://deno.land/std@0.173.0/node/_fs/_fs_readFile.ts": "2c155de6b568a4e5d3d089e58723355fc519de2d2c9422f7dd211cda2c8f36dc", 51 | "https://deno.land/std@0.173.0/node/_fs/_fs_readdir.ts": "85f742c2ad38bebb8ba5dee72b37a966fc4b42b10382a76a60d7a2dda0a6278c", 52 | "https://deno.land/std@0.173.0/node/_fs/_fs_readlink.ts": "d5d9746c1d3c76cce0be5045dbb3bfde100406a98f1d4db8243776a2fc5619af", 53 | "https://deno.land/std@0.173.0/node/_fs/_fs_realpath.ts": "671afd8bc1b33126d56155de3827d6ec55361631eec9f4944d7f91835d897329", 54 | "https://deno.land/std@0.173.0/node/_fs/_fs_rename.ts": "2fd973c38ab5c66d806a954914a2d2b6beec55308b6da0616837ba81946bba3b", 55 | "https://deno.land/std@0.173.0/node/_fs/_fs_rm.ts": "27c01d261a3631729f9406d9dc7be263a7adf240094ba9133da511169785023b", 56 | "https://deno.land/std@0.173.0/node/_fs/_fs_rmdir.ts": "d9a35aa265670aba4a6da10cb151139bd69762ccfb88e27f266c1260c244d3ec", 57 | "https://deno.land/std@0.173.0/node/_fs/_fs_stat.ts": "bf1ca585b624f5b183ff547f02ad40b51d47247a7fd5df84f8c27376e7a7c2d5", 58 | "https://deno.land/std@0.173.0/node/_fs/_fs_symlink.ts": "89752d75dd823be7ea2c0f2ca024b14c954f7d1507360abf883245f4b700464b", 59 | "https://deno.land/std@0.173.0/node/_fs/_fs_truncate.ts": "4333d191574be1d6ab20fdee346c0dd4868e5c9c5e8ee716e3b09bf562aee698", 60 | "https://deno.land/std@0.173.0/node/_fs/_fs_unlink.ts": "6a760088a99c7465d9da3cbd67a456a6207c9764c65926ce1e0d3172aab780a2", 61 | "https://deno.land/std@0.173.0/node/_fs/_fs_utimes.ts": "c433ef58bfd20d84d0f940c17575b496dcd4706e8dc86aea777c73f667164444", 62 | "https://deno.land/std@0.173.0/node/_fs/_fs_watch.ts": "2ed05b68759e1771515efa4c6d19db9c956cfbc79a715d61e4ce8f38ac12c966", 63 | "https://deno.land/std@0.173.0/node/_fs/_fs_write.d.ts": "a405627931c1a5a3160d3f1cf028761d51b50cd632d6602cb0f98c6b39c96b23", 64 | "https://deno.land/std@0.173.0/node/_fs/_fs_write.mjs": "595abc0d7be9ef3709b62bf09972c2836b25c945f4c531a6688b910e428e1b42", 65 | "https://deno.land/std@0.173.0/node/_fs/_fs_writeFile.ts": "c65f61a167e5f80f29a88147012ade2a81233c882e51c6a07f45a153f2316a58", 66 | "https://deno.land/std@0.173.0/node/_fs/_fs_writev.d.ts": "2cd3596fe24579debe43b587d5bb5845f6f0ce3913357376eb279511ce832d15", 67 | "https://deno.land/std@0.173.0/node/_fs/_fs_writev.mjs": "54adae0d5e5148d2ee0690d04f7272dbccd1242ffbdf838778ac514c10197844", 68 | "https://deno.land/std@0.173.0/node/_global.d.ts": "2d88342f38b4083b858998e27c706725fb03a74aa14ef8d985dc18438b5188e4", 69 | "https://deno.land/std@0.173.0/node/_next_tick.ts": "9a3cf107d59b019a355d3cf32275b4c6157282e4b68ea85b46a799cb1d379305", 70 | "https://deno.land/std@0.173.0/node/_process/exiting.ts": "6e336180aaabd1192bf99ffeb0d14b689116a3dec1dfb34a2afbacd6766e98ab", 71 | "https://deno.land/std@0.173.0/node/_process/process.ts": "c96bb1f6253824c372f4866ee006dcefda02b7050d46759736e403f862d91051", 72 | "https://deno.land/std@0.173.0/node/_process/stdio.mjs": "cf17727eac8da3a665851df700b5aca6a12bacc3ebbf33e63e4b919f80ba44a6", 73 | "https://deno.land/std@0.173.0/node/_process/streams.mjs": "c1461c4dbf963a93a0ca8233467573a685bbde347562573761cc9435fd7080f6", 74 | "https://deno.land/std@0.173.0/node/_stream.d.ts": "112e1a0677cd6db932c3ce0e6e5bbdc7a2ac1874572f449044ecc82afcf5ee2e", 75 | "https://deno.land/std@0.173.0/node/_stream.mjs": "d6e2c86c1158ac65b4c2ca4fa019d7e84374ff12e21e2175345fe68c0823efe3", 76 | "https://deno.land/std@0.173.0/node/_util/_util_callbackify.ts": "a7ffe799ac5f54f3a780ee1c9b190b94dc7dc8afbb430c0e1c73756638d25d64", 77 | "https://deno.land/std@0.173.0/node/_utils.ts": "7fd55872a0cf9275e3c080a60e2fa6d45b8de9e956ebcde9053e72a344185884", 78 | "https://deno.land/std@0.173.0/node/buffer.ts": "85617be2063eccaf177dbb84c7580d1e32023724ed14bd9df4e453b152a26167", 79 | "https://deno.land/std@0.173.0/node/child_process.ts": "87ce152680caeedcfa56474e06df7294d27a65ab1c52462d8cc8f45931d68fc1", 80 | "https://deno.land/std@0.173.0/node/events.ts": "d2de352d509de11a375e2cb397d6b98f5fed4e562fc1d41be33214903a38e6b0", 81 | "https://deno.land/std@0.173.0/node/fs.ts": "de13cb511655b594157b327cd11bb833cc96051409f34148f043e8a8a92d66a1", 82 | "https://deno.land/std@0.173.0/node/fs/promises.ts": "5db686797cec9a6bc7b1460beb7e049ada81a43bbc0ff8231a26442261ec3fd0", 83 | "https://deno.land/std@0.173.0/node/internal/assert.mjs": "1d50c20eeaf16a6d9c1d90347e497669cebc915f5ee238417a73847eb4c2f0de", 84 | "https://deno.land/std@0.173.0/node/internal/buffer.d.ts": "bdfa991cd88cb02fd08bf8235d2618550e3e511c970b2a8f2e1a6885a2793cac", 85 | "https://deno.land/std@0.173.0/node/internal/buffer.mjs": "e92303a3cc6d9aaabcd270a937ad9319825d9ba08cb332650944df4562029b27", 86 | "https://deno.land/std@0.173.0/node/internal/child_process.ts": "047d7e872b2a3cd58d5cce50146a77c003d011ecb8e67a8c630b24375665e607", 87 | "https://deno.land/std@0.173.0/node/internal/crypto/_keys.ts": "8f3c3b5a141aa0331a53c205e9338655f1b3b307a08085fd6ff6dda6f7c4190b", 88 | "https://deno.land/std@0.173.0/node/internal/crypto/constants.ts": "544d605703053218499b08214f2e25cf4310651d535b7ab995891c4b7a217693", 89 | "https://deno.land/std@0.173.0/node/internal/error_codes.ts": "8495e33f448a484518d76fa3d41d34fc20fe03c14b30130ad8e936b0035d4b8b", 90 | "https://deno.land/std@0.173.0/node/internal/errors.ts": "1c699b8a3cb93174f697a348c004b1c6d576b66688eac8a48ebb78e65c720aae", 91 | "https://deno.land/std@0.173.0/node/internal/fixed_queue.ts": "62bb119afa5b5ae8fc0c7048b50502347bec82e2588017d0b250c4671d6eff8f", 92 | "https://deno.land/std@0.173.0/node/internal/fs/streams.d.ts": "23571ff9af59d86307831b80823e440953f3e57b134ca7ec6e55b60b845d38de", 93 | "https://deno.land/std@0.173.0/node/internal/fs/streams.mjs": "5de00d105009fb8cec6b6d0a6e6e6288ae40879cc64d9bf7a84852220be9fa34", 94 | "https://deno.land/std@0.173.0/node/internal/fs/utils.mjs": "64b6dc17752fa861b46a0876647336ba24efe3b5130bd1826f1f2d59b9b374ed", 95 | "https://deno.land/std@0.173.0/node/internal/hide_stack_frames.ts": "9dd1bad0a6e62a1042ce3a51eb1b1ecee2f246907bff44835f86e8f021de679a", 96 | "https://deno.land/std@0.173.0/node/internal/idna.ts": "034043ac9273eb5ba83112c926dba1777775f1eca40e021c8703cd1720bedd9f", 97 | "https://deno.land/std@0.173.0/node/internal/net.ts": "5538d31b595ac63d4b3e90393168bc65ace2f332c3317cffa2fd780070b2d86c", 98 | "https://deno.land/std@0.173.0/node/internal/normalize_encoding.mjs": "fd1d9df61c44d7196432f6e8244621468715131d18cc79cd299fc78ac549f707", 99 | "https://deno.land/std@0.173.0/node/internal/options.ts": "888f267c3fe8f18dc7b2f2fbdbe7e4a0fd3302ff3e99f5d6645601e924f3e3fb", 100 | "https://deno.land/std@0.173.0/node/internal/primordials.mjs": "a72d86b5aa55d3d50b8e916b6a59b7cc0dc5a31da8937114b4a113ad5aa08c74", 101 | "https://deno.land/std@0.173.0/node/internal/process/per_thread.mjs": "10142bbb13978c2f8f79778ad90f3a67a8ea6d8d2970f3dfc6bf2c6fff0162a2", 102 | "https://deno.land/std@0.173.0/node/internal/querystring.ts": "479f30c136555dc3b6f09af7d0de8a70c753035c1d5b57acc696722028788323", 103 | "https://deno.land/std@0.173.0/node/internal/readline/callbacks.mjs": "bdb129b140c3b21b5e08cdc3d8e43517ad818ac03f75197338d665cca1cbaed3", 104 | "https://deno.land/std@0.173.0/node/internal/readline/utils.mjs": "c3dbf3a97c01ed14052cca3848f09e2fc24818c1822ceed57c33b9f0840f3b87", 105 | "https://deno.land/std@0.173.0/node/internal/streams/destroy.mjs": "b665fc71178919a34ddeac8389d162a81b4bc693ff7dc2557fa41b3a91011967", 106 | "https://deno.land/std@0.173.0/node/internal/streams/end-of-stream.mjs": "a4fb1c2e32d58dff440d4e716e2c4daaa403b3095304a028bb428575cfeed716", 107 | "https://deno.land/std@0.173.0/node/internal/streams/utils.mjs": "f2fe2e6bdc506da24c758970890cc2a21642045b129dee618bd3827c60dd9e33", 108 | "https://deno.land/std@0.173.0/node/internal/url.ts": "7e62e16520de552c130c354d9c725a2f5e2af453ff929a2009fa66ae445bbe14", 109 | "https://deno.land/std@0.173.0/node/internal/util.mjs": "f7fe2e1ca5e66f550ad0856b9f5ee4d666f0c071fe212ea7fc7f37cfa81f97a5", 110 | "https://deno.land/std@0.173.0/node/internal/util/comparisons.ts": "0da27292b2714c14873a798221189321ee044e52a9c5df99979f8415f1348665", 111 | "https://deno.land/std@0.173.0/node/internal/util/debuglog.ts": "a2392980a65cc6916afc17fa6686242ee0e3b47bd98c792ff59358560b24185e", 112 | "https://deno.land/std@0.173.0/node/internal/util/inspect.mjs": "11d7c9cab514b8e485acc3978c74b837263ff9c08ae4537fa18ad56bae633259", 113 | "https://deno.land/std@0.173.0/node/internal/util/types.ts": "4f3625ea39111eaae1443c834e769b0c5ce9ea33b31d5a853b02af6a78105178", 114 | "https://deno.land/std@0.173.0/node/internal/validators.mjs": "e02f2b02dd072a5d623970292588d541204dc82207b4c58985d933a5f4b382e6", 115 | "https://deno.land/std@0.173.0/node/internal_binding/_libuv_winerror.ts": "30c9569603d4b97a1f1a034d88a3f74800d5ea1f12fcc3d225c9899d4e1a518b", 116 | "https://deno.land/std@0.173.0/node/internal_binding/_listen.ts": "c6038be47116f7755c01fd98340a0d1e8e66ef874710ab59ed3f5607d50d7a25", 117 | "https://deno.land/std@0.173.0/node/internal_binding/_node.ts": "cb2389b0eab121df99853eb6a5e3a684e4537e065fb8bf2cca0cbf219ce4e32e", 118 | "https://deno.land/std@0.173.0/node/internal_binding/_timingSafeEqual.ts": "7d9732464d3c669ff07713868ce5d25bc974a06112edbfb5f017fc3c70c0853e", 119 | "https://deno.land/std@0.173.0/node/internal_binding/_utils.ts": "7c58a2fbb031a204dee9583ba211cf9c67922112fe77e7f0b3226112469e9fe1", 120 | "https://deno.land/std@0.173.0/node/internal_binding/_winerror.ts": "3e8cfdfe22e89f13d2b28529bab35155e6b1730c0221ec5a6fc7077dc037be13", 121 | "https://deno.land/std@0.173.0/node/internal_binding/ares.ts": "bdd34c679265a6c115a8cfdde000656837a0a0dcdb0e4c258e622e136e9c31b8", 122 | "https://deno.land/std@0.173.0/node/internal_binding/async_wrap.ts": "0dc5ae64eea2c9e57ab17887ef1573922245167ffe38e3685c28d636f487f1b7", 123 | "https://deno.land/std@0.173.0/node/internal_binding/buffer.ts": "31729e0537921d6c730ad0afea44a7e8a0a1044d070ade8368226cb6f7390c8b", 124 | "https://deno.land/std@0.173.0/node/internal_binding/cares_wrap.ts": "9b7247772167f8ed56acd0244a232d9d50e8d7c9cfc379f77f3d54cecc2f32ab", 125 | "https://deno.land/std@0.173.0/node/internal_binding/config.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 126 | "https://deno.land/std@0.173.0/node/internal_binding/connection_wrap.ts": "7dd089ea46de38e4992d0f43a09b586e4cf04878fb06863c1cb8cb2ece7da521", 127 | "https://deno.land/std@0.173.0/node/internal_binding/constants.ts": "21ff9d1ee71d0a2086541083a7711842fc6ae25e264dbf45c73815aadce06f4c", 128 | "https://deno.land/std@0.173.0/node/internal_binding/contextify.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 129 | "https://deno.land/std@0.173.0/node/internal_binding/credentials.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 130 | "https://deno.land/std@0.173.0/node/internal_binding/crypto.ts": "29e8f94f283a2e7d4229d3551369c6a40c2af9737fad948cb9be56bef6c468cd", 131 | "https://deno.land/std@0.173.0/node/internal_binding/errors.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 132 | "https://deno.land/std@0.173.0/node/internal_binding/fs.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 133 | "https://deno.land/std@0.173.0/node/internal_binding/fs_dir.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 134 | "https://deno.land/std@0.173.0/node/internal_binding/fs_event_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 135 | "https://deno.land/std@0.173.0/node/internal_binding/handle_wrap.ts": "adf0b8063da2c54f26edd5e8ec50296a4d38e42716a70a229f14654b17a071d9", 136 | "https://deno.land/std@0.173.0/node/internal_binding/heap_utils.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 137 | "https://deno.land/std@0.173.0/node/internal_binding/http_parser.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 138 | "https://deno.land/std@0.173.0/node/internal_binding/icu.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 139 | "https://deno.land/std@0.173.0/node/internal_binding/inspector.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 140 | "https://deno.land/std@0.173.0/node/internal_binding/js_stream.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 141 | "https://deno.land/std@0.173.0/node/internal_binding/messaging.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 142 | "https://deno.land/std@0.173.0/node/internal_binding/mod.ts": "9fc65f7af1d35e2d3557539a558ea9ad7a9954eefafe614ad82d94bddfe25845", 143 | "https://deno.land/std@0.173.0/node/internal_binding/module_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 144 | "https://deno.land/std@0.173.0/node/internal_binding/native_module.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 145 | "https://deno.land/std@0.173.0/node/internal_binding/natives.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 146 | "https://deno.land/std@0.173.0/node/internal_binding/node_file.ts": "21edbbc95653e45514aff252b6cae7bf127a4338cbc5f090557d258aa205d8a5", 147 | "https://deno.land/std@0.173.0/node/internal_binding/node_options.ts": "0b5cb0bf4379a39278d7b7bb6bb2c2751baf428fe437abe5ed3e8441fae1f18b", 148 | "https://deno.land/std@0.173.0/node/internal_binding/options.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 149 | "https://deno.land/std@0.173.0/node/internal_binding/os.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 150 | "https://deno.land/std@0.173.0/node/internal_binding/performance.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 151 | "https://deno.land/std@0.173.0/node/internal_binding/pipe_wrap.ts": "e5429879551fb7195039986fe6da920a86971fad4342046cbf653643e6c85e21", 152 | "https://deno.land/std@0.173.0/node/internal_binding/process_methods.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 153 | "https://deno.land/std@0.173.0/node/internal_binding/report.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 154 | "https://deno.land/std@0.173.0/node/internal_binding/serdes.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 155 | "https://deno.land/std@0.173.0/node/internal_binding/signal_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 156 | "https://deno.land/std@0.173.0/node/internal_binding/spawn_sync.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 157 | "https://deno.land/std@0.173.0/node/internal_binding/stream_wrap.ts": "452bff74d1db280a0cd78c75a95bb6d163e849e06e9638c4af405d40296bd050", 158 | "https://deno.land/std@0.173.0/node/internal_binding/string_decoder.ts": "54c3c1cbd5a9254881be58bf22637965dc69535483014dab60487e299cb95445", 159 | "https://deno.land/std@0.173.0/node/internal_binding/symbols.ts": "4dee2f3a400d711fd57fa3430b8de1fdb011e08e260b81fef5b81cc06ed77129", 160 | "https://deno.land/std@0.173.0/node/internal_binding/task_queue.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 161 | "https://deno.land/std@0.173.0/node/internal_binding/tcp_wrap.ts": "cbede7224fcf0adc4b04e2e1222488a7a9c137807f143bd32cc8b1a121e0d4fa", 162 | "https://deno.land/std@0.173.0/node/internal_binding/timers.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 163 | "https://deno.land/std@0.173.0/node/internal_binding/tls_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 164 | "https://deno.land/std@0.173.0/node/internal_binding/trace_events.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 165 | "https://deno.land/std@0.173.0/node/internal_binding/tty_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 166 | "https://deno.land/std@0.173.0/node/internal_binding/types.ts": "5a658bf08975af30d0fad6fa6247274379be26ba3f023425bec03e61c74083ef", 167 | "https://deno.land/std@0.173.0/node/internal_binding/udp_wrap.ts": "cc86f7e51bf56fd619505cf9d4f77d7aae1526abdf295399dd277162d28ca6c1", 168 | "https://deno.land/std@0.173.0/node/internal_binding/url.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 169 | "https://deno.land/std@0.173.0/node/internal_binding/util.ts": "808ff3b92740284184ab824adfc420e75398c88c8bccf5111f0c24ac18c48f10", 170 | "https://deno.land/std@0.173.0/node/internal_binding/uv.ts": "eb0048e30af4db407fb3f95563e30d70efd6187051c033713b0a5b768593a3a3", 171 | "https://deno.land/std@0.173.0/node/internal_binding/v8.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 172 | "https://deno.land/std@0.173.0/node/internal_binding/worker.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 173 | "https://deno.land/std@0.173.0/node/internal_binding/zlib.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 174 | "https://deno.land/std@0.173.0/node/path.ts": "1c6aa9101554136525b368e8280f0f78136d4071dd71ad3a70477f27d9e4dd91", 175 | "https://deno.land/std@0.173.0/node/path/_constants.ts": "2e2f68b8679cbf0ef118de8e5719e90cfb091de17d4a7c026c911b6772e6a247", 176 | "https://deno.land/std@0.173.0/node/path/_interface.ts": "c67d76726d0f86ea62ec68d17f11d50680c4659a60a0ea6dcd2488109435b4ce", 177 | "https://deno.land/std@0.173.0/node/path/_util.ts": "44deaf5bbd947eafb3439ea7208d0625e231c5f55c421fe83f5ef91218dcd28c", 178 | "https://deno.land/std@0.173.0/node/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", 179 | "https://deno.land/std@0.173.0/node/path/glob.ts": "b5fc2aed74aa7511cfd07d52dcd595cc18cd7ca431326a664e735d8905d85ce8", 180 | "https://deno.land/std@0.173.0/node/path/mod.ts": "cad27b16a7a3a8c2bb3ad1ba68a63d11e4fb616d63fd55c95e399a0a3a927be2", 181 | "https://deno.land/std@0.173.0/node/path/posix.ts": "a066e77f554358a82b4a693726faa41932f02f5bcd520f07afb6b2372e62484d", 182 | "https://deno.land/std@0.173.0/node/path/separator.ts": "5cfefe182e88bc8138022475703a9b39b13250c79bf234cdc6e3be9afd639662", 183 | "https://deno.land/std@0.173.0/node/path/win32.ts": "3a1b21948e0063cf1ac1c6834ef3ed633b5405f107be01aadfaedd2088b57eef", 184 | "https://deno.land/std@0.173.0/node/process.ts": "6608012d6d51a17a7346f36079c574b9b9f81f1b5c35436489ad089f39757466", 185 | "https://deno.land/std@0.173.0/node/querystring.ts": "2dce8068cb80ce2bf503aecd888be1b89827288352b6581e0fc401886d56cd86", 186 | "https://deno.land/std@0.173.0/node/stream.ts": "09e348302af40dcc7dc58aa5e40fdff868d11d8d6b0cfb85cbb9c75b9fe450c7", 187 | "https://deno.land/std@0.173.0/node/string_decoder.ts": "1a17e3572037c512cc5fc4b29076613e90f225474362d18da908cb7e5ccb7e88", 188 | "https://deno.land/std@0.173.0/node/url.ts": "f8c6656f32728a447705a273e3d8a5118631c0b6560d13fc613901ec9a3f69d0", 189 | "https://deno.land/std@0.173.0/node/util.ts": "4c12edeafde7e50dfe2d4022e383decb422c77858b938b093698cb7250c9e125", 190 | "https://deno.land/std@0.173.0/node/util/types.ts": "461b2e1118fd32456967e14b99f01c892dee1e94d144d6b96e9d94eb086a9574", 191 | "https://deno.land/std@0.173.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", 192 | "https://deno.land/std@0.173.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", 193 | "https://deno.land/std@0.173.0/path/_util.ts": "86c2375a996c1931b2f2ac71fefd5ddf0cf0e579fa4ab12d3e4c552d4223b8d8", 194 | "https://deno.land/std@0.173.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", 195 | "https://deno.land/std@0.173.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", 196 | "https://deno.land/std@0.173.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232", 197 | "https://deno.land/std@0.173.0/path/posix.ts": "0874b341c2c6968ca38d323338b8b295ea1dae10fa872a768d812e2e7d634789", 198 | "https://deno.land/std@0.173.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", 199 | "https://deno.land/std@0.173.0/path/win32.ts": "672942919dd66ce1b8c224e77d3447e2ad8254eaff13fe6946420a9f78fa141e", 200 | "https://deno.land/std@0.173.0/streams/write_all.ts": "3b2e1ce44913f966348ce353d02fa5369e94115181037cd8b602510853ec3033", 201 | "https://deno.land/std@0.173.0/types.d.ts": "220ed56662a0bd393ba5d124aa6ae2ad36a00d2fcbc0e8666a65f4606aaa9784", 202 | "https://deno.land/std@0.174.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", 203 | "https://deno.land/std@0.174.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", 204 | "https://deno.land/std@0.174.0/async/abortable.ts": "73acfb3ed7261ce0d930dbe89e43db8d34e017b063cf0eaa7d215477bf53442e", 205 | "https://deno.land/std@0.174.0/async/deadline.ts": "b98e50d2c42399af03ad13bbb8cf59dadb9f0cd5d70648cc0c3b9202d75ab565", 206 | "https://deno.land/std@0.174.0/async/debounce.ts": "adab11d04ca38d699444ac8a9d9856b4155e8dda2afd07ce78276c01ea5a4332", 207 | "https://deno.land/std@0.174.0/async/deferred.ts": "42790112f36a75a57db4a96d33974a936deb7b04d25c6084a9fa8a49f135def8", 208 | "https://deno.land/std@0.174.0/async/delay.ts": "73aa04cec034c84fc748c7be49bb15cac3dd43a57174bfdb7a4aec22c248f0dd", 209 | "https://deno.land/std@0.174.0/async/mod.ts": "f04344fa21738e5ad6bea37a6bfffd57c617c2d372bb9f9dcfd118a1b622e576", 210 | "https://deno.land/std@0.174.0/async/mux_async_iterator.ts": "70c7f2ee4e9466161350473ad61cac0b9f115cff4c552eaa7ef9d50c4cbb4cc9", 211 | "https://deno.land/std@0.174.0/async/pool.ts": "fd082bd4aaf26445909889435a5c74334c017847842ec035739b4ae637ae8260", 212 | "https://deno.land/std@0.174.0/async/retry.ts": "5efa3ba450ac0c07a40a82e2df296287b5013755d232049efd7ea2244f15b20f", 213 | "https://deno.land/std@0.174.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757", 214 | "https://deno.land/std@0.174.0/bytes/index_of_needle.ts": "65c939607df609374c4415598fa4dad04a2f14c4d98cd15775216f0aaf597f24", 215 | "https://deno.land/std@0.174.0/collections/map_values.ts": "431b78fd770c72cc978ca7bbfa08cdc0805e69c7d2b69ad19859e093467bd46d", 216 | "https://deno.land/std@0.174.0/crypto/timing_safe_equal.ts": "8d69ab611c67fe51b6127d97fcfb4d8e7d0e1b6b4f3e0cc4ab86744c3691f965", 217 | "https://deno.land/std@0.174.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1", 218 | "https://deno.land/std@0.174.0/encoding/base64url.ts": "3f1178f6446834457b16bfde8b559c1cd3481727fe384d3385e4a9995dc2d851", 219 | "https://deno.land/std@0.174.0/flags/mod.ts": "d1cdefa18472ef69858a17df5cf7c98445ed27ac10e1460183081303b0ebc270", 220 | "https://deno.land/std@0.174.0/fmt/printf.ts": "e5b426cd6ad13df5d408e9c375c025d59de30e380c5534715bd892df874ab057", 221 | "https://deno.land/std@0.174.0/fs/exists.ts": "b8c8a457b71e9d7f29b9d2f87aad8dba2739cbe637e8926d6ba6e92567875f8e", 222 | "https://deno.land/std@0.174.0/node/_core.ts": "8667bf9129cdcbaac6f5c14f4def45ca954928baaa697a0ffdb0ee1556d4c644", 223 | "https://deno.land/std@0.174.0/node/_events.d.ts": "1347437fd6b084d7c9a4e16b9fe7435f00b030970086482edeeb3b179d0775af", 224 | "https://deno.land/std@0.174.0/node/_events.mjs": "d4ba4e629abe3db9f1b14659fd5c282b7da8b2b95eaf13238eee4ebb142a2448", 225 | "https://deno.land/std@0.174.0/node/_fs/_fs_access.ts": "48a722db00fd34ec567c1d03c47f6b94d07658c658eeb7d9a10c6b823ebdefbd", 226 | "https://deno.land/std@0.174.0/node/_fs/_fs_appendFile.ts": "2e5230c88804f4b5bee29efa1ba723d71a53f9b0f85d5e6372509ba12e9c00c3", 227 | "https://deno.land/std@0.174.0/node/_fs/_fs_chmod.ts": "fcba6aa4fe2d9178746b5b4ae7f42a72a971007c855988f0e26ff8f694c3c212", 228 | "https://deno.land/std@0.174.0/node/_fs/_fs_chown.ts": "6a24414772d689f8e83b6f53f134420dc25d752bd5be56cade39e92f182c9c9a", 229 | "https://deno.land/std@0.174.0/node/_fs/_fs_close.ts": "8fc5819affb69fb5708f3babce49cd673133e939cebef0665099da78a0d0be7a", 230 | "https://deno.land/std@0.174.0/node/_fs/_fs_common.ts": "21caae4ab7c07c66244446c63c50291cc553d1224d3f6a0cd7bea688c6b2a815", 231 | "https://deno.land/std@0.174.0/node/_fs/_fs_constants.ts": "22ce5f8b07fa8fd7ba37718ad85f6655954b7585d21e6d0b9d73676c16ef1b15", 232 | "https://deno.land/std@0.174.0/node/_fs/_fs_copy.ts": "9074e3a1609b9ee10ca1a2d77e94836c57190e791a0878f7e03b2f0e4e0d5dfb", 233 | "https://deno.land/std@0.174.0/node/_fs/_fs_dir.ts": "26c16ef8003772c9cd2439b448530443ea09a1508a6d808a5913576c3d11882b", 234 | "https://deno.land/std@0.174.0/node/_fs/_fs_dirent.ts": "e8c30d8059336cb6b122738c487cb46c1bcfc4c99fd6d64186f04b4e1805be34", 235 | "https://deno.land/std@0.174.0/node/_fs/_fs_exists.ts": "012e8bf6a6a9b53f9e6451db6ddabf1b883a25e6aebb8aadf8958b57efffefd0", 236 | "https://deno.land/std@0.174.0/node/_fs/_fs_fdatasync.ts": "cfe9409aed4bfe707fb497fe5be449a678b4ae454c9068f3720138ff06f7a71f", 237 | "https://deno.land/std@0.174.0/node/_fs/_fs_fstat.ts": "b15968d0f0da997960f0814e52beee35aff5e04519f007c3ac1c431829a03ac4", 238 | "https://deno.land/std@0.174.0/node/_fs/_fs_fsync.ts": "902c1d4ef9b022c61a12c5f85db3ec4e14778019697cf453822313f9eab9516b", 239 | "https://deno.land/std@0.174.0/node/_fs/_fs_ftruncate.ts": "36d76a3d6b325345ba6fbef745ec1a39d6efb4472214ede8421449296fd25711", 240 | "https://deno.land/std@0.174.0/node/_fs/_fs_futimes.ts": "75b9aaa28588d94b9d8be3c5ca4b74595cde342d644afc9c5dda1e1dcc1e604f", 241 | "https://deno.land/std@0.174.0/node/_fs/_fs_link.ts": "5cfa4f02cbedf913d90618c1bf130796bc3cdd7cd0e59cf5defb05619ae10b8a", 242 | "https://deno.land/std@0.174.0/node/_fs/_fs_lstat.ts": "da6a26b4745dbb92eda21f07992d16497a6848fe2ded6a425ade4a2418262b57", 243 | "https://deno.land/std@0.174.0/node/_fs/_fs_mkdir.ts": "94e4341f9bbc3bae9f1474e86621d48101a4a863ce51fd6b1170ef244533c494", 244 | "https://deno.land/std@0.174.0/node/_fs/_fs_mkdtemp.ts": "33658ccb449f90d69305868b718f8fe8d72a2a8e2be7136ebd69ba313fd0b4a9", 245 | "https://deno.land/std@0.174.0/node/_fs/_fs_open.ts": "9f728953c07748a54a73bb9ff0013530e33556a688a359a554d5db5b4ed30d06", 246 | "https://deno.land/std@0.174.0/node/_fs/_fs_opendir.ts": "fe65a45b92b6b970da8f3acec15920cb5669c7a19fd07afa8ebcd248ec69740b", 247 | "https://deno.land/std@0.174.0/node/_fs/_fs_read.ts": "a0223081bc460a8af5d1bb01e59a44182629bf7bff7c583031912abf20ac6b04", 248 | "https://deno.land/std@0.174.0/node/_fs/_fs_readFile.ts": "2c155de6b568a4e5d3d089e58723355fc519de2d2c9422f7dd211cda2c8f36dc", 249 | "https://deno.land/std@0.174.0/node/_fs/_fs_readdir.ts": "85f742c2ad38bebb8ba5dee72b37a966fc4b42b10382a76a60d7a2dda0a6278c", 250 | "https://deno.land/std@0.174.0/node/_fs/_fs_readlink.ts": "d5d9746c1d3c76cce0be5045dbb3bfde100406a98f1d4db8243776a2fc5619af", 251 | "https://deno.land/std@0.174.0/node/_fs/_fs_realpath.ts": "671afd8bc1b33126d56155de3827d6ec55361631eec9f4944d7f91835d897329", 252 | "https://deno.land/std@0.174.0/node/_fs/_fs_rename.ts": "2fd973c38ab5c66d806a954914a2d2b6beec55308b6da0616837ba81946bba3b", 253 | "https://deno.land/std@0.174.0/node/_fs/_fs_rm.ts": "27c01d261a3631729f9406d9dc7be263a7adf240094ba9133da511169785023b", 254 | "https://deno.land/std@0.174.0/node/_fs/_fs_rmdir.ts": "d9a35aa265670aba4a6da10cb151139bd69762ccfb88e27f266c1260c244d3ec", 255 | "https://deno.land/std@0.174.0/node/_fs/_fs_stat.ts": "bf1ca585b624f5b183ff547f02ad40b51d47247a7fd5df84f8c27376e7a7c2d5", 256 | "https://deno.land/std@0.174.0/node/_fs/_fs_symlink.ts": "89752d75dd823be7ea2c0f2ca024b14c954f7d1507360abf883245f4b700464b", 257 | "https://deno.land/std@0.174.0/node/_fs/_fs_truncate.ts": "4333d191574be1d6ab20fdee346c0dd4868e5c9c5e8ee716e3b09bf562aee698", 258 | "https://deno.land/std@0.174.0/node/_fs/_fs_unlink.ts": "6a760088a99c7465d9da3cbd67a456a6207c9764c65926ce1e0d3172aab780a2", 259 | "https://deno.land/std@0.174.0/node/_fs/_fs_utimes.ts": "c433ef58bfd20d84d0f940c17575b496dcd4706e8dc86aea777c73f667164444", 260 | "https://deno.land/std@0.174.0/node/_fs/_fs_watch.ts": "2ed05b68759e1771515efa4c6d19db9c956cfbc79a715d61e4ce8f38ac12c966", 261 | "https://deno.land/std@0.174.0/node/_fs/_fs_write.d.ts": "a405627931c1a5a3160d3f1cf028761d51b50cd632d6602cb0f98c6b39c96b23", 262 | "https://deno.land/std@0.174.0/node/_fs/_fs_write.mjs": "595abc0d7be9ef3709b62bf09972c2836b25c945f4c531a6688b910e428e1b42", 263 | "https://deno.land/std@0.174.0/node/_fs/_fs_writeFile.ts": "c65f61a167e5f80f29a88147012ade2a81233c882e51c6a07f45a153f2316a58", 264 | "https://deno.land/std@0.174.0/node/_fs/_fs_writev.d.ts": "2cd3596fe24579debe43b587d5bb5845f6f0ce3913357376eb279511ce832d15", 265 | "https://deno.land/std@0.174.0/node/_fs/_fs_writev.mjs": "54adae0d5e5148d2ee0690d04f7272dbccd1242ffbdf838778ac514c10197844", 266 | "https://deno.land/std@0.174.0/node/_global.d.ts": "2d88342f38b4083b858998e27c706725fb03a74aa14ef8d985dc18438b5188e4", 267 | "https://deno.land/std@0.174.0/node/_next_tick.ts": "9a3cf107d59b019a355d3cf32275b4c6157282e4b68ea85b46a799cb1d379305", 268 | "https://deno.land/std@0.174.0/node/_process/exiting.ts": "6e336180aaabd1192bf99ffeb0d14b689116a3dec1dfb34a2afbacd6766e98ab", 269 | "https://deno.land/std@0.174.0/node/_process/process.ts": "c96bb1f6253824c372f4866ee006dcefda02b7050d46759736e403f862d91051", 270 | "https://deno.land/std@0.174.0/node/_process/stdio.mjs": "cf17727eac8da3a665851df700b5aca6a12bacc3ebbf33e63e4b919f80ba44a6", 271 | "https://deno.land/std@0.174.0/node/_process/streams.mjs": "c1461c4dbf963a93a0ca8233467573a685bbde347562573761cc9435fd7080f6", 272 | "https://deno.land/std@0.174.0/node/_stream.d.ts": "112e1a0677cd6db932c3ce0e6e5bbdc7a2ac1874572f449044ecc82afcf5ee2e", 273 | "https://deno.land/std@0.174.0/node/_stream.mjs": "d6e2c86c1158ac65b4c2ca4fa019d7e84374ff12e21e2175345fe68c0823efe3", 274 | "https://deno.land/std@0.174.0/node/_util/_util_callbackify.ts": "a7ffe799ac5f54f3a780ee1c9b190b94dc7dc8afbb430c0e1c73756638d25d64", 275 | "https://deno.land/std@0.174.0/node/_utils.ts": "7fd55872a0cf9275e3c080a60e2fa6d45b8de9e956ebcde9053e72a344185884", 276 | "https://deno.land/std@0.174.0/node/buffer.ts": "85617be2063eccaf177dbb84c7580d1e32023724ed14bd9df4e453b152a26167", 277 | "https://deno.land/std@0.174.0/node/child_process.ts": "63e235b64473038d869076034884cbe4e6bd0a39cf28ebaadf883bcfb5af4457", 278 | "https://deno.land/std@0.174.0/node/events.ts": "d2de352d509de11a375e2cb397d6b98f5fed4e562fc1d41be33214903a38e6b0", 279 | "https://deno.land/std@0.174.0/node/fs.ts": "de13cb511655b594157b327cd11bb833cc96051409f34148f043e8a8a92d66a1", 280 | "https://deno.land/std@0.174.0/node/fs/promises.ts": "5db686797cec9a6bc7b1460beb7e049ada81a43bbc0ff8231a26442261ec3fd0", 281 | "https://deno.land/std@0.174.0/node/internal/assert.mjs": "1d50c20eeaf16a6d9c1d90347e497669cebc915f5ee238417a73847eb4c2f0de", 282 | "https://deno.land/std@0.174.0/node/internal/buffer.d.ts": "bdfa991cd88cb02fd08bf8235d2618550e3e511c970b2a8f2e1a6885a2793cac", 283 | "https://deno.land/std@0.174.0/node/internal/buffer.mjs": "e92303a3cc6d9aaabcd270a937ad9319825d9ba08cb332650944df4562029b27", 284 | "https://deno.land/std@0.174.0/node/internal/child_process.ts": "047d7e872b2a3cd58d5cce50146a77c003d011ecb8e67a8c630b24375665e607", 285 | "https://deno.land/std@0.174.0/node/internal/crypto/_keys.ts": "8f3c3b5a141aa0331a53c205e9338655f1b3b307a08085fd6ff6dda6f7c4190b", 286 | "https://deno.land/std@0.174.0/node/internal/crypto/constants.ts": "544d605703053218499b08214f2e25cf4310651d535b7ab995891c4b7a217693", 287 | "https://deno.land/std@0.174.0/node/internal/error_codes.ts": "8495e33f448a484518d76fa3d41d34fc20fe03c14b30130ad8e936b0035d4b8b", 288 | "https://deno.land/std@0.174.0/node/internal/errors.ts": "1c699b8a3cb93174f697a348c004b1c6d576b66688eac8a48ebb78e65c720aae", 289 | "https://deno.land/std@0.174.0/node/internal/fixed_queue.ts": "62bb119afa5b5ae8fc0c7048b50502347bec82e2588017d0b250c4671d6eff8f", 290 | "https://deno.land/std@0.174.0/node/internal/fs/streams.d.ts": "23571ff9af59d86307831b80823e440953f3e57b134ca7ec6e55b60b845d38de", 291 | "https://deno.land/std@0.174.0/node/internal/fs/streams.mjs": "5de00d105009fb8cec6b6d0a6e6e6288ae40879cc64d9bf7a84852220be9fa34", 292 | "https://deno.land/std@0.174.0/node/internal/fs/utils.mjs": "64b6dc17752fa861b46a0876647336ba24efe3b5130bd1826f1f2d59b9b374ed", 293 | "https://deno.land/std@0.174.0/node/internal/hide_stack_frames.ts": "9dd1bad0a6e62a1042ce3a51eb1b1ecee2f246907bff44835f86e8f021de679a", 294 | "https://deno.land/std@0.174.0/node/internal/idna.ts": "034043ac9273eb5ba83112c926dba1777775f1eca40e021c8703cd1720bedd9f", 295 | "https://deno.land/std@0.174.0/node/internal/net.ts": "5538d31b595ac63d4b3e90393168bc65ace2f332c3317cffa2fd780070b2d86c", 296 | "https://deno.land/std@0.174.0/node/internal/normalize_encoding.mjs": "fd1d9df61c44d7196432f6e8244621468715131d18cc79cd299fc78ac549f707", 297 | "https://deno.land/std@0.174.0/node/internal/options.ts": "888f267c3fe8f18dc7b2f2fbdbe7e4a0fd3302ff3e99f5d6645601e924f3e3fb", 298 | "https://deno.land/std@0.174.0/node/internal/primordials.mjs": "a72d86b5aa55d3d50b8e916b6a59b7cc0dc5a31da8937114b4a113ad5aa08c74", 299 | "https://deno.land/std@0.174.0/node/internal/process/per_thread.mjs": "10142bbb13978c2f8f79778ad90f3a67a8ea6d8d2970f3dfc6bf2c6fff0162a2", 300 | "https://deno.land/std@0.174.0/node/internal/querystring.ts": "479f30c136555dc3b6f09af7d0de8a70c753035c1d5b57acc696722028788323", 301 | "https://deno.land/std@0.174.0/node/internal/readline/callbacks.mjs": "bdb129b140c3b21b5e08cdc3d8e43517ad818ac03f75197338d665cca1cbaed3", 302 | "https://deno.land/std@0.174.0/node/internal/readline/utils.mjs": "c3dbf3a97c01ed14052cca3848f09e2fc24818c1822ceed57c33b9f0840f3b87", 303 | "https://deno.land/std@0.174.0/node/internal/streams/destroy.mjs": "b665fc71178919a34ddeac8389d162a81b4bc693ff7dc2557fa41b3a91011967", 304 | "https://deno.land/std@0.174.0/node/internal/streams/end-of-stream.mjs": "a4fb1c2e32d58dff440d4e716e2c4daaa403b3095304a028bb428575cfeed716", 305 | "https://deno.land/std@0.174.0/node/internal/streams/utils.mjs": "f2fe2e6bdc506da24c758970890cc2a21642045b129dee618bd3827c60dd9e33", 306 | "https://deno.land/std@0.174.0/node/internal/url.ts": "7e62e16520de552c130c354d9c725a2f5e2af453ff929a2009fa66ae445bbe14", 307 | "https://deno.land/std@0.174.0/node/internal/util.mjs": "f7fe2e1ca5e66f550ad0856b9f5ee4d666f0c071fe212ea7fc7f37cfa81f97a5", 308 | "https://deno.land/std@0.174.0/node/internal/util/comparisons.ts": "0da27292b2714c14873a798221189321ee044e52a9c5df99979f8415f1348665", 309 | "https://deno.land/std@0.174.0/node/internal/util/debuglog.ts": "a2392980a65cc6916afc17fa6686242ee0e3b47bd98c792ff59358560b24185e", 310 | "https://deno.land/std@0.174.0/node/internal/util/inspect.mjs": "11d7c9cab514b8e485acc3978c74b837263ff9c08ae4537fa18ad56bae633259", 311 | "https://deno.land/std@0.174.0/node/internal/util/types.ts": "4f3625ea39111eaae1443c834e769b0c5ce9ea33b31d5a853b02af6a78105178", 312 | "https://deno.land/std@0.174.0/node/internal/validators.mjs": "e02f2b02dd072a5d623970292588d541204dc82207b4c58985d933a5f4b382e6", 313 | "https://deno.land/std@0.174.0/node/internal_binding/_libuv_winerror.ts": "30c9569603d4b97a1f1a034d88a3f74800d5ea1f12fcc3d225c9899d4e1a518b", 314 | "https://deno.land/std@0.174.0/node/internal_binding/_listen.ts": "c6038be47116f7755c01fd98340a0d1e8e66ef874710ab59ed3f5607d50d7a25", 315 | "https://deno.land/std@0.174.0/node/internal_binding/_node.ts": "cb2389b0eab121df99853eb6a5e3a684e4537e065fb8bf2cca0cbf219ce4e32e", 316 | "https://deno.land/std@0.174.0/node/internal_binding/_timingSafeEqual.ts": "7d9732464d3c669ff07713868ce5d25bc974a06112edbfb5f017fc3c70c0853e", 317 | "https://deno.land/std@0.174.0/node/internal_binding/_utils.ts": "7c58a2fbb031a204dee9583ba211cf9c67922112fe77e7f0b3226112469e9fe1", 318 | "https://deno.land/std@0.174.0/node/internal_binding/_winerror.ts": "3e8cfdfe22e89f13d2b28529bab35155e6b1730c0221ec5a6fc7077dc037be13", 319 | "https://deno.land/std@0.174.0/node/internal_binding/ares.ts": "bdd34c679265a6c115a8cfdde000656837a0a0dcdb0e4c258e622e136e9c31b8", 320 | "https://deno.land/std@0.174.0/node/internal_binding/async_wrap.ts": "0dc5ae64eea2c9e57ab17887ef1573922245167ffe38e3685c28d636f487f1b7", 321 | "https://deno.land/std@0.174.0/node/internal_binding/buffer.ts": "31729e0537921d6c730ad0afea44a7e8a0a1044d070ade8368226cb6f7390c8b", 322 | "https://deno.land/std@0.174.0/node/internal_binding/cares_wrap.ts": "9b7247772167f8ed56acd0244a232d9d50e8d7c9cfc379f77f3d54cecc2f32ab", 323 | "https://deno.land/std@0.174.0/node/internal_binding/config.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 324 | "https://deno.land/std@0.174.0/node/internal_binding/connection_wrap.ts": "7dd089ea46de38e4992d0f43a09b586e4cf04878fb06863c1cb8cb2ece7da521", 325 | "https://deno.land/std@0.174.0/node/internal_binding/constants.ts": "21ff9d1ee71d0a2086541083a7711842fc6ae25e264dbf45c73815aadce06f4c", 326 | "https://deno.land/std@0.174.0/node/internal_binding/contextify.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 327 | "https://deno.land/std@0.174.0/node/internal_binding/credentials.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 328 | "https://deno.land/std@0.174.0/node/internal_binding/crypto.ts": "29e8f94f283a2e7d4229d3551369c6a40c2af9737fad948cb9be56bef6c468cd", 329 | "https://deno.land/std@0.174.0/node/internal_binding/errors.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 330 | "https://deno.land/std@0.174.0/node/internal_binding/fs.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 331 | "https://deno.land/std@0.174.0/node/internal_binding/fs_dir.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 332 | "https://deno.land/std@0.174.0/node/internal_binding/fs_event_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 333 | "https://deno.land/std@0.174.0/node/internal_binding/handle_wrap.ts": "adf0b8063da2c54f26edd5e8ec50296a4d38e42716a70a229f14654b17a071d9", 334 | "https://deno.land/std@0.174.0/node/internal_binding/heap_utils.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 335 | "https://deno.land/std@0.174.0/node/internal_binding/http_parser.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 336 | "https://deno.land/std@0.174.0/node/internal_binding/icu.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 337 | "https://deno.land/std@0.174.0/node/internal_binding/inspector.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 338 | "https://deno.land/std@0.174.0/node/internal_binding/js_stream.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 339 | "https://deno.land/std@0.174.0/node/internal_binding/messaging.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 340 | "https://deno.land/std@0.174.0/node/internal_binding/mod.ts": "9fc65f7af1d35e2d3557539a558ea9ad7a9954eefafe614ad82d94bddfe25845", 341 | "https://deno.land/std@0.174.0/node/internal_binding/module_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 342 | "https://deno.land/std@0.174.0/node/internal_binding/native_module.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 343 | "https://deno.land/std@0.174.0/node/internal_binding/natives.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 344 | "https://deno.land/std@0.174.0/node/internal_binding/node_file.ts": "21edbbc95653e45514aff252b6cae7bf127a4338cbc5f090557d258aa205d8a5", 345 | "https://deno.land/std@0.174.0/node/internal_binding/node_options.ts": "0b5cb0bf4379a39278d7b7bb6bb2c2751baf428fe437abe5ed3e8441fae1f18b", 346 | "https://deno.land/std@0.174.0/node/internal_binding/options.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 347 | "https://deno.land/std@0.174.0/node/internal_binding/os.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 348 | "https://deno.land/std@0.174.0/node/internal_binding/performance.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 349 | "https://deno.land/std@0.174.0/node/internal_binding/pipe_wrap.ts": "e5429879551fb7195039986fe6da920a86971fad4342046cbf653643e6c85e21", 350 | "https://deno.land/std@0.174.0/node/internal_binding/process_methods.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 351 | "https://deno.land/std@0.174.0/node/internal_binding/report.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 352 | "https://deno.land/std@0.174.0/node/internal_binding/serdes.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 353 | "https://deno.land/std@0.174.0/node/internal_binding/signal_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 354 | "https://deno.land/std@0.174.0/node/internal_binding/spawn_sync.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 355 | "https://deno.land/std@0.174.0/node/internal_binding/stream_wrap.ts": "452bff74d1db280a0cd78c75a95bb6d163e849e06e9638c4af405d40296bd050", 356 | "https://deno.land/std@0.174.0/node/internal_binding/string_decoder.ts": "54c3c1cbd5a9254881be58bf22637965dc69535483014dab60487e299cb95445", 357 | "https://deno.land/std@0.174.0/node/internal_binding/symbols.ts": "4dee2f3a400d711fd57fa3430b8de1fdb011e08e260b81fef5b81cc06ed77129", 358 | "https://deno.land/std@0.174.0/node/internal_binding/task_queue.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 359 | "https://deno.land/std@0.174.0/node/internal_binding/tcp_wrap.ts": "cbede7224fcf0adc4b04e2e1222488a7a9c137807f143bd32cc8b1a121e0d4fa", 360 | "https://deno.land/std@0.174.0/node/internal_binding/timers.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 361 | "https://deno.land/std@0.174.0/node/internal_binding/tls_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 362 | "https://deno.land/std@0.174.0/node/internal_binding/trace_events.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 363 | "https://deno.land/std@0.174.0/node/internal_binding/tty_wrap.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 364 | "https://deno.land/std@0.174.0/node/internal_binding/types.ts": "5a658bf08975af30d0fad6fa6247274379be26ba3f023425bec03e61c74083ef", 365 | "https://deno.land/std@0.174.0/node/internal_binding/udp_wrap.ts": "cc86f7e51bf56fd619505cf9d4f77d7aae1526abdf295399dd277162d28ca6c1", 366 | "https://deno.land/std@0.174.0/node/internal_binding/url.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 367 | "https://deno.land/std@0.174.0/node/internal_binding/util.ts": "808ff3b92740284184ab824adfc420e75398c88c8bccf5111f0c24ac18c48f10", 368 | "https://deno.land/std@0.174.0/node/internal_binding/uv.ts": "eb0048e30af4db407fb3f95563e30d70efd6187051c033713b0a5b768593a3a3", 369 | "https://deno.land/std@0.174.0/node/internal_binding/v8.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 370 | "https://deno.land/std@0.174.0/node/internal_binding/worker.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 371 | "https://deno.land/std@0.174.0/node/internal_binding/zlib.ts": "37d293009d1718205bf28e878e54a9f1ca24c1c320cee2416c20dc054104c6ea", 372 | "https://deno.land/std@0.174.0/node/path.ts": "1c6aa9101554136525b368e8280f0f78136d4071dd71ad3a70477f27d9e4dd91", 373 | "https://deno.land/std@0.174.0/node/path/_constants.ts": "2e2f68b8679cbf0ef118de8e5719e90cfb091de17d4a7c026c911b6772e6a247", 374 | "https://deno.land/std@0.174.0/node/path/_interface.ts": "c67d76726d0f86ea62ec68d17f11d50680c4659a60a0ea6dcd2488109435b4ce", 375 | "https://deno.land/std@0.174.0/node/path/_util.ts": "44deaf5bbd947eafb3439ea7208d0625e231c5f55c421fe83f5ef91218dcd28c", 376 | "https://deno.land/std@0.174.0/node/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", 377 | "https://deno.land/std@0.174.0/node/path/glob.ts": "b5fc2aed74aa7511cfd07d52dcd595cc18cd7ca431326a664e735d8905d85ce8", 378 | "https://deno.land/std@0.174.0/node/path/mod.ts": "cad27b16a7a3a8c2bb3ad1ba68a63d11e4fb616d63fd55c95e399a0a3a927be2", 379 | "https://deno.land/std@0.174.0/node/path/posix.ts": "a066e77f554358a82b4a693726faa41932f02f5bcd520f07afb6b2372e62484d", 380 | "https://deno.land/std@0.174.0/node/path/separator.ts": "5cfefe182e88bc8138022475703a9b39b13250c79bf234cdc6e3be9afd639662", 381 | "https://deno.land/std@0.174.0/node/path/win32.ts": "3a1b21948e0063cf1ac1c6834ef3ed633b5405f107be01aadfaedd2088b57eef", 382 | "https://deno.land/std@0.174.0/node/process.ts": "6608012d6d51a17a7346f36079c574b9b9f81f1b5c35436489ad089f39757466", 383 | "https://deno.land/std@0.174.0/node/querystring.ts": "2dce8068cb80ce2bf503aecd888be1b89827288352b6581e0fc401886d56cd86", 384 | "https://deno.land/std@0.174.0/node/stream.ts": "09e348302af40dcc7dc58aa5e40fdff868d11d8d6b0cfb85cbb9c75b9fe450c7", 385 | "https://deno.land/std@0.174.0/node/string_decoder.ts": "1a17e3572037c512cc5fc4b29076613e90f225474362d18da908cb7e5ccb7e88", 386 | "https://deno.land/std@0.174.0/node/url.ts": "f8c6656f32728a447705a273e3d8a5118631c0b6560d13fc613901ec9a3f69d0", 387 | "https://deno.land/std@0.174.0/node/util.ts": "4c12edeafde7e50dfe2d4022e383decb422c77858b938b093698cb7250c9e125", 388 | "https://deno.land/std@0.174.0/node/util/types.ts": "461b2e1118fd32456967e14b99f01c892dee1e94d144d6b96e9d94eb086a9574", 389 | "https://deno.land/std@0.174.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", 390 | "https://deno.land/std@0.174.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", 391 | "https://deno.land/std@0.174.0/path/_util.ts": "86c2375a996c1931b2f2ac71fefd5ddf0cf0e579fa4ab12d3e4c552d4223b8d8", 392 | "https://deno.land/std@0.174.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", 393 | "https://deno.land/std@0.174.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", 394 | "https://deno.land/std@0.174.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232", 395 | "https://deno.land/std@0.174.0/path/posix.ts": "0874b341c2c6968ca38d323338b8b295ea1dae10fa872a768d812e2e7d634789", 396 | "https://deno.land/std@0.174.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", 397 | "https://deno.land/std@0.174.0/path/win32.ts": "672942919dd66ce1b8c224e77d3447e2ad8254eaff13fe6946420a9f78fa141e", 398 | "https://deno.land/std@0.174.0/streams/write_all.ts": "3b2e1ce44913f966348ce353d02fa5369e94115181037cd8b602510853ec3033", 399 | "https://deno.land/std@0.174.0/types.d.ts": "220ed56662a0bd393ba5d124aa6ae2ad36a00d2fcbc0e8666a65f4606aaa9784" 400 | } 401 | } -------------------------------------------------------------------------------- /glacier-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inoas/glacier/aae146deb6b380ac905ed732dce97612a1b14530/glacier-logo.png -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "glacier" 2 | version = "1.3.2" 3 | gleam = ">= 1.9.1" 4 | 5 | licences = ["Apache-2.0"] 6 | description = "Glacier brings incremental interactive unit testing to Gleam. It is meant as a drop-in replacement for Gleeunit and depends on and wraps a fork of it." 7 | repository = { type = "github", user = "inoas", repo = "glacier" } 8 | # links = [{ title = "Website", href = "https://gleam.run" }] 9 | internal_modules = ["glacier_demo", "glacier_demo/*"] 10 | 11 | [dependencies] 12 | argv = ">= 1.0.0 and < 2.0.0" 13 | fs = ">= 8.6.0 and < 9.0.0" 14 | glacier_gleeunit = ">= 1.3.1002 and < 2.0.0" 15 | # glacier_gleeunit = { path = "../gleeunit" } 16 | gleam_community_ansi = ">= 1.4.0 and < 2.0.0" 17 | gleam_community_colour = ">= 2.0.0 and < 3.0.0" 18 | gleam_stdlib = ">= 0.42.0 and < 2.0.0" 19 | shellout = ">= 1.6.0 and < 2.0.0" 20 | simplifile = ">= 2.0.0 and < 3.0.0" 21 | 22 | [javascript.deno] 23 | allow_net = ["deno.land"] 24 | allow_read = ["./"] 25 | allow_run = ["gleam"] 26 | 27 | [documentation] 28 | pages = [ 29 | { title = "Changelog", path = "changelog.html", source = "CHANGELOG.md" }, 30 | { title = "Contribute", path = "contribute.html", source = "CONTRIBUTE.md" }, 31 | { title = "Licence", path = "licence.html", source = "LICENCE" }, 32 | ] 33 | -------------------------------------------------------------------------------- /import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "child_process": "https://deno.land/std@0.173.0/node/child_process.ts", 4 | "fs" : "https://deno.land/std@0.173.0/node/fs.ts", 5 | "fs/promises": "https://deno.land/std@0.173.0/node/fs/promises.ts", 6 | "path" : "https://deno.land/std@0.173.0/node/path.ts", 7 | "process": "https://deno.land/std@0.173.0/node/process.ts" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, 6 | { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 7 | { name = "fs", version = "8.6.1", build_tools = ["rebar3"], requirements = [], otp_app = "fs", source = "hex", outer_checksum = "61EA2BDAEDAE4E2024D0D25C63E44DCCF65622D4402DB4A2DF12868D1546503F" }, 8 | { name = "glacier_gleeunit", version = "1.3.1002", build_tools = ["gleam"], requirements = ["argv", "gleam_stdlib"], otp_app = "glacier_gleeunit", source = "hex", outer_checksum = "D33D329C6CD57562D0697E6D686918D6140A5C881FD2C16A9A3905DAC0EB573F" }, 9 | { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, 10 | { name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" }, 11 | { name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" }, 12 | { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, 13 | { name = "gleam_stdlib", version = "0.59.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F8FEE9B35797301994B81AF75508CF87C328FE1585558B0FFD188DC2B32EAA95" }, 14 | { name = "shellout", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "shellout", source = "hex", outer_checksum = "1BDC03438FEB97A6AF3E396F4ABEB32BECF20DF2452EC9A8C0ACEB7BDDF70B14" }, 15 | { name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" }, 16 | ] 17 | 18 | [requirements] 19 | argv = { version = ">= 1.0.0 and < 2.0.0" } 20 | fs = { version = ">= 8.6.0 and < 9.0.0" } 21 | glacier_gleeunit = { version = ">= 1.3.1002 and < 2.0.0" } 22 | gleam_community_ansi = { version = ">= 1.4.0 and < 2.0.0" } 23 | gleam_community_colour = { version = ">= 2.0.0 and < 3.0.0" } 24 | gleam_stdlib = { version = ">= 0.42.0 and < 2.0.0" } 25 | shellout = { version = ">= 1.6.0 and < 2.0.0" } 26 | simplifile = { version = ">= 2.0.0 and < 3.0.0" } 27 | -------------------------------------------------------------------------------- /src/glacier.gleam: -------------------------------------------------------------------------------- 1 | import gleam/io 2 | import gleam/list 3 | import gleam/string 4 | import gleam/string_tree 5 | import gleam_community/ansi 6 | import gleam_community/colour 7 | import gleeunit 8 | 9 | /// Lets Gleam switch code based on the current target. 10 | /// 11 | type Target { 12 | ErlangTarget 13 | JavaScriptTarget 14 | } 15 | 16 | /// Atom to internally differentiate between `src` and `test` modules. 17 | /// Public because the FFIs rely on it. 18 | /// 19 | pub type ModuleKind { 20 | SrcModuleKind 21 | TestModuleKind 22 | } 23 | 24 | fn light_cyan_ansi_colour() { 25 | let assert Ok(colour) = colour.from_rgb255(r: 156, g: 231, b: 255) 26 | colour 27 | } 28 | 29 | /// Runs either `glacier` or `gleeunit` bundled as `gleeunit`, depending on 30 | /// the given command line arguments. 31 | /// 32 | pub fn main() { 33 | let start_args = start_args() 34 | let is_incremental = list.contains(start_args, "--glacier") 35 | let is_empty_args = start_args == [] 36 | case is_empty_args, is_incremental { 37 | True, _ -> gleeunit.main() 38 | _, True -> { 39 | "🏔 Glacier is watching for changes…" 40 | |> ansi.colour(light_cyan_ansi_colour()) 41 | |> ansi.italic() 42 | |> io.println 43 | start_file_change_watcher(fn(modules: List(#(ModuleKind, String))) -> Nil { 44 | execute_tests(modules) 45 | }) 46 | } 47 | _, _ -> gleeunit.run(start_args, halts_on_error: False) 48 | } 49 | } 50 | 51 | /// Executes tests 52 | /// 53 | fn execute_tests(modules: List(#(ModuleKind, String))) { 54 | let test_modules = 55 | list.fold( 56 | over: modules, 57 | from: [], 58 | with: fn(test_modules_acc: List(String), module: #(ModuleKind, String)) { 59 | let module_kind = module.0 60 | let full_module_path = module.1 61 | let test_modules = case module_kind { 62 | SrcModuleKind -> 63 | detect_distinct_import_module_dependency_chain( 64 | [file_name_to_module_name(full_module_path, SrcModuleKind)], 65 | [], 66 | ) 67 | |> derive_test_modules_from_src_import_dependencies() 68 | |> list.map(fn(test_module) { "test/" <> test_module <> ".gleam" }) 69 | TestModuleKind -> [to_relative_path(full_module_path)] 70 | } 71 | list.append(test_modules_acc, test_modules) 72 | }, 73 | ) 74 | |> list.unique() 75 | 76 | case test_modules { 77 | [] -> { 78 | "🏔 Did not detect any matching test modules!" 79 | |> ansi.colour(light_cyan_ansi_colour()) 80 | |> ansi.bold() 81 | |> io.println 82 | Nil 83 | } 84 | test_modules -> { 85 | list.map(test_modules, with: fn(test_module: String) { 86 | "🏔 " <> test_module 87 | }) 88 | |> string.join("\n") 89 | |> ansi.colour(light_cyan_ansi_colour()) 90 | |> io.println 91 | 92 | let args = [ 93 | "test", 94 | "--target", 95 | case target() { 96 | ErlangTarget -> "erlang" 97 | JavaScriptTarget -> "javascript" 98 | }, 99 | "--", 100 | ..test_modules 101 | ] 102 | 103 | shell_exec_print(args) 104 | } 105 | } 106 | } 107 | 108 | /// Starts the file watcher. 109 | /// 110 | fn start_file_change_watcher( 111 | file_change_handler: fn(List(#(ModuleKind, String))) -> Nil, 112 | ) -> Nil { 113 | do_start_file_change_watcher(file_change_handler) 114 | Nil 115 | } 116 | 117 | /// Detects distinct import module dependency chain. 118 | /// 119 | fn detect_distinct_import_module_dependency_chain( 120 | module_names: List(String), 121 | processed_module_names: List(String), 122 | ) -> List(String) { 123 | case module_names { 124 | [] -> processed_module_names 125 | [module_name, ..rest_module_names] -> 126 | case list.contains(processed_module_names, module_name) { 127 | True -> 128 | detect_distinct_import_module_dependency_chain( 129 | rest_module_names, 130 | processed_module_names, 131 | ) 132 | False -> { 133 | let unchecked_module_names = 134 | module_name 135 | |> module_name_to_file_name(SrcModuleKind) 136 | |> parse_module_for_imports 137 | |> list.append(module_names) 138 | |> list.filter(keeping: fn(module_name) { 139 | list.contains(processed_module_names, module_name) == False 140 | && file_exists(module_name_to_file_name( 141 | module_name, 142 | SrcModuleKind, 143 | )) 144 | }) 145 | detect_distinct_import_module_dependency_chain( 146 | list.append(rest_module_names, unchecked_module_names), 147 | list.append(processed_module_names, [module_name]), 148 | ) 149 | } 150 | } 151 | } 152 | } 153 | 154 | /// Parses a module file for its import statements. 155 | /// 156 | fn parse_module_for_imports(module_file_name: String) -> List(String) { 157 | module_file_name 158 | |> read_module_file() 159 | |> fn(result: Result(String, Nil)) -> List(String) { 160 | case result { 161 | Ok(text) -> 162 | text 163 | |> string.to_graphemes() 164 | |> parse_module_string([], ParseModeSearch, "") 165 | |> list.unique() 166 | Error(Nil) -> [] 167 | } 168 | } 169 | } 170 | 171 | type ParseMode { 172 | ParseModeInComment 173 | ParseModeInString 174 | ParseModeSearch 175 | } 176 | 177 | /// Parses a module string for its import statements 178 | /// 179 | fn parse_module_string( 180 | chars: List(String), 181 | imports: List(String), 182 | context: ParseMode, 183 | collected: String, 184 | ) -> List(String) { 185 | case chars { 186 | [] -> imports 187 | [char, ..rest_chars] -> 188 | case context, collected, char { 189 | // Found `/`: Continue Initial with / in collected 190 | ParseModeSearch, "", "/" -> 191 | parse_module_string(rest_chars, imports, ParseModeSearch, "/") 192 | // Found `/` + `/`: Enter Comment 193 | ParseModeSearch, "/", "/" -> 194 | parse_module_string(rest_chars, imports, ParseModeInComment, "") 195 | // Found `"`: Enter String 196 | ParseModeSearch, _collected, "\"" -> 197 | parse_module_string(rest_chars, imports, ParseModeInString, "") 198 | // Collecting import keyword: Continue Initial 199 | ParseModeSearch, collected, char 200 | if collected == "" 201 | && char == "i" 202 | || collected == "i" 203 | && char == "m" 204 | || collected == "im" 205 | && char == "p" 206 | || collected == "imp" 207 | && char == "o" 208 | || collected == "impo" 209 | && char == "r" 210 | || collected == "impor" 211 | && char == "t" 212 | -> 213 | parse_module_string( 214 | rest_chars, 215 | imports, 216 | ParseModeSearch, 217 | collected <> char, 218 | ) 219 | // Found `import` + whitespaceish: Enter Import 220 | ParseModeSearch, "import", char 221 | if char == " " || char == "\t" || char == "\n" || char == "\r\n" 222 | -> { 223 | let #(rest_chars, new_import) = 224 | parse_import_chars(rest_chars, string_tree.new()) 225 | let new_import = string_tree.to_string(new_import) 226 | let updated_imports = [new_import, ..imports] 227 | parse_module_string(rest_chars, updated_imports, ParseModeSearch, "") 228 | } 229 | // Found `import\r` + "\n": Enter Import 230 | ParseModeSearch, "import\r", "\n" -> { 231 | let #(rest_chars, new_import) = 232 | parse_import_chars(rest_chars, string_tree.new()) 233 | let imports = [string_tree.to_string(new_import), ..imports] 234 | parse_module_string(rest_chars, imports, ParseModeSearch, "") 235 | } 236 | // Found whitespaceish char: Continue Initial with empty collected 237 | ParseModeSearch, _collected, _char -> 238 | parse_module_string(rest_chars, imports, ParseModeSearch, "") 239 | // In Comment; found `\n`: Exit Comment 240 | ParseModeInComment, _collected, "\n" -> 241 | parse_module_string(rest_chars, imports, ParseModeSearch, "") 242 | // In Comment; found `\n`: Exit Comment 243 | ParseModeInComment, _collected, "\r" -> 244 | parse_module_string(rest_chars, imports, ParseModeSearch, "") 245 | ParseModeInComment, _collected, "\r\n" -> 246 | parse_module_string(rest_chars, imports, ParseModeSearch, "") 247 | // In Comment; found any other char: Continue Comment 248 | ParseModeInComment, _collected, _any -> 249 | parse_module_string(rest_chars, imports, ParseModeInComment, "") 250 | // In String; escape found char: Continue String 251 | ParseModeInString, "\\", _escaped -> 252 | parse_module_string(rest_chars, imports, ParseModeInString, "") 253 | // In String; found `"`: Exit String 254 | ParseModeInString, _collected, "\"" -> 255 | parse_module_string(rest_chars, imports, ParseModeSearch, "") 256 | // In String; found a single `\`: Continue String with collected set to `\` 257 | ParseModeInString, _collected, "\\" -> 258 | parse_module_string(rest_chars, imports, ParseModeInString, "\\") 259 | // In String; found any other char: Continue String with empty collected 260 | ParseModeInString, _collected, _char -> 261 | parse_module_string(rest_chars, imports, ParseModeInString, "") 262 | } 263 | } 264 | } 265 | 266 | /// Parses an import statement 267 | /// 268 | fn parse_import_chars( 269 | chars: List(String), 270 | import_module: string_tree.StringTree, 271 | ) { 272 | // TODO: Try pop grapheme 273 | case chars { 274 | // Return if end of line 275 | [] -> #([], import_module) 276 | // Return if . - aka found unqualified import 277 | [".", ..rest_chars] -> #(rest_chars, import_module) 278 | // Whitespaces stop inmports and return 279 | [" ", ..rest_chars] -> #(rest_chars, import_module) 280 | // Return if \r\n 281 | ["\r\n", ..rest_chars] -> #(rest_chars, import_module) 282 | // Return if \n 283 | ["\n", ..rest_chars] -> #(rest_chars, import_module) 284 | // Ignore whitespaces 285 | [char, ..rest_chars] 286 | if char == "\t" || char == "\r" || char == "\n" || char == "\r\n" 287 | -> parse_import_chars(rest_chars, import_module) 288 | // Append for any other character 289 | [char, ..rest_chars] -> 290 | parse_import_chars(rest_chars, string_tree.append(import_module, char)) 291 | } 292 | } 293 | 294 | /// Derives test modules from its src import dependencies 295 | /// 296 | fn derive_test_modules_from_src_import_dependencies( 297 | src_modules: List(String), 298 | ) -> List(String) { 299 | let project_test_files = find_project_files(in: "test") 300 | let all_test_modules = 301 | project_test_files 302 | |> list.map(fn(module_name_dot_gleam) { 303 | let assert Ok(#(module_name, _dot_gleam)) = 304 | string.split_once(module_name_dot_gleam, ".gleam") 305 | module_name 306 | }) 307 | let dirty_test_modules = 308 | all_test_modules 309 | |> list.filter(fn(test_module) { 310 | let test_module_imports = derive_src_imports_off_test_module(test_module) 311 | list.any(in: src_modules, satisfying: fn(src_module) { 312 | test_module_imports 313 | |> list.contains(src_module) 314 | }) 315 | }) 316 | dirty_test_modules 317 | } 318 | 319 | /// Derives src imports from test module 320 | /// 321 | fn derive_src_imports_off_test_module(test_module_name) { 322 | test_module_name 323 | |> module_name_to_file_name(TestModuleKind) 324 | |> parse_module_for_imports 325 | } 326 | 327 | /// Converts a module name to a module file name 328 | /// 329 | fn module_name_to_file_name( 330 | module_name: String, 331 | module_kind: ModuleKind, 332 | ) -> String { 333 | case module_kind { 334 | SrcModuleKind -> get_src_dir() <> "/" <> module_name <> ".gleam" 335 | TestModuleKind -> get_test_dir() <> "/" <> module_name <> ".gleam" 336 | } 337 | } 338 | 339 | /// Converts a module file name to a module name 340 | /// 341 | fn file_name_to_module_name(module_name: String, module_kind: ModuleKind) { 342 | let assert Ok(#(_base_path, module_name_dot_gleam)) = case module_kind { 343 | SrcModuleKind -> string.split_once(module_name, get_src_dir() <> "/") 344 | TestModuleKind -> string.split_once(module_name, get_test_dir() <> "/") 345 | } 346 | case string.ends_with(module_name, ".erl") { 347 | True -> { 348 | let assert Ok(#(module_name, _dot_gleam)) = 349 | string.split_once(module_name_dot_gleam, ".erl") 350 | module_name 351 | } 352 | False -> { 353 | let assert Ok(#(module_name, _dot_gleam)) = 354 | string.split_once(module_name_dot_gleam, ".gleam") 355 | module_name 356 | } 357 | } 358 | } 359 | 360 | /// Checks if a given absolute file path exists 361 | /// 362 | fn file_exists(absolute_file_name: String) -> Bool { 363 | do_file_exists(absolute_file_name) 364 | } 365 | 366 | /// Finds files in project sub directory 367 | /// 368 | fn find_project_files(in sub_directory: String) -> List(String) { 369 | do_find_project_files(sub_directory) 370 | } 371 | 372 | /// Gets the target at runtime 373 | /// 374 | fn target() -> Target { 375 | do_target() 376 | } 377 | 378 | /// Gets the start arguments sometimes called argv. 379 | /// 380 | fn start_args() -> List(String) { 381 | do_start_args() 382 | } 383 | 384 | /// Gets the current project directory. 385 | /// 386 | fn get_cwd() -> String { 387 | do_get_cwd() 388 | } 389 | 390 | /// Gets the project's `src` directory. 391 | /// 392 | fn get_src_dir() -> String { 393 | get_cwd() <> "/src" 394 | } 395 | 396 | /// Gets the project's `test` directory. 397 | /// 398 | fn get_test_dir() -> String { 399 | get_cwd() <> "/test" 400 | } 401 | 402 | /// Cuts off the base path from the project directory. 403 | /// 404 | fn to_relative_path(absolute_file_path path: String) -> String { 405 | let assert Ok(#(_pre_path, relative_file_name)) = 406 | string.split_once(path, get_cwd() <> "/") 407 | relative_file_name 408 | } 409 | 410 | @target(erlang) 411 | import shellout 412 | @target(erlang) 413 | import simplifile as file 414 | 415 | @target(erlang) 416 | fn do_target() -> Target { 417 | ErlangTarget 418 | } 419 | 420 | @target(erlang) 421 | import argv 422 | 423 | @target(erlang) 424 | fn do_start_args() -> List(String) { 425 | argv.load().arguments 426 | } 427 | 428 | @external(erlang, "glacier_ffi", "start_file_change_watcher") 429 | @external(javascript, "./glacier_ffi.mjs", "start_file_change_watcher") 430 | fn do_start_file_change_watcher( 431 | file_change_handler file_change_handler: fn(List(#(ModuleKind, String))) -> 432 | Nil, 433 | ) -> Nil 434 | 435 | @target(erlang) 436 | fn read_module_file(module_path: String) -> Result(String, Nil) { 437 | case file.read(module_path) { 438 | Ok(text) -> Ok(text) 439 | Error(file_reason) -> { 440 | #("Could not read file", module_path, "with reason", file_reason) 441 | |> string.inspect 442 | |> io.println_error 443 | Error(Nil) 444 | } 445 | } 446 | } 447 | 448 | @external(erlang, "glacier_ffi", "get_cwd_as_binary") 449 | @external(javascript, "./glacier_ffi.mjs", "cwd") 450 | fn do_get_cwd() -> String 451 | 452 | @external(erlang, "filelib", "is_regular") 453 | @external(javascript, "./glacier_ffi.mjs", "file_exists") 454 | fn do_file_exists(absolute_file_name absolute_file_name: String) -> Bool 455 | 456 | @target(erlang) 457 | fn do_find_project_files(in: String) -> List(String) { 458 | do_find_files_recursive(in: in, matching: "**/*.{gleam}") 459 | } 460 | 461 | @target(erlang) 462 | @external(erlang, "glacier_ffi", "find_files_recursive") 463 | fn do_find_files_recursive( 464 | in in: String, 465 | matching matching: String, 466 | ) -> List(String) 467 | 468 | @target(erlang) 469 | fn shell_exec_print(args: List(String)) -> Nil { 470 | case 471 | shellout.command(run: "gleam", with: args, in: ".", opt: [ 472 | shellout.LetBeStderr, 473 | ]) 474 | { 475 | Ok(msg) -> { 476 | io.print(msg) 477 | Nil 478 | } 479 | Error(_error_tuple) -> Nil 480 | } 481 | } 482 | 483 | @target(javascript) 484 | fn do_target() -> Target { 485 | JavaScriptTarget 486 | } 487 | 488 | @target(javascript) 489 | fn do_start_args() -> List(String) { 490 | start_args_ffi() 491 | // // This is a work-around for a bug introduced in Gleam 0.26.0: 492 | // |> list.filter(fn(arg) { 493 | // arg != "--" && string.ends_with(arg, "/gleam.main.mjs") == False 494 | // }) 495 | } 496 | 497 | @target(javascript) 498 | @external(javascript, "./glacier_ffi.mjs", "start_args") 499 | fn start_args_ffi() -> List(String) 500 | 501 | @target(javascript) 502 | fn read_module_file(module_path: String) -> Result(String, Nil) { 503 | do_read_module_file(module_path) 504 | } 505 | 506 | @target(javascript) 507 | @external(javascript, "./glacier_ffi.mjs", "read_file") 508 | fn do_read_module_file(module_path module_path: String) -> Result(String, Nil) 509 | 510 | @target(javascript) 511 | fn do_find_project_files(dir: String) -> List(String) { 512 | do_find_files_recursive(dir, [".gleam"]) 513 | |> list.map(fn(file_name) { 514 | let assert Ok(#(_test_prefix, file_name)) = 515 | string.split_once(file_name, "test/") 516 | file_name 517 | }) 518 | } 519 | 520 | @target(javascript) 521 | @external(javascript, "./glacier_ffi.mjs", "find_files_recursive_by_exts") 522 | fn do_find_files_recursive( 523 | in in: String, 524 | file_ext file_ext: List(String), 525 | ) -> List(String) 526 | 527 | @target(javascript) 528 | @external(javascript, "./glacier_ffi.mjs", "shell_exec_print") 529 | fn shell_exec_print(args args: List(String)) -> Nil 530 | -------------------------------------------------------------------------------- /src/glacier/should.gleam: -------------------------------------------------------------------------------- 1 | //// A proxy module into gleeunit/should 2 | //// 3 | //// If you are consistently using glacier (which wraps gleeunit) instead of 4 | //// gleeunit you may replace all `import gleeunit/should` with 5 | //// `import glacier/should`. 6 | 7 | import gleam/option.{type Option} 8 | import gleeunit/should as gleeunit_should 9 | 10 | pub fn equal(a: any, b: any) -> Nil { 11 | gleeunit_should.equal(a, b) 12 | } 13 | 14 | pub fn not_equal(a: any, b: any) -> Nil { 15 | gleeunit_should.not_equal(a, b) 16 | } 17 | 18 | pub fn be_ok(result: Result(a, b)) -> a { 19 | gleeunit_should.be_ok(result) 20 | } 21 | 22 | pub fn be_error(result: Result(a, b)) -> b { 23 | gleeunit_should.be_error(result) 24 | } 25 | 26 | pub fn be_some(a: Option(a)) -> a { 27 | gleeunit_should.be_some(a) 28 | } 29 | 30 | pub fn be_none(a: Option(a)) -> Nil { 31 | gleeunit_should.be_none(a) 32 | } 33 | 34 | pub fn be_true(actual: Bool) -> Nil { 35 | gleeunit_should.be_true(actual) 36 | } 37 | 38 | pub fn be_false(actual: Bool) -> Nil { 39 | gleeunit_should.be_false(actual) 40 | } 41 | 42 | pub fn fail() -> Nil { 43 | gleeunit_should.fail() 44 | } 45 | -------------------------------------------------------------------------------- /src/glacier_demo/glacier_demo_module_a.gleam: -------------------------------------------------------------------------------- 1 | pub fn function_1() { 2 | 1 3 | } 4 | 5 | pub fn function_2() { 6 | 2 7 | } 8 | -------------------------------------------------------------------------------- /src/glacier_demo/glacier_demo_module_aliasing_c.gleam: -------------------------------------------------------------------------------- 1 | import glacier_demo/glacier_demo_module_c as c 2 | 3 | pub fn proxied_function_5() { 4 | c.function_5() 5 | } 6 | -------------------------------------------------------------------------------- /src/glacier_demo/glacier_demo_module_b.gleam: -------------------------------------------------------------------------------- 1 | //// module doc import in_module_docblock should be ignored 2 | 3 | import glacier_demo/glacier_demo_module_a 4 | import glacier_demo/glacier_demo_module_c 5 | 6 | /// function doc import in_fn_docblock should be ignored 7 | pub fn function_3() { 8 | // regular comment import in_comment should be ignored 9 | let _foo = "import string in string_1 should be ignored" 10 | let _bar = "in escaped \" string import string_2 should be ignored" 11 | let _quux = "in escaped \\ string import string_3 should be ignored" 12 | glacier_demo_module_a.function_1() + 2 13 | } 14 | 15 | pub fn function_4() { 16 | glacier_demo_module_c.function_5() - 1 17 | } 18 | 19 | @target(erlang) 20 | import gleam/string 21 | 22 | @target(erlang) 23 | pub fn a() { 24 | string.inspect("a") 25 | } 26 | 27 | @target(javascript) 28 | import gleam/string 29 | 30 | @target(javascript) 31 | pub fn a() { 32 | string.inspect("a") 33 | } 34 | -------------------------------------------------------------------------------- /src/glacier_demo/glacier_demo_module_c.gleam: -------------------------------------------------------------------------------- 1 | import glacier_demo/glacier_demo_module_d 2 | 3 | pub fn function_5() { 4 | glacier_demo_module_d.main() 5 | 5 6 | } 7 | 8 | pub fn function_6() { 9 | 6 10 | } 11 | 12 | pub fn function_a() { 13 | "a" 14 | } 15 | -------------------------------------------------------------------------------- /src/glacier_demo/glacier_demo_module_d.gleam: -------------------------------------------------------------------------------- 1 | // import in_comment 2 | 3 | pub fn main() { 4 | "import in_string a" 5 | } 6 | -------------------------------------------------------------------------------- /src/glacier_demo/glacier_demo_module_e.gleam: -------------------------------------------------------------------------------- 1 | // import in_comment 2 | 3 | pub fn main() { 4 | Nil 5 | } 6 | -------------------------------------------------------------------------------- /src/glacier_demo/glacier_demo_module_unqualifying.gleam: -------------------------------------------------------------------------------- 1 | import glacier_demo/glacier_demo_module_c.{function_5} 2 | 3 | pub fn proxied_function_5() { 4 | function_5() 5 | } 6 | -------------------------------------------------------------------------------- /src/glacier_demo/glacier_demo_references_a.gleam: -------------------------------------------------------------------------------- 1 | import glacier_demo/glacier_demo_module_a 2 | 3 | pub fn function_1() { 4 | glacier_demo_module_a.function_1() 5 | } 6 | 7 | pub fn function_2() { 8 | 2 9 | } 10 | -------------------------------------------------------------------------------- /src/glacier_demo/hello.gleam: -------------------------------------------------------------------------------- 1 | import glacier_demo/glacier_demo_module_a 2 | 3 | pub fn function_1() { 4 | glacier_demo_module_a.function_1() 5 | "hello" 6 | } 7 | -------------------------------------------------------------------------------- /src/glacier_demo/other.txt: -------------------------------------------------------------------------------- 1 | //// This should not be handled as a gleam file because of the .txt extension 2 | 3 | import glacier_demo/glacier_demo_module_a 4 | 5 | pub fn function_1() { 6 | glacier_demo_module_a.function_1() 7 | } 8 | 9 | pub fn function_2() { 10 | 2 11 | } 12 | -------------------------------------------------------------------------------- /src/glacier_ffi.erl: -------------------------------------------------------------------------------- 1 | -module(glacier_ffi). 2 | 3 | -export([start_file_change_watcher/1, get_cwd_as_binary/0, find_files_recursive/2]). 4 | 5 | get_cwd() -> 6 | {ok, Cwd} = file:get_cwd(), 7 | Cwd. 8 | 9 | get_cwd_as_binary() -> 10 | iolist_to_binary(get_cwd()). 11 | 12 | start_file_change_watcher(FileChangeHandlerFn) -> 13 | watch_directory(fs_src_watcher, src_module_kind, FileChangeHandlerFn), 14 | % watch_directory(fs_test_watcher, test_module_kind, FileChangeHandlerFn), 15 | timer:sleep(infinity), 16 | nil. 17 | 18 | process_file_update_and_loop(ModuleKind, WatchPath, FileChangeHandlerFn) -> 19 | receive 20 | {_Pid, {fs, file_event}, {FilePath, Changes}} -> 21 | % By stripping spaces we just do not support them at all in source 22 | % file names. This makes handling across targets and in regards do 23 | % module names and file name mapping a lot easier: 24 | FilePathNoSpaces = re:replace(FilePath, "\\s+", "", [global, {return, list}]), 25 | % Detect the parent dir type: 26 | CwdBinary = get_cwd_as_binary(), 27 | SrcDir = <>, 28 | TestDir = <>, 29 | IsSrcModuleKind = string:find(FilePath, SrcDir) =:= FilePathNoSpaces, 30 | IsTestModuleKind = string:find(FilePathNoSpaces, TestDir) =:= FilePathNoSpaces, 31 | % Make sure files are actually existing and end with .gleam: 32 | FileExists = filelib:is_regular(FilePathNoSpaces), 33 | IsGleamFile = 34 | gleam_stdlib:string_ends_with(list_to_bitstring(FilePathNoSpaces), <<".gleam"/utf8>>), 35 | MatchingEvent = 36 | lists:member(modified, Changes) 37 | orelse lists:member(created, Changes) 38 | orelse lists:member(renamed, Changes), 39 | case {FileExists, IsGleamFile, MatchingEvent, IsSrcModuleKind, IsTestModuleKind} of 40 | {true, true, true, true, _} -> 41 | % io:format("\n~p: ~p\n\n", [Changes, FilePathNoSpaces]), 42 | FileChangeHandlerFn([{src_module_kind, iolist_to_binary(FilePathNoSpaces)}]); 43 | {true, true, true, _, true} -> 44 | % io:format("\n~p: ~p \n\n", [Changes, FilePathNoSpaces]), 45 | FileChangeHandlerFn([{test_module_kind, iolist_to_binary(FilePathNoSpaces)}]); 46 | {_, _, _, _, _} -> 47 | nil 48 | end, 49 | process_file_update_and_loop(ModuleKind, WatchPath, FileChangeHandlerFn); 50 | _Any -> 51 | process_file_update_and_loop(ModuleKind, WatchPath, FileChangeHandlerFn) 52 | end. 53 | 54 | watch_directory(WatcherAtom, ModuleKind, FileChangeHandlerFn) -> 55 | Cwd = get_cwd(), 56 | WatchPath = Cwd, 57 | % SubPath = case ModuleKind of 58 | % src_module_kind -> "src"; 59 | % test_module_kind -> "test" 60 | % end, 61 | % WatchPath = filename:join([Cwd, SubPath]), 62 | % io:format("~p ", [WatchPath]), 63 | fs:start_link(WatcherAtom, WatchPath), 64 | fs:subscribe(WatcherAtom), 65 | process_file_update_and_loop(ModuleKind, WatchPath, FileChangeHandlerFn), 66 | nil. 67 | 68 | % Derived from gleeunit_ffi.erl 69 | find_files_recursive(In, Pattern) -> 70 | Results = filelib:wildcard(binary_to_list(Pattern), binary_to_list(In)), 71 | lists:map(fun list_to_binary/1, Results). 72 | -------------------------------------------------------------------------------- /src/glacier_ffi.mjs: -------------------------------------------------------------------------------- 1 | import { SrcModuleKind, TestModuleKind } from "./glacier.mjs"; 2 | import * as Gleam from "./gleam.mjs"; 3 | import child_process from 'node:child_process'; 4 | import fs from "node:fs"; 5 | import fs_promises from "node:fs/promises"; 6 | import path from "node:path"; 7 | import process from 'node:process'; 8 | 9 | const file_change_watcher_debounce_interval_in_ms = 100; 10 | const Nil = undefined; // Translates to `Nil` in Gleam 11 | 12 | ['SIGINT', 'SIGTERM', 'SIGQUIT'] 13 | .forEach(signal => process.on(signal, function () { 14 | console.log("\n🏔 Gracefully shutting down Glacier from SIGINT (Ctrl-C)!"); 15 | process.exit(0); 16 | })); 17 | 18 | process.on('warning', function (e) { 19 | console.warn(e.stack); 20 | }); 21 | 22 | export const start_args = function () { 23 | return Gleam.List.fromArray(process.argv.slice(2)); 24 | }; 25 | 26 | export const cwd = function () { 27 | return process.cwd(); 28 | }; 29 | 30 | export const start_file_change_watcher = function (file_change_handler_fn) { 31 | let file_change_handler_timeout_id = null; 32 | let file_change_handler_collection = []; 33 | const watch_directory = async function (directory, observed_events, file_change_handler_fn, module_kind) { 34 | let watcher = undefined; 35 | if (globalThis.Deno) { 36 | watcher = Deno.watchFs([directory], { recursive: true }); 37 | } else { 38 | watcher = fs_promises.watch(directory, { persistent: true, recursive: true }); 39 | } 40 | for await (const event of watcher) { 41 | const event_kind = function () { 42 | if (globalThis.Deno) { 43 | return event.kind;; 44 | } { 45 | return event.eventType; 46 | } 47 | }(); 48 | const touched_file = function () { 49 | if (globalThis.Deno) { 50 | return event.paths[0]; 51 | } { 52 | return directory + "/" + event.filename; 53 | } 54 | }(); 55 | if (observed_events.includes(event_kind) && touched_file.endsWith(".gleam")) { 56 | if (file_change_handler_timeout_id !== null) { 57 | clearTimeout(file_change_handler_timeout_id); 58 | } 59 | file_change_handler_collection.push([module_kind, touched_file]); 60 | file_change_handler_timeout_id = setTimeout(function () { 61 | // NodeJS fs.watch is prone to report the same change twice, thus we need to distinct the changes: 62 | let distinct_file_change_handler_collection = [...new Set(file_change_handler_collection)]; 63 | // As we collect file on a delay set by file_change_watcher_debounce_interval_in_ms, 64 | // they could be gone once we want to handle them: 65 | distinct_file_change_handler_collection = distinct_file_change_handler_collection.filter(function (file_info) { 66 | let absolute_file_name = file_info[1]; 67 | absolute_file_name = absolute_file_name.replace(/\s/g, ''); 68 | return file_exists(absolute_file_name); 69 | }); 70 | if (distinct_file_change_handler_collection.length > 0) { 71 | file_change_handler_fn(Gleam.List.fromArray(distinct_file_change_handler_collection)); 72 | file_change_handler_timeout_id = null; 73 | file_change_handler_collection = []; 74 | } 75 | }, file_change_watcher_debounce_interval_in_ms); 76 | } 77 | } 78 | }; 79 | watch_directory(cwd() + "/src", ["change", "rename", "modify"], file_change_handler_fn, new SrcModuleKind()); 80 | watch_directory(cwd() + "/test", ["change", "rename", "modify"], file_change_handler_fn, new TestModuleKind()); 81 | 82 | return Nil; 83 | }; 84 | 85 | export const read_file = function (absolute_file_name) { 86 | try { 87 | const data = fs.readFileSync(absolute_file_name, 'utf8'); 88 | return new Gleam.Ok(data); 89 | } catch (err) { 90 | // console.error({"Could not read file" : err}); 91 | return new Gleam.Error(Nil); 92 | } 93 | }; 94 | 95 | export const file_exists = function (absolute_file_name) { 96 | if (fs.existsSync(absolute_file_name)) { 97 | return true; 98 | } 99 | return false; 100 | }; 101 | 102 | export const find_files_recursive_by_exts = function (directory, file_exts_list) { 103 | file_exts_list = file_exts_list.toArray(); 104 | let files = []; 105 | /* mut files */ const detect_files_recursive = function (directory) { 106 | const files_in_directory = fs.readdirSync(directory); 107 | for (const file of files_in_directory) { 108 | let absolute_path = path.join(directory, file); 109 | absolute_path = absolute_path.replace(/\s/g, ''); 110 | if (fs.statSync(absolute_path).isDirectory()) { 111 | detect_files_recursive(absolute_path); 112 | } else if (absolute_path.endsWith(file_exts_list) && file_exists(absolute_path)) { 113 | files.push(absolute_path); 114 | } 115 | } 116 | }; 117 | detect_files_recursive(directory); 118 | return Gleam.List.fromArray(files); 119 | }; 120 | 121 | export const shell_exec_print = async function (gleam_list_of_graphemes) { 122 | if (globalThis.Deno) { 123 | Deno.run({ 124 | cmd: ["gleam", ...gleam_list_of_graphemes.toArray()] 125 | }); 126 | } else { 127 | const cmd = "gleam " + gleam_list_of_graphemes.toArray().join(" "); 128 | let { stdout } = await node_shell_exec(cmd); 129 | for (let line of stdout.split('\n')) { 130 | console.log(`${line}`); 131 | } 132 | } 133 | } 134 | 135 | const node_shell_exec = async function (cmd) { 136 | return new Promise(function (resolve, reject) { 137 | child_process.exec(cmd, (err, stdout, stderr) => { 138 | if (err) { 139 | reject(err); 140 | } else { 141 | resolve({ stdout, stderr }); 142 | } 143 | }); 144 | }); 145 | } 146 | -------------------------------------------------------------------------------- /test/glacier_demo/glacier_demo_module_a_test.gleam: -------------------------------------------------------------------------------- 1 | import glacier/should 2 | import glacier_demo/glacier_demo_module_a 3 | 4 | pub fn function_1_test() { 5 | glacier_demo_module_a.function_1() 6 | |> should.equal(1) 7 | } 8 | 9 | pub fn function_2_test() { 10 | glacier_demo_module_a.function_2() 11 | |> should.equal(2) 12 | } 13 | -------------------------------------------------------------------------------- /test/glacier_demo/glacier_demo_module_b_test.gleam: -------------------------------------------------------------------------------- 1 | import glacier/should 2 | import glacier_demo/glacier_demo_module_b 3 | 4 | pub fn function_3_test() { 5 | glacier_demo_module_b.function_3() 6 | |> should.equal(3) 7 | } 8 | 9 | pub fn function_4_test() { 10 | glacier_demo_module_b.function_4() 11 | |> should.equal(4) 12 | } 13 | -------------------------------------------------------------------------------- /test/glacier_demo/glacier_demo_module_c_test.gleam: -------------------------------------------------------------------------------- 1 | import glacier/should 2 | import glacier_demo/glacier_demo_module_c 3 | 4 | pub fn function_5_test() { 5 | glacier_demo_module_c.function_5() 6 | |> should.equal(5) 7 | } 8 | 9 | pub fn function_6_test() { 10 | glacier_demo_module_c.function_6() 11 | |> should.equal(6) 12 | } 13 | 14 | pub fn function_6b_test() { 15 | glacier_demo_module_c.function_6() 16 | |> should.equal(6) 17 | } 18 | -------------------------------------------------------------------------------- /test/glacier_demo/glacier_demo_module_gleeunit_should.gleam: -------------------------------------------------------------------------------- 1 | import glacier_demo/glacier_demo_module_c 2 | import gleeunit/should 3 | 4 | pub fn function_5_test() { 5 | glacier_demo_module_c.function_5() 6 | |> should.equal(5) 7 | } 8 | 9 | pub fn function_6_test() { 10 | glacier_demo_module_c.function_6() 11 | |> should.equal(6) 12 | } 13 | 14 | pub fn function_6b_test() { 15 | glacier_demo_module_c.function_6() 16 | |> should.equal(6) 17 | } 18 | -------------------------------------------------------------------------------- /test/glacier_test.gleam: -------------------------------------------------------------------------------- 1 | import glacier 2 | 3 | pub fn main() { 4 | glacier.main() 5 | } 6 | --------------------------------------------------------------------------------