git.iko.soy/rowtype-yoga/purescript-docs-search
on master help sign in
66 files changed+9059−1884
since splitsnapshots
unifiedsplit
.github/workflows/build.yml+44 .github/workflows/release.yml+55 .gitignore+2 CHANGELOG.md+89 README.md+40−8 ci-npmrc.txt+4 package-lock.json+2285 package.json+55 packages.dhall+174−44 preview.png release.sh+24 spago.dhall+53−31 src/Docs/Search/App.purs+260 src/Docs/Search/App/SearchField.purs+184 src/Docs/Search/App/SearchResults.purs+715 src/Docs/Search/App/Sidebar.purs+218 src/Docs/Search/BrowserEngine.js+30 src/Docs/Search/BrowserEngine.purs+130 src/Docs/Search/Config.purs+85 src/Docs/Search/Declarations.purs+327 src/Docs/Search/DocsJson.purs+224 src/Docs/Search/Engine.purs+167 src/Docs/Search/Extra.purs+47 src/Docs/Search/IndexBuilder.js+13 src/Docs/Search/IndexBuilder.purs+450 src/Docs/Search/Interactive.purs+340 src/Docs/Search/Loader.js+26 src/Docs/Search/Loader.purs+38 src/Docs/Search/Main.purs+163 src/Docs/Search/Meta.purs+21 src/Docs/Search/ModuleIndex.js+24 src/Docs/Search/ModuleIndex.purs+126 src/Docs/Search/ModuleParser.purs+50 src/Docs/Search/NodeEngine.purs+48 src/Docs/Search/PackageIndex.purs+94 src/Docs/Search/Score.purs+52 src/Docs/Search/SearchResult.purs+85 src/Docs/Search/Terminal.purs+23 src/Spago/Search/TypeDecoder.purs → src/Docs/Search/TypeDecoder.purs+150−89 src/Docs/Search/TypeIndex.js+26 src/Docs/Search/TypeIndex.purs+111 src/Docs/Search/TypePrinter.purs+169 src/Docs/Search/TypeQuery.purs+440 src/Docs/Search/TypeShape.js+14 src/Docs/Search/TypeShape.purs+148 src/Docs/Search/Types.purs+104 src/Docs/Search/URIHash.js+17 src/Docs/Search/URIHash.purs+38 src/Spago/Search/App.purs−94 src/Spago/Search/App/SearchField.purs−82 src/Spago/Search/App/SearchResults.purs−357 src/Spago/Search/DocsJson.js−24 src/Spago/Search/DocsJson.purs−190 src/Spago/Search/Extra.purs−9 src/Spago/Search/Index.purs−231 src/Spago/Search/IndexBuilder.purs−99 src/Spago/Search/TypeQuery.purs−119 test/Main.purs+22−414 test/Test/Declarations.purs+41 test/Test/Extra.purs+24 test/Test/IndexBuilder.purs+20 test/Test/ModuleIndex.purs+23 test/Test/ModuleParser.purs+61 test/Test/TypeJson.purs+300 test/Test/TypeQuery.purs+518−93 test/Test/UI.purs+68
.github/workflows/build.ymlnew file+44
@@ -0,0 +1,44 @@
1+name: build
2+on:
3+ push:
4+ branches: [master]
5+ pull_request:
6+ branches: [master]
7+jobs:
8+ build:
9+ runs-on: ubuntu-latest
10+ steps:
11+ - uses: actions/checkout@v2
12+
13+ - uses: actions/setup-node@v2
14+ with:
15+ node-version: "18"
16+
17+ - uses: purescript-contrib/setup-purescript@main
18+
19+ - uses: actions/cache@v2
20+ with:
21+ path: ~/.npm
22+ key: ${{ runner.os }}-node-${{ hashFiles('./**/package-lock.json') }}
23+ restore-keys: |
24+ ${{ runner.os }}-node-
25+
26+ - name: Cache PureScript dependencies
27+ uses: actions/cache@v2
28+ with:
29+ key: ${{ runner.os }}-spago-${{ hashFiles('./**/*.dhall') }}
30+ path: |
31+ .spago
32+ output
33+
34+ - name: Install
35+ run: |
36+ npm ci
37+ spago install
38+
39+ - name: Build
40+ run: |
41+ npm run build
42+ npm test
43+ spago docs --no-search
44+ ./dist/purescript-docs-search.cjs build-index
.github/workflows/release.ymlnew file+55
@@ -0,0 +1,55 @@
1+name: release
2+on:
3+ push:
4+ tags:
5+ - 'v*.*.*'
6+jobs:
7+ release:
8+ runs-on: ubuntu-latest
9+ steps:
10+ - uses: actions/checkout@v2
11+
12+ - uses: actions/setup-node@v2
13+ with:
14+ node-version: "18"
15+
16+ - uses: purescript-contrib/setup-purescript@main
17+
18+ - uses: actions/cache@v2
19+ with:
20+ path: ~/.npm
21+ key: ${{ runner.os }}-node-${{ hashFiles('./**/package-lock.json') }}
22+ restore-keys: |
23+ ${{ runner.os }}-node-
24+
25+ - name: Cache PureScript dependencies
26+ uses: actions/cache@v2
27+ with:
28+ key: ${{ runner.os }}-spago-${{ hashFiles('./**/*.dhall') }}
29+ path: |
30+ .spago
31+ output
32+
33+ - name: Build
34+ run: |
35+ npm ci
36+ spago install
37+ npm run build
38+
39+ - name: Release
40+ uses: softprops/action-gh-release@v1
41+ with:
42+ files: |
43+ dist/docs-search-app.js
44+ dist/purescript-docs-search.cjs
45+ env:
46+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47+
48+ - name: Publish to NPM
49+ shell: bash
50+ env:
51+ NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
52+ run: |
53+ mv ci-npmrc.txt .npmrc
54+ npm ci
55+ npm publish --non-interactive --access public
.gitignore+2
@@ -8,3 +8,5 @@
88/.purs*
99/.psa*
1010/.spago/
11+/dist/
12+/.cache/
CHANGELOG.mdnew file+89
@@ -0,0 +1,89 @@
1+# Changelog
2+
3+All notable changes to this project will be documented in this file.
4+
5+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+## [0.0.12 - 2022-08-31]
9+
10+Changes:
11+- Package structure change: `dist/purescript-docs-search.cjs` has been moved to `dist/purescript-docs-search.cjs` due to NodeJS restrictions for `type: module` packages.
12+- Improve output for data and newtype constuctors ([#58](https://github.com/purescript/purescript-docs-search/pull/58))
13+
14+New features:
15+- Docs for each CLI option
16+- New `--source-files` option that allows to specify `.purs` sources (see below)
17+
18+Bugfixes:
19+- Include modules with only re-exports in the module list ([#62](https://github.com/purescript/purescript-docs-search/issues/62))
20+- Fix `docs.json` parser for some types
21+
22+## [0.0.11 - 2021-04-15]
23+
24+Changes:
25+- Adapt JSON API for use with PureScript v0.14.
26+
27+## [0.0.10 - 2020-08-11]
28+
29+New features:
30+- Add `--package-name` flag to allow passing local package name.
31+
32+Changes:
33+- Group modules by default.
34+- Add local package modules and `Prim*` modules to the sidebar.
35+- UI tests, using [purescript-toppokki](https://github.com/justinwoo/purescript-toppokki/).
36+
37+## [0.0.9 - 2020-07-29]
38+
39+New features:
40+- Implement sorting by package popularity for declarations.
41+- Add app version info to the footer.
42+- Scroll to document top when search bar gets focus.
43+- Group modules by package in the sidebar (#34)
44+
45+Bugfixes:
46+- Fix CLI autocompleter (now works correctly with capital letters).
47+
48+## [0.0.8 - 2020-01-18]
49+
50+Skipped due to failed deployment.
51+
52+## [0.0.7 - 2020-01-18]
53+
54+Changes:
55+- Consider something a builtin when there's no `sourceSpan` (#32)
56+
57+## [0.0.6 - 2019-11-29]
58+
59+New features:
60+- Allow using the app as a search engine (show search term in URI hash) (#26)
61+
62+## [0.0.5 - 2019-09-21]
63+
64+New features:
65+- Render docs as markdown (#15)
66+- Show help for each CLI command (#17)
67+- Add packages to the search index (#16)
68+- Sort search results by popularity (based on number of package reverse dependencies).
69+- Add `--no-patch` flag to `build-index` command.
70+
71+Bugfixes:
72+- Fix decoding of kind annotations in `forall`s (#17)
73+- Fix rendering for variable-parametrized records (e.g. `Record a`) and type-level strings.
74+
75+## [0.0.4] - 2019-07-25
76+
77+New features:
78+- Reduce the package size by using a minifier.
79+- Add `version` command to print the app version.
80+- **S** hotkey now `.select()`s everything in the search field, insetead of just `.focus()`ing (#11).
81+
82+## [0.0.3] - 2019-07-23
83+
84+Bugfixes:
85+- Fix stack safety issue (#8).
86+
87+## [0.0.2] - 2019-07-21
88+
89+## [0.0.1] - 2019-07-20
README.md+40−8
@@ -1,16 +1,48 @@
1−# spago-search
1+# purescript-docs-search
22
3−An app that adds search capabilities to generated documentation for purescript code.
3+[![Build status](https://github.com/purescript/purescript-docs-search/actions/workflows/build.yml/badge.svg)](https://github.com/purescript/purescript-docs-search/actions/workflows/build.yml)
44
5−The goal is to replicate all functionality of pursuit, including querying by type.
5+An app that adds search capabilities to generated documentation for PureScript code.
66
7−See [#89](https://github.com/spacchetti/spago/issues/89).
7+It supports nearly-all functionality of [Pursuit](https://github.com/purescript/pursuit), including querying by type.
88
9+## Installing
910
11+When using [spago](https://github.com/spacchetti/spago), you don't need to install this app manually: run `spago docs` or `spago search`.
12+
13+Otherwise, use NPM (`npm install purescript-docs-search`) or [npx](https://github.com/npm/npx): `npx purescript-docs-search`.
14+
15+## Usage
16+
17+There are two usage scenarios:
18+
19+### Patching static documentation
20+
21+Use `purescript-docs-search build-index` command to patch HTML files located in `generated-docs/html`. You then will be able to search for declarations or types:
22+
23+![Preview](preview.png)
24+
25+The user interface of the app is optimised for keyboard-only use.
26+
27+**S** hotkey can be used to focus on the search field, **Escape** can be used to leave it. Pressing **Escape** twice will close the search results listing.
28+
29+### Using the CLI
30+
31+Running `purescript-docs-search` within a project directory will open an interactive command-line session.
32+
33+Note that unlike in Pursuit, most relevant results will appear last.
34+
35+A quick demo:
36+
37+[![asciicast](https://asciinema.org/a/Hexie5JoWjlAqLqv2IgafIdb9.svg)](https://asciinema.org/a/Hexie5JoWjlAqLqv2IgafIdb9)
38+
39+You may notice that the CLI offers slightly better results than the web interface. This is a performance tradeoff.
40+
41+## Development
1042
1143```
12−spago build
13−spago docs
14−spago bundle-app -m Spago.Search.App --to generated-docs/spago-search-app.js
15−spago run -m Spago.Seach.IndexBuilder
44+npm install
45+npm run build # or build-dev to skip JS compression stage
1646```
47+
48+Use `spago docs --no-search && ./dist/purescript-docs-search.cjs build-index` to generate the docs and patch them using the local version of the app.
ci-npmrc.txtnew file+4
@@ -0,0 +1,4 @@
1+//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
2+registry=https://registry.npmjs.org/
3+always-auth=true
4+
package-lock.jsonnew file+2285
@@ -0,0 +1,2285 @@
1+{
2+ "name": "purescript-docs-search",
3+ "version": "0.0.11",
4+ "lockfileVersion": 2,
5+ "requires": true,
6+ "packages": {
7+ "": {
8+ "name": "purescript-docs-search",
9+ "version": "0.0.11",
10+ "license": "BSD-3-Clause",
11+ "dependencies": {
12+ "punycode": "^2.1.1"
13+ },
14+ "bin": {
15+ "purescript-docs-search": "dist/purescript-docs-search.js"
16+ },
17+ "devDependencies": {
18+ "esbuild": "^0.11.10",
19+ "glob": "^7.1.6",
20+ "markdown-it": "^12.0.4",
21+ "puppeteer": "^8.0.0",
22+ "spago": "^0.20.9"
23+ }
24+ },
25+ "node_modules/@gar/promisify": {
26+ "version": "1.1.3",
27+ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
28+ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
29+ "dev": true
30+ },
31+ "node_modules/@npmcli/fs": {
32+ "version": "1.1.1",
33+ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
34+ "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==",
35+ "dev": true,
36+ "dependencies": {
37+ "@gar/promisify": "^1.0.1",
38+ "semver": "^7.3.5"
39+ }
40+ },
41+ "node_modules/@npmcli/move-file": {
42+ "version": "1.1.2",
43+ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
44+ "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
45+ "dev": true,
46+ "dependencies": {
47+ "mkdirp": "^1.0.4",
48+ "rimraf": "^3.0.2"
49+ },
50+ "engines": {
51+ "node": ">=10"
52+ }
53+ },
54+ "node_modules/@tootallnate/once": {
55+ "version": "1.1.2",
56+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
57+ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
58+ "dev": true,
59+ "engines": {
60+ "node": ">= 6"
61+ }
62+ },
63+ "node_modules/@types/node": {
64+ "version": "14.14.39",
65+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz",
66+ "integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw==",
67+ "dev": true,
68+ "optional": true
69+ },
70+ "node_modules/@types/yauzl": {
71+ "version": "2.9.1",
72+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
73+ "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
74+ "dev": true,
75+ "optional": true,
76+ "dependencies": {
77+ "@types/node": "*"
78+ }
79+ },
80+ "node_modules/agent-base": {
81+ "version": "6.0.2",
82+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
83+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
84+ "dev": true,
85+ "dependencies": {
86+ "debug": "4"
87+ },
88+ "engines": {
89+ "node": ">= 6.0.0"
90+ }
91+ },
92+ "node_modules/agentkeepalive": {
93+ "version": "4.2.1",
94+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz",
95+ "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==",
96+ "dev": true,
97+ "dependencies": {
98+ "debug": "^4.1.0",
99+ "depd": "^1.1.2",
100+ "humanize-ms": "^1.2.1"
101+ },
102+ "engines": {
103+ "node": ">= 8.0.0"
104+ }
105+ },
106+ "node_modules/aggregate-error": {
107+ "version": "3.1.0",
108+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
109+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
110+ "dev": true,
111+ "dependencies": {
112+ "clean-stack": "^2.0.0",
113+ "indent-string": "^4.0.0"
114+ },
115+ "engines": {
116+ "node": ">=8"
117+ }
118+ },
119+ "node_modules/argparse": {
120+ "version": "2.0.1",
121+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
122+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
123+ "dev": true
124+ },
125+ "node_modules/balanced-match": {
126+ "version": "1.0.0",
127+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
128+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
129+ "dev": true
130+ },
131+ "node_modules/base64-js": {
132+ "version": "1.5.1",
133+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
134+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
135+ "dev": true,
136+ "funding": [
137+ {
138+ "type": "github",
139+ "url": "https://github.com/sponsors/feross"
140+ },
141+ {
142+ "type": "patreon",
143+ "url": "https://www.patreon.com/feross"
144+ },
145+ {
146+ "type": "consulting",
147+ "url": "https://feross.org/support"
148+ }
149+ ]
150+ },
151+ "node_modules/bl": {
152+ "version": "4.1.0",
153+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
154+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
155+ "dev": true,
156+ "dependencies": {
157+ "buffer": "^5.5.0",
158+ "inherits": "^2.0.4",
159+ "readable-stream": "^3.4.0"
160+ }
161+ },
162+ "node_modules/bl/node_modules/buffer": {
163+ "version": "5.7.1",
164+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
165+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
166+ "dev": true,
167+ "funding": [
168+ {
169+ "type": "github",
170+ "url": "https://github.com/sponsors/feross"
171+ },
172+ {
173+ "type": "patreon",
174+ "url": "https://www.patreon.com/feross"
175+ },
176+ {
177+ "type": "consulting",
178+ "url": "https://feross.org/support"
179+ }
180+ ],
181+ "dependencies": {
182+ "base64-js": "^1.3.1",
183+ "ieee754": "^1.1.13"
184+ }
185+ },
186+ "node_modules/bl/node_modules/readable-stream": {
187+ "version": "3.6.0",
188+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
189+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
190+ "dev": true,
191+ "dependencies": {
192+ "inherits": "^2.0.3",
193+ "string_decoder": "^1.1.1",
194+ "util-deprecate": "^1.0.1"
195+ },
196+ "engines": {
197+ "node": ">= 6"
198+ }
199+ },
200+ "node_modules/brace-expansion": {
201+ "version": "1.1.11",
202+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
203+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
204+ "dev": true,
205+ "dependencies": {
206+ "balanced-match": "^1.0.0",
207+ "concat-map": "0.0.1"
208+ }
209+ },
210+ "node_modules/buffer-crc32": {
211+ "version": "0.2.13",
212+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
213+ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
214+ "dev": true,
215+ "engines": {
216+ "node": "*"
217+ }
218+ },
219+ "node_modules/cacache": {
220+ "version": "15.3.0",
221+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
222+ "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==",
223+ "dev": true,
224+ "dependencies": {
225+ "@npmcli/fs": "^1.0.0",
226+ "@npmcli/move-file": "^1.0.1",
227+ "chownr": "^2.0.0",
228+ "fs-minipass": "^2.0.0",
229+ "glob": "^7.1.4",
230+ "infer-owner": "^1.0.4",
231+ "lru-cache": "^6.0.0",
232+ "minipass": "^3.1.1",
233+ "minipass-collect": "^1.0.2",
234+ "minipass-flush": "^1.0.5",
235+ "minipass-pipeline": "^1.2.2",
236+ "mkdirp": "^1.0.3",
237+ "p-map": "^4.0.0",
238+ "promise-inflight": "^1.0.1",
239+ "rimraf": "^3.0.2",
240+ "ssri": "^8.0.1",
241+ "tar": "^6.0.2",
242+ "unique-filename": "^1.1.1"
243+ },
244+ "engines": {
245+ "node": ">= 10"
246+ }
247+ },
248+ "node_modules/cacache/node_modules/chownr": {
249+ "version": "2.0.0",
250+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
251+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
252+ "dev": true,
253+ "engines": {
254+ "node": ">=10"
255+ }
256+ },
257+ "node_modules/chownr": {
258+ "version": "1.1.4",
259+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
260+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
261+ "dev": true
262+ },
263+ "node_modules/clean-stack": {
264+ "version": "2.2.0",
265+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
266+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
267+ "dev": true,
268+ "engines": {
269+ "node": ">=6"
270+ }
271+ },
272+ "node_modules/concat-map": {
273+ "version": "0.0.1",
274+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
275+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
276+ "dev": true
277+ },
278+ "node_modules/debug": {
279+ "version": "4.3.4",
280+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
281+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
282+ "dev": true,
283+ "dependencies": {
284+ "ms": "2.1.2"
285+ },
286+ "engines": {
287+ "node": ">=6.0"
288+ },
289+ "peerDependenciesMeta": {
290+ "supports-color": {
291+ "optional": true
292+ }
293+ }
294+ },
295+ "node_modules/depd": {
296+ "version": "1.1.2",
297+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
298+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
299+ "dev": true,
300+ "engines": {
301+ "node": ">= 0.6"
302+ }
303+ },
304+ "node_modules/devtools-protocol": {
305+ "version": "0.0.854822",
306+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz",
307+ "integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==",
308+ "dev": true
309+ },
310+ "node_modules/encoding": {
311+ "version": "0.1.13",
312+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
313+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
314+ "dev": true,
315+ "optional": true,
316+ "dependencies": {
317+ "iconv-lite": "^0.6.2"
318+ }
319+ },
320+ "node_modules/end-of-stream": {
321+ "version": "1.4.4",
322+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
323+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
324+ "dev": true,
325+ "dependencies": {
326+ "once": "^1.4.0"
327+ }
328+ },
329+ "node_modules/entities": {
330+ "version": "2.1.0",
331+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
332+ "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
333+ "dev": true,
334+ "funding": {
335+ "url": "https://github.com/fb55/entities?sponsor=1"
336+ }
337+ },
338+ "node_modules/err-code": {
339+ "version": "2.0.3",
340+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
341+ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
342+ "dev": true
343+ },
344+ "node_modules/esbuild": {
345+ "version": "0.11.11",
346+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.11.tgz",
347+ "integrity": "sha512-iq5YdV63vY/nUAFIvY92BXVkYjMbOchnofLKoLKMPZIa4uuIJAJG9WRA+ZRjQBZbrsORUwvZcANeG2d3p46PJQ==",
348+ "dev": true,
349+ "hasInstallScript": true,
350+ "bin": {
351+ "esbuild": "bin/esbuild"
352+ }
353+ },
354+ "node_modules/extract-zip": {
355+ "version": "2.0.1",
356+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
357+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
358+ "dev": true,
359+ "dependencies": {
360+ "debug": "^4.1.1",
361+ "get-stream": "^5.1.0",
362+ "yauzl": "^2.10.0"
363+ },
364+ "bin": {
365+ "extract-zip": "cli.js"
366+ },
367+ "engines": {
368+ "node": ">= 10.17.0"
369+ },
370+ "optionalDependencies": {
371+ "@types/yauzl": "^2.9.1"
372+ }
373+ },
374+ "node_modules/fd-slicer": {
375+ "version": "1.1.0",
376+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
377+ "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
378+ "dev": true,
379+ "dependencies": {
380+ "pend": "~1.2.0"
381+ }
382+ },
383+ "node_modules/fs-constants": {
384+ "version": "1.0.0",
385+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
386+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
387+ "dev": true
388+ },
389+ "node_modules/fs-minipass": {
390+ "version": "2.1.0",
391+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
392+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
393+ "dev": true,
394+ "dependencies": {
395+ "minipass": "^3.0.0"
396+ },
397+ "engines": {
398+ "node": ">= 8"
399+ }
400+ },
401+ "node_modules/fs.realpath": {
402+ "version": "1.0.0",
403+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
404+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
405+ "dev": true
406+ },
407+ "node_modules/get-stream": {
408+ "version": "5.2.0",
409+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
410+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
411+ "dev": true,
412+ "dependencies": {
413+ "pump": "^3.0.0"
414+ },
415+ "engines": {
416+ "node": ">=8"
417+ },
418+ "funding": {
419+ "url": "https://github.com/sponsors/sindresorhus"
420+ }
421+ },
422+ "node_modules/glob": {
423+ "version": "7.1.6",
424+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
425+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
426+ "dev": true,
427+ "dependencies": {
428+ "fs.realpath": "^1.0.0",
429+ "inflight": "^1.0.4",
430+ "inherits": "2",
431+ "minimatch": "^3.0.4",
432+ "once": "^1.3.0",
433+ "path-is-absolute": "^1.0.0"
434+ },
435+ "engines": {
436+ "node": "*"
437+ },
438+ "funding": {
439+ "url": "https://github.com/sponsors/isaacs"
440+ }
441+ },
442+ "node_modules/http-cache-semantics": {
443+ "version": "4.1.0",
444+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
445+ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
446+ "dev": true
447+ },
448+ "node_modules/http-proxy-agent": {
449+ "version": "4.0.1",
450+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
451+ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
452+ "dev": true,
453+ "dependencies": {
454+ "@tootallnate/once": "1",
455+ "agent-base": "6",
456+ "debug": "4"
457+ },
458+ "engines": {
459+ "node": ">= 6"
460+ }
461+ },
462+ "node_modules/https-proxy-agent": {
463+ "version": "5.0.0",
464+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
465+ "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
466+ "dev": true,
467+ "dependencies": {
468+ "agent-base": "6",
469+ "debug": "4"
470+ },
471+ "engines": {
472+ "node": ">= 6"
473+ }
474+ },
475+ "node_modules/humanize-ms": {
476+ "version": "1.2.1",
477+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
478+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
479+ "dev": true,
480+ "dependencies": {
481+ "ms": "^2.0.0"
482+ }
483+ },
484+ "node_modules/iconv-lite": {
485+ "version": "0.6.3",
486+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
487+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
488+ "dev": true,
489+ "optional": true,
490+ "dependencies": {
491+ "safer-buffer": ">= 2.1.2 < 3.0.0"
492+ },
493+ "engines": {
494+ "node": ">=0.10.0"
495+ }
496+ },
497+ "node_modules/ieee754": {
498+ "version": "1.2.1",
499+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
500+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
501+ "dev": true,
502+ "funding": [
503+ {
504+ "type": "github",
505+ "url": "https://github.com/sponsors/feross"
506+ },
507+ {
508+ "type": "patreon",
509+ "url": "https://www.patreon.com/feross"
510+ },
511+ {
512+ "type": "consulting",
513+ "url": "https://feross.org/support"
514+ }
515+ ]
516+ },
517+ "node_modules/imurmurhash": {
518+ "version": "0.1.4",
519+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
520+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
521+ "dev": true,
522+ "engines": {
523+ "node": ">=0.8.19"
524+ }
525+ },
526+ "node_modules/indent-string": {
527+ "version": "4.0.0",
528+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
529+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
530+ "dev": true,
531+ "engines": {
532+ "node": ">=8"
533+ }
534+ },
535+ "node_modules/infer-owner": {
536+ "version": "1.0.4",
537+ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
538+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
539+ "dev": true
540+ },
541+ "node_modules/inflight": {
542+ "version": "1.0.6",
543+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
544+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
545+ "dev": true,
546+ "dependencies": {
547+ "once": "^1.3.0",
548+ "wrappy": "1"
549+ }
550+ },
551+ "node_modules/inherits": {
552+ "version": "2.0.4",
553+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
554+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
555+ "dev": true
556+ },
557+ "node_modules/ip": {
558+ "version": "2.0.0",
559+ "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
560+ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
561+ "dev": true
562+ },
563+ "node_modules/is-lambda": {
564+ "version": "1.0.1",
565+ "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
566+ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
567+ "dev": true
568+ },
569+ "node_modules/linkify-it": {
570+ "version": "3.0.3",
571+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
572+ "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
573+ "dev": true,
574+ "dependencies": {
575+ "uc.micro": "^1.0.1"
576+ }
577+ },
578+ "node_modules/lru-cache": {
579+ "version": "6.0.0",
580+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
581+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
582+ "dev": true,
583+ "dependencies": {
584+ "yallist": "^4.0.0"
585+ },
586+ "engines": {
587+ "node": ">=10"
588+ }
589+ },
590+ "node_modules/make-fetch-happen": {
591+ "version": "9.1.0",
592+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
593+ "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==",
594+ "dev": true,
595+ "dependencies": {
596+ "agentkeepalive": "^4.1.3",
597+ "cacache": "^15.2.0",
598+ "http-cache-semantics": "^4.1.0",
599+ "http-proxy-agent": "^4.0.1",
600+ "https-proxy-agent": "^5.0.0",
601+ "is-lambda": "^1.0.1",
602+ "lru-cache": "^6.0.0",
603+ "minipass": "^3.1.3",
604+ "minipass-collect": "^1.0.2",
605+ "minipass-fetch": "^1.3.2",
606+ "minipass-flush": "^1.0.5",
607+ "minipass-pipeline": "^1.2.4",
608+ "negotiator": "^0.6.2",
609+ "promise-retry": "^2.0.1",
610+ "socks-proxy-agent": "^6.0.0",
611+ "ssri": "^8.0.0"
612+ },
613+ "engines": {
614+ "node": ">= 10"
615+ }
616+ },
617+ "node_modules/markdown-it": {
618+ "version": "12.3.2",
619+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
620+ "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
621+ "dev": true,
622+ "dependencies": {
623+ "argparse": "^2.0.1",
624+ "entities": "~2.1.0",
625+ "linkify-it": "^3.0.1",
626+ "mdurl": "^1.0.1",
627+ "uc.micro": "^1.0.5"
628+ },
629+ "bin": {
630+ "markdown-it": "bin/markdown-it.js"
631+ }
632+ },
633+ "node_modules/mdurl": {
634+ "version": "1.0.1",
635+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
636+ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
637+ "dev": true
638+ },
639+ "node_modules/minimatch": {
640+ "version": "3.0.4",
641+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
642+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
643+ "dev": true,
644+ "dependencies": {
645+ "brace-expansion": "^1.1.7"
646+ },
647+ "engines": {
648+ "node": "*"
649+ }
650+ },
651+ "node_modules/minipass": {
652+ "version": "3.3.4",
653+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz",
654+ "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==",
655+ "dev": true,
656+ "dependencies": {
657+ "yallist": "^4.0.0"
658+ },
659+ "engines": {
660+ "node": ">=8"
661+ }
662+ },
663+ "node_modules/minipass-collect": {
664+ "version": "1.0.2",
665+ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
666+ "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
667+ "dev": true,
668+ "dependencies": {
669+ "minipass": "^3.0.0"
670+ },
671+ "engines": {
672+ "node": ">= 8"
673+ }
674+ },
675+ "node_modules/minipass-fetch": {
676+ "version": "1.4.1",
677+ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz",
678+ "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==",
679+ "dev": true,
680+ "dependencies": {
681+ "minipass": "^3.1.0",
682+ "minipass-sized": "^1.0.3",
683+ "minizlib": "^2.0.0"
684+ },
685+ "engines": {
686+ "node": ">=8"
687+ },
688+ "optionalDependencies": {
689+ "encoding": "^0.1.12"
690+ }
691+ },
692+ "node_modules/minipass-flush": {
693+ "version": "1.0.5",
694+ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
695+ "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
696+ "dev": true,
697+ "dependencies": {
698+ "minipass": "^3.0.0"
699+ },
700+ "engines": {
701+ "node": ">= 8"
702+ }
703+ },
704+ "node_modules/minipass-pipeline": {
705+ "version": "1.2.4",
706+ "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
707+ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
708+ "dev": true,
709+ "dependencies": {
710+ "minipass": "^3.0.0"
711+ },
712+ "engines": {
713+ "node": ">=8"
714+ }
715+ },
716+ "node_modules/minipass-sized": {
717+ "version": "1.0.3",
718+ "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
719+ "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
720+ "dev": true,
721+ "dependencies": {
722+ "minipass": "^3.0.0"
723+ },
724+ "engines": {
725+ "node": ">=8"
726+ }
727+ },
728+ "node_modules/minizlib": {
729+ "version": "2.1.2",
730+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
731+ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
732+ "dev": true,
733+ "dependencies": {
734+ "minipass": "^3.0.0",
735+ "yallist": "^4.0.0"
736+ },
737+ "engines": {
738+ "node": ">= 8"
739+ }
740+ },
741+ "node_modules/mkdirp": {
742+ "version": "1.0.4",
743+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
744+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
745+ "dev": true,
746+ "bin": {
747+ "mkdirp": "bin/cmd.js"
748+ },
749+ "engines": {
750+ "node": ">=10"
751+ }
752+ },
753+ "node_modules/mkdirp-classic": {
754+ "version": "0.5.3",
755+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
756+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
757+ "dev": true
758+ },
759+ "node_modules/ms": {
760+ "version": "2.1.2",
761+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
762+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
763+ "dev": true
764+ },
765+ "node_modules/negotiator": {
766+ "version": "0.6.3",
767+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
768+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
769+ "dev": true,
770+ "engines": {
771+ "node": ">= 0.6"
772+ }
773+ },
774+ "node_modules/node-fetch": {
775+ "version": "2.6.1",
776+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
777+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
778+ "dev": true,
779+ "engines": {
780+ "node": "4.x || >=6.0.0"
781+ }
782+ },
783+ "node_modules/once": {
784+ "version": "1.4.0",
785+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
786+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
787+ "dev": true,
788+ "dependencies": {
789+ "wrappy": "1"
790+ }
791+ },
792+ "node_modules/p-limit": {
793+ "version": "2.3.0",
794+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
795+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
796+ "dev": true,
797+ "dependencies": {
798+ "p-try": "^2.0.0"
799+ },
800+ "engines": {
801+ "node": ">=6"
802+ },
803+ "funding": {
804+ "url": "https://github.com/sponsors/sindresorhus"
805+ }
806+ },
807+ "node_modules/p-map": {
808+ "version": "4.0.0",
809+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
810+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
811+ "dev": true,
812+ "dependencies": {
813+ "aggregate-error": "^3.0.0"
814+ },
815+ "engines": {
816+ "node": ">=10"
817+ },
818+ "funding": {
819+ "url": "https://github.com/sponsors/sindresorhus"
820+ }
821+ },
822+ "node_modules/p-try": {
823+ "version": "2.2.0",
824+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
825+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
826+ "dev": true,
827+ "engines": {
828+ "node": ">=6"
829+ }
830+ },
831+ "node_modules/path-is-absolute": {
832+ "version": "1.0.1",
833+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
834+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
835+ "dev": true,
836+ "engines": {
837+ "node": ">=0.10.0"
838+ }
839+ },
840+ "node_modules/pend": {
841+ "version": "1.2.0",
842+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
843+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
844+ "dev": true
845+ },
846+ "node_modules/pkg-dir": {
847+ "version": "4.2.0",
848+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
849+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
850+ "dev": true,
851+ "dependencies": {
852+ "find-up": "^4.0.0"
853+ },
854+ "engines": {
855+ "node": ">=8"
856+ }
857+ },
858+ "node_modules/pkg-dir/node_modules/find-up": {
859+ "version": "4.1.0",
860+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
861+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
862+ "dev": true,
863+ "dependencies": {
864+ "locate-path": "^5.0.0",
865+ "path-exists": "^4.0.0"
866+ },
867+ "engines": {
868+ "node": ">=8"
869+ }
870+ },
871+ "node_modules/pkg-dir/node_modules/locate-path": {
872+ "version": "5.0.0",
873+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
874+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
875+ "dev": true,
876+ "dependencies": {
877+ "p-locate": "^4.1.0"
878+ },
879+ "engines": {
880+ "node": ">=8"
881+ }
882+ },
883+ "node_modules/pkg-dir/node_modules/p-locate": {
884+ "version": "4.1.0",
885+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
886+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
887+ "dev": true,
888+ "dependencies": {
889+ "p-limit": "^2.2.0"
890+ },
891+ "engines": {
892+ "node": ">=8"
893+ }
894+ },
895+ "node_modules/pkg-dir/node_modules/path-exists": {
896+ "version": "4.0.0",
897+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
898+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
899+ "dev": true,
900+ "engines": {
901+ "node": ">=8"
902+ }
903+ },
904+ "node_modules/progress": {
905+ "version": "2.0.3",
906+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
907+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
908+ "dev": true,
909+ "engines": {
910+ "node": ">=0.4.0"
911+ }
912+ },
913+ "node_modules/promise-inflight": {
914+ "version": "1.0.1",
915+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
916+ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
917+ "dev": true
918+ },
919+ "node_modules/promise-retry": {
920+ "version": "2.0.1",
921+ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
922+ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
923+ "dev": true,
924+ "dependencies": {
925+ "err-code": "^2.0.2",
926+ "retry": "^0.12.0"
927+ },
928+ "engines": {
929+ "node": ">=10"
930+ }
931+ },
932+ "node_modules/proxy-from-env": {
933+ "version": "1.1.0",
934+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
935+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
936+ "dev": true
937+ },
938+ "node_modules/pump": {
939+ "version": "3.0.0",
940+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
941+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
942+ "dev": true,
943+ "dependencies": {
944+ "end-of-stream": "^1.1.0",
945+ "once": "^1.3.1"
946+ }
947+ },
948+ "node_modules/punycode": {
949+ "version": "2.1.1",
950+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
951+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
952+ "engines": {
953+ "node": ">=6"
954+ }
955+ },
956+ "node_modules/puppeteer": {
957+ "version": "8.0.0",
958+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz",
959+ "integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==",
960+ "deprecated": "Version no longer supported. Upgrade to @latest",
961+ "dev": true,
962+ "hasInstallScript": true,
963+ "dependencies": {
964+ "debug": "^4.1.0",
965+ "devtools-protocol": "0.0.854822",
966+ "extract-zip": "^2.0.0",
967+ "https-proxy-agent": "^5.0.0",
968+ "node-fetch": "^2.6.1",
969+ "pkg-dir": "^4.2.0",
970+ "progress": "^2.0.1",
971+ "proxy-from-env": "^1.1.0",
972+ "rimraf": "^3.0.2",
973+ "tar-fs": "^2.0.0",
974+ "unbzip2-stream": "^1.3.3",
975+ "ws": "^7.2.3"
976+ },
977+ "engines": {
978+ "node": ">=10.18.1"
979+ }
980+ },
981+ "node_modules/puppeteer/node_modules/ws": {
982+ "version": "7.4.4",
983+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
984+ "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
985+ "dev": true,
986+ "engines": {
987+ "node": ">=8.3.0"
988+ },
989+ "peerDependencies": {
990+ "bufferutil": "^4.0.1",
991+ "utf-8-validate": "^5.0.2"
992+ },
993+ "peerDependenciesMeta": {
994+ "bufferutil": {
995+ "optional": true
996+ },
997+ "utf-8-validate": {
998+ "optional": true
999+ }
1000+ }
1001+ },
1002+ "node_modules/retry": {
1003+ "version": "0.12.0",
1004+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
1005+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
1006+ "dev": true,
1007+ "engines": {
1008+ "node": ">= 4"
1009+ }
1010+ },
1011+ "node_modules/rimraf": {
1012+ "version": "3.0.2",
1013+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
1014+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
1015+ "dev": true,
1016+ "dependencies": {
1017+ "glob": "^7.1.3"
1018+ },
1019+ "bin": {
1020+ "rimraf": "bin.js"
1021+ },
1022+ "funding": {
1023+ "url": "https://github.com/sponsors/isaacs"
1024+ }
1025+ },
1026+ "node_modules/safe-buffer": {
1027+ "version": "5.2.1",
1028+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1029+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1030+ "dev": true,
1031+ "funding": [
1032+ {
1033+ "type": "github",
1034+ "url": "https://github.com/sponsors/feross"
1035+ },
1036+ {
1037+ "type": "patreon",
1038+ "url": "https://www.patreon.com/feross"
1039+ },
1040+ {
1041+ "type": "consulting",
1042+ "url": "https://feross.org/support"
1043+ }
1044+ ]
1045+ },
1046+ "node_modules/safer-buffer": {
1047+ "version": "2.1.2",
1048+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1049+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1050+ "dev": true,
1051+ "optional": true
1052+ },
1053+ "node_modules/semver": {
1054+ "version": "7.3.7",
1055+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
1056+ "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
1057+ "dev": true,
1058+ "dependencies": {
1059+ "lru-cache": "^6.0.0"
1060+ },
1061+ "bin": {
1062+ "semver": "bin/semver.js"
1063+ },
1064+ "engines": {
1065+ "node": ">=10"
1066+ }
1067+ },
1068+ "node_modules/smart-buffer": {
1069+ "version": "4.2.0",
1070+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1071+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
1072+ "dev": true,
1073+ "engines": {
1074+ "node": ">= 6.0.0",
1075+ "npm": ">= 3.0.0"
1076+ }
1077+ },
1078+ "node_modules/socks": {
1079+ "version": "2.7.0",
1080+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
1081+ "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
1082+ "dev": true,
1083+ "dependencies": {
1084+ "ip": "^2.0.0",
1085+ "smart-buffer": "^4.2.0"
1086+ },
1087+ "engines": {
1088+ "node": ">= 10.13.0",
1089+ "npm": ">= 3.0.0"
1090+ }
1091+ },
1092+ "node_modules/socks-proxy-agent": {
1093+ "version": "6.2.1",
1094+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz",
1095+ "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==",
1096+ "dev": true,
1097+ "dependencies": {
1098+ "agent-base": "^6.0.2",
1099+ "debug": "^4.3.3",
1100+ "socks": "^2.6.2"
1101+ },
1102+ "engines": {
1103+ "node": ">= 10"
1104+ }
1105+ },
1106+ "node_modules/spago": {
1107+ "version": "0.20.9",
1108+ "resolved": "https://registry.npmjs.org/spago/-/spago-0.20.9.tgz",
1109+ "integrity": "sha512-r5TUxnYn9HawlQyMswlhIk24BGFSN2KGbqgZFZrn47GjTpMscU14xkt9CqTWgoSQYsoZieG+3dUtOxUQ7GYD7w==",
1110+ "dev": true,
1111+ "hasInstallScript": true,
1112+ "dependencies": {
1113+ "make-fetch-happen": "^9.1.0",
1114+ "tar": "^6.1.11"
1115+ },
1116+ "bin": {
1117+ "spago": "spago"
1118+ }
1119+ },
1120+ "node_modules/ssri": {
1121+ "version": "8.0.1",
1122+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
1123+ "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
1124+ "dev": true,
1125+ "dependencies": {
1126+ "minipass": "^3.1.1"
1127+ },
1128+ "engines": {
1129+ "node": ">= 8"
1130+ }
1131+ },
1132+ "node_modules/string_decoder": {
1133+ "version": "1.3.0",
1134+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
1135+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1136+ "dev": true,
1137+ "dependencies": {
1138+ "safe-buffer": "~5.2.0"
1139+ }
1140+ },
1141+ "node_modules/tar": {
1142+ "version": "6.1.11",
1143+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
1144+ "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
1145+ "dev": true,
1146+ "dependencies": {
1147+ "chownr": "^2.0.0",
1148+ "fs-minipass": "^2.0.0",
1149+ "minipass": "^3.0.0",
1150+ "minizlib": "^2.1.1",
1151+ "mkdirp": "^1.0.3",
1152+ "yallist": "^4.0.0"
1153+ },
1154+ "engines": {
1155+ "node": ">= 10"
1156+ }
1157+ },
1158+ "node_modules/tar-fs": {
1159+ "version": "2.1.1",
1160+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
1161+ "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
1162+ "dev": true,
1163+ "dependencies": {
1164+ "chownr": "^1.1.1",
1165+ "mkdirp-classic": "^0.5.2",
1166+ "pump": "^3.0.0",
1167+ "tar-stream": "^2.1.4"
1168+ }
1169+ },
1170+ "node_modules/tar-stream": {
1171+ "version": "2.2.0",
1172+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
1173+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
1174+ "dev": true,
1175+ "dependencies": {
1176+ "bl": "^4.0.3",
1177+ "end-of-stream": "^1.4.1",
1178+ "fs-constants": "^1.0.0",
1179+ "inherits": "^2.0.3",
1180+ "readable-stream": "^3.1.1"
1181+ },
1182+ "engines": {
1183+ "node": ">=6"
1184+ }
1185+ },
1186+ "node_modules/tar-stream/node_modules/readable-stream": {
1187+ "version": "3.6.0",
1188+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
1189+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
1190+ "dev": true,
1191+ "dependencies": {
1192+ "inherits": "^2.0.3",
1193+ "string_decoder": "^1.1.1",
1194+ "util-deprecate": "^1.0.1"
1195+ },
1196+ "engines": {
1197+ "node": ">= 6"
1198+ }
1199+ },
1200+ "node_modules/tar/node_modules/chownr": {
1201+ "version": "2.0.0",
1202+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
1203+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
1204+ "dev": true,
1205+ "engines": {
1206+ "node": ">=10"
1207+ }
1208+ },
1209+ "node_modules/through": {
1210+ "version": "2.3.8",
1211+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
1212+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
1213+ "dev": true
1214+ },
1215+ "node_modules/uc.micro": {
1216+ "version": "1.0.6",
1217+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
1218+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
1219+ "dev": true
1220+ },
1221+ "node_modules/unbzip2-stream": {
1222+ "version": "1.4.3",
1223+ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
1224+ "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
1225+ "dev": true,
1226+ "dependencies": {
1227+ "buffer": "^5.2.1",
1228+ "through": "^2.3.8"
1229+ }
1230+ },
1231+ "node_modules/unbzip2-stream/node_modules/buffer": {
1232+ "version": "5.7.1",
1233+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
1234+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
1235+ "dev": true,
1236+ "funding": [
1237+ {
1238+ "type": "github",
1239+ "url": "https://github.com/sponsors/feross"
1240+ },
1241+ {
1242+ "type": "patreon",
1243+ "url": "https://www.patreon.com/feross"
1244+ },
1245+ {
1246+ "type": "consulting",
1247+ "url": "https://feross.org/support"
1248+ }
1249+ ],
1250+ "dependencies": {
1251+ "base64-js": "^1.3.1",
1252+ "ieee754": "^1.1.13"
1253+ }
1254+ },
1255+ "node_modules/unique-filename": {
1256+ "version": "1.1.1",
1257+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
1258+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
1259+ "dev": true,
1260+ "dependencies": {
1261+ "unique-slug": "^2.0.0"
1262+ }
1263+ },
1264+ "node_modules/unique-slug": {
1265+ "version": "2.0.2",
1266+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
1267+ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
1268+ "dev": true,
1269+ "dependencies": {
1270+ "imurmurhash": "^0.1.4"
1271+ }
1272+ },
1273+ "node_modules/util-deprecate": {
1274+ "version": "1.0.2",
1275+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1276+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
1277+ "dev": true
1278+ },
1279+ "node_modules/wrappy": {
1280+ "version": "1.0.2",
1281+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1282+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1283+ "dev": true
1284+ },
1285+ "node_modules/yallist": {
1286+ "version": "4.0.0",
1287+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
1288+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
1289+ "dev": true
1290+ },
1291+ "node_modules/yauzl": {
1292+ "version": "2.10.0",
1293+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
1294+ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
1295+ "dev": true,
1296+ "dependencies": {
1297+ "buffer-crc32": "~0.2.3",
1298+ "fd-slicer": "~1.1.0"
1299+ }
1300+ }
1301+ },
1302+ "dependencies": {
1303+ "@gar/promisify": {
1304+ "version": "1.1.3",
1305+ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
1306+ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
1307+ "dev": true
1308+ },
1309+ "@npmcli/fs": {
1310+ "version": "1.1.1",
1311+ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
1312+ "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==",
1313+ "dev": true,
1314+ "requires": {
1315+ "@gar/promisify": "^1.0.1",
1316+ "semver": "^7.3.5"
1317+ }
1318+ },
1319+ "@npmcli/move-file": {
1320+ "version": "1.1.2",
1321+ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
1322+ "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
1323+ "dev": true,
1324+ "requires": {
1325+ "mkdirp": "^1.0.4",
1326+ "rimraf": "^3.0.2"
1327+ }
1328+ },
1329+ "@tootallnate/once": {
1330+ "version": "1.1.2",
1331+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
1332+ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
1333+ "dev": true
1334+ },
1335+ "@types/node": {
1336+ "version": "14.14.39",
1337+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz",
1338+ "integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw==",
1339+ "dev": true,
1340+ "optional": true
1341+ },
1342+ "@types/yauzl": {
1343+ "version": "2.9.1",
1344+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
1345+ "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
1346+ "dev": true,
1347+ "optional": true,
1348+ "requires": {
1349+ "@types/node": "*"
1350+ }
1351+ },
1352+ "agent-base": {
1353+ "version": "6.0.2",
1354+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
1355+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
1356+ "dev": true,
1357+ "requires": {
1358+ "debug": "4"
1359+ }
1360+ },
1361+ "agentkeepalive": {
1362+ "version": "4.2.1",
1363+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz",
1364+ "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==",
1365+ "dev": true,
1366+ "requires": {
1367+ "debug": "^4.1.0",
1368+ "depd": "^1.1.2",
1369+ "humanize-ms": "^1.2.1"
1370+ }
1371+ },
1372+ "aggregate-error": {
1373+ "version": "3.1.0",
1374+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
1375+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
1376+ "dev": true,
1377+ "requires": {
1378+ "clean-stack": "^2.0.0",
1379+ "indent-string": "^4.0.0"
1380+ }
1381+ },
1382+ "argparse": {
1383+ "version": "2.0.1",
1384+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
1385+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
1386+ "dev": true
1387+ },
1388+ "balanced-match": {
1389+ "version": "1.0.0",
1390+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
1391+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
1392+ "dev": true
1393+ },
1394+ "base64-js": {
1395+ "version": "1.5.1",
1396+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
1397+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
1398+ "dev": true
1399+ },
1400+ "bl": {
1401+ "version": "4.1.0",
1402+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
1403+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
1404+ "dev": true,
1405+ "requires": {
1406+ "buffer": "^5.5.0",
1407+ "inherits": "^2.0.4",
1408+ "readable-stream": "^3.4.0"
1409+ },
1410+ "dependencies": {
1411+ "buffer": {
1412+ "version": "5.7.1",
1413+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
1414+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
1415+ "dev": true,
1416+ "requires": {
1417+ "base64-js": "^1.3.1",
1418+ "ieee754": "^1.1.13"
1419+ }
1420+ },
1421+ "readable-stream": {
1422+ "version": "3.6.0",
1423+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
1424+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
1425+ "dev": true,
1426+ "requires": {
1427+ "inherits": "^2.0.3",
1428+ "string_decoder": "^1.1.1",
1429+ "util-deprecate": "^1.0.1"
1430+ }
1431+ }
1432+ }
1433+ },
1434+ "brace-expansion": {
1435+ "version": "1.1.11",
1436+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
1437+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
1438+ "dev": true,
1439+ "requires": {
1440+ "balanced-match": "^1.0.0",
1441+ "concat-map": "0.0.1"
1442+ }
1443+ },
1444+ "buffer-crc32": {
1445+ "version": "0.2.13",
1446+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
1447+ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
1448+ "dev": true
1449+ },
1450+ "cacache": {
1451+ "version": "15.3.0",
1452+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
1453+ "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==",
1454+ "dev": true,
1455+ "requires": {
1456+ "@npmcli/fs": "^1.0.0",
1457+ "@npmcli/move-file": "^1.0.1",
1458+ "chownr": "^2.0.0",
1459+ "fs-minipass": "^2.0.0",
1460+ "glob": "^7.1.4",
1461+ "infer-owner": "^1.0.4",
1462+ "lru-cache": "^6.0.0",
1463+ "minipass": "^3.1.1",
1464+ "minipass-collect": "^1.0.2",
1465+ "minipass-flush": "^1.0.5",
1466+ "minipass-pipeline": "^1.2.2",
1467+ "mkdirp": "^1.0.3",
1468+ "p-map": "^4.0.0",
1469+ "promise-inflight": "^1.0.1",
1470+ "rimraf": "^3.0.2",
1471+ "ssri": "^8.0.1",
1472+ "tar": "^6.0.2",
1473+ "unique-filename": "^1.1.1"
1474+ },
1475+ "dependencies": {
1476+ "chownr": {
1477+ "version": "2.0.0",
1478+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
1479+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
1480+ "dev": true
1481+ }
1482+ }
1483+ },
1484+ "chownr": {
1485+ "version": "1.1.4",
1486+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
1487+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
1488+ "dev": true
1489+ },
1490+ "clean-stack": {
1491+ "version": "2.2.0",
1492+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
1493+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
1494+ "dev": true
1495+ },
1496+ "concat-map": {
1497+ "version": "0.0.1",
1498+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
1499+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
1500+ "dev": true
1501+ },
1502+ "debug": {
1503+ "version": "4.3.4",
1504+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
1505+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
1506+ "dev": true,
1507+ "requires": {
1508+ "ms": "2.1.2"
1509+ }
1510+ },
1511+ "depd": {
1512+ "version": "1.1.2",
1513+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
1514+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
1515+ "dev": true
1516+ },
1517+ "devtools-protocol": {
1518+ "version": "0.0.854822",
1519+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz",
1520+ "integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==",
1521+ "dev": true
1522+ },
1523+ "encoding": {
1524+ "version": "0.1.13",
1525+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
1526+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
1527+ "dev": true,
1528+ "optional": true,
1529+ "requires": {
1530+ "iconv-lite": "^0.6.2"
1531+ }
1532+ },
1533+ "end-of-stream": {
1534+ "version": "1.4.4",
1535+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
1536+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
1537+ "dev": true,
1538+ "requires": {
1539+ "once": "^1.4.0"
1540+ }
1541+ },
1542+ "entities": {
1543+ "version": "2.1.0",
1544+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
1545+ "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
1546+ "dev": true
1547+ },
1548+ "err-code": {
1549+ "version": "2.0.3",
1550+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
1551+ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
1552+ "dev": true
1553+ },
1554+ "esbuild": {
1555+ "version": "0.11.11",
1556+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.11.tgz",
1557+ "integrity": "sha512-iq5YdV63vY/nUAFIvY92BXVkYjMbOchnofLKoLKMPZIa4uuIJAJG9WRA+ZRjQBZbrsORUwvZcANeG2d3p46PJQ==",
1558+ "dev": true
1559+ },
1560+ "extract-zip": {
1561+ "version": "2.0.1",
1562+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
1563+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
1564+ "dev": true,
1565+ "requires": {
1566+ "@types/yauzl": "^2.9.1",
1567+ "debug": "^4.1.1",
1568+ "get-stream": "^5.1.0",
1569+ "yauzl": "^2.10.0"
1570+ }
1571+ },
1572+ "fd-slicer": {
1573+ "version": "1.1.0",
1574+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
1575+ "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
1576+ "dev": true,
1577+ "requires": {
1578+ "pend": "~1.2.0"
1579+ }
1580+ },
1581+ "fs-constants": {
1582+ "version": "1.0.0",
1583+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
1584+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
1585+ "dev": true
1586+ },
1587+ "fs-minipass": {
1588+ "version": "2.1.0",
1589+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
1590+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
1591+ "dev": true,
1592+ "requires": {
1593+ "minipass": "^3.0.0"
1594+ }
1595+ },
1596+ "fs.realpath": {
1597+ "version": "1.0.0",
1598+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
1599+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
1600+ "dev": true
1601+ },
1602+ "get-stream": {
1603+ "version": "5.2.0",
1604+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
1605+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
1606+ "dev": true,
1607+ "requires": {
1608+ "pump": "^3.0.0"
1609+ }
1610+ },
1611+ "glob": {
1612+ "version": "7.1.6",
1613+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
1614+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
1615+ "dev": true,
1616+ "requires": {
1617+ "fs.realpath": "^1.0.0",
1618+ "inflight": "^1.0.4",
1619+ "inherits": "2",
1620+ "minimatch": "^3.0.4",
1621+ "once": "^1.3.0",
1622+ "path-is-absolute": "^1.0.0"
1623+ }
1624+ },
1625+ "http-cache-semantics": {
1626+ "version": "4.1.0",
1627+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
1628+ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
1629+ "dev": true
1630+ },
1631+ "http-proxy-agent": {
1632+ "version": "4.0.1",
1633+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
1634+ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
1635+ "dev": true,
1636+ "requires": {
1637+ "@tootallnate/once": "1",
1638+ "agent-base": "6",
1639+ "debug": "4"
1640+ }
1641+ },
1642+ "https-proxy-agent": {
1643+ "version": "5.0.0",
1644+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
1645+ "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
1646+ "dev": true,
1647+ "requires": {
1648+ "agent-base": "6",
1649+ "debug": "4"
1650+ }
1651+ },
1652+ "humanize-ms": {
1653+ "version": "1.2.1",
1654+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
1655+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
1656+ "dev": true,
1657+ "requires": {
1658+ "ms": "^2.0.0"
1659+ }
1660+ },
1661+ "iconv-lite": {
1662+ "version": "0.6.3",
1663+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
1664+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
1665+ "dev": true,
1666+ "optional": true,
1667+ "requires": {
1668+ "safer-buffer": ">= 2.1.2 < 3.0.0"
1669+ }
1670+ },
1671+ "ieee754": {
1672+ "version": "1.2.1",
1673+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
1674+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
1675+ "dev": true
1676+ },
1677+ "imurmurhash": {
1678+ "version": "0.1.4",
1679+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
1680+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
1681+ "dev": true
1682+ },
1683+ "indent-string": {
1684+ "version": "4.0.0",
1685+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
1686+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
1687+ "dev": true
1688+ },
1689+ "infer-owner": {
1690+ "version": "1.0.4",
1691+ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
1692+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
1693+ "dev": true
1694+ },
1695+ "inflight": {
1696+ "version": "1.0.6",
1697+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
1698+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
1699+ "dev": true,
1700+ "requires": {
1701+ "once": "^1.3.0",
1702+ "wrappy": "1"
1703+ }
1704+ },
1705+ "inherits": {
1706+ "version": "2.0.4",
1707+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1708+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1709+ "dev": true
1710+ },
1711+ "ip": {
1712+ "version": "2.0.0",
1713+ "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
1714+ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
1715+ "dev": true
1716+ },
1717+ "is-lambda": {
1718+ "version": "1.0.1",
1719+ "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
1720+ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
1721+ "dev": true
1722+ },
1723+ "linkify-it": {
1724+ "version": "3.0.3",
1725+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
1726+ "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
1727+ "dev": true,
1728+ "requires": {
1729+ "uc.micro": "^1.0.1"
1730+ }
1731+ },
1732+ "lru-cache": {
1733+ "version": "6.0.0",
1734+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
1735+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
1736+ "dev": true,
1737+ "requires": {
1738+ "yallist": "^4.0.0"
1739+ }
1740+ },
1741+ "make-fetch-happen": {
1742+ "version": "9.1.0",
1743+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
1744+ "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==",
1745+ "dev": true,
1746+ "requires": {
1747+ "agentkeepalive": "^4.1.3",
1748+ "cacache": "^15.2.0",
1749+ "http-cache-semantics": "^4.1.0",
1750+ "http-proxy-agent": "^4.0.1",
1751+ "https-proxy-agent": "^5.0.0",
1752+ "is-lambda": "^1.0.1",
1753+ "lru-cache": "^6.0.0",
1754+ "minipass": "^3.1.3",
1755+ "minipass-collect": "^1.0.2",
1756+ "minipass-fetch": "^1.3.2",
1757+ "minipass-flush": "^1.0.5",
1758+ "minipass-pipeline": "^1.2.4",
1759+ "negotiator": "^0.6.2",
1760+ "promise-retry": "^2.0.1",
1761+ "socks-proxy-agent": "^6.0.0",
1762+ "ssri": "^8.0.0"
1763+ }
1764+ },
1765+ "markdown-it": {
1766+ "version": "12.3.2",
1767+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
1768+ "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
1769+ "dev": true,
1770+ "requires": {
1771+ "argparse": "^2.0.1",
1772+ "entities": "~2.1.0",
1773+ "linkify-it": "^3.0.1",
1774+ "mdurl": "^1.0.1",
1775+ "uc.micro": "^1.0.5"
1776+ }
1777+ },
1778+ "mdurl": {
1779+ "version": "1.0.1",
1780+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
1781+ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
1782+ "dev": true
1783+ },
1784+ "minimatch": {
1785+ "version": "3.0.4",
1786+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
1787+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
1788+ "dev": true,
1789+ "requires": {
1790+ "brace-expansion": "^1.1.7"
1791+ }
1792+ },
1793+ "minipass": {
1794+ "version": "3.3.4",
1795+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz",
1796+ "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==",
1797+ "dev": true,
1798+ "requires": {
1799+ "yallist": "^4.0.0"
1800+ }
1801+ },
1802+ "minipass-collect": {
1803+ "version": "1.0.2",
1804+ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
1805+ "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
1806+ "dev": true,
1807+ "requires": {
1808+ "minipass": "^3.0.0"
1809+ }
1810+ },
1811+ "minipass-fetch": {
1812+ "version": "1.4.1",
1813+ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz",
1814+ "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==",
1815+ "dev": true,
1816+ "requires": {
1817+ "encoding": "^0.1.12",
1818+ "minipass": "^3.1.0",
1819+ "minipass-sized": "^1.0.3",
1820+ "minizlib": "^2.0.0"
1821+ }
1822+ },
1823+ "minipass-flush": {
1824+ "version": "1.0.5",
1825+ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
1826+ "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
1827+ "dev": true,
1828+ "requires": {
1829+ "minipass": "^3.0.0"
1830+ }
1831+ },
1832+ "minipass-pipeline": {
1833+ "version": "1.2.4",
1834+ "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
1835+ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
1836+ "dev": true,
1837+ "requires": {
1838+ "minipass": "^3.0.0"
1839+ }
1840+ },
1841+ "minipass-sized": {
1842+ "version": "1.0.3",
1843+ "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
1844+ "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
1845+ "dev": true,
1846+ "requires": {
1847+ "minipass": "^3.0.0"
1848+ }
1849+ },
1850+ "minizlib": {
1851+ "version": "2.1.2",
1852+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
1853+ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
1854+ "dev": true,
1855+ "requires": {
1856+ "minipass": "^3.0.0",
1857+ "yallist": "^4.0.0"
1858+ }
1859+ },
1860+ "mkdirp": {
1861+ "version": "1.0.4",
1862+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
1863+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
1864+ "dev": true
1865+ },
1866+ "mkdirp-classic": {
1867+ "version": "0.5.3",
1868+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
1869+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
1870+ "dev": true
1871+ },
1872+ "ms": {
1873+ "version": "2.1.2",
1874+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1875+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1876+ "dev": true
1877+ },
1878+ "negotiator": {
1879+ "version": "0.6.3",
1880+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1881+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
1882+ "dev": true
1883+ },
1884+ "node-fetch": {
1885+ "version": "2.6.1",
1886+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
1887+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
1888+ "dev": true
1889+ },
1890+ "once": {
1891+ "version": "1.4.0",
1892+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1893+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
1894+ "dev": true,
1895+ "requires": {
1896+ "wrappy": "1"
1897+ }
1898+ },
1899+ "p-limit": {
1900+ "version": "2.3.0",
1901+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
1902+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
1903+ "dev": true,
1904+ "requires": {
1905+ "p-try": "^2.0.0"
1906+ }
1907+ },
1908+ "p-map": {
1909+ "version": "4.0.0",
1910+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
1911+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
1912+ "dev": true,
1913+ "requires": {
1914+ "aggregate-error": "^3.0.0"
1915+ }
1916+ },
1917+ "p-try": {
1918+ "version": "2.2.0",
1919+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
1920+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
1921+ "dev": true
1922+ },
1923+ "path-is-absolute": {
1924+ "version": "1.0.1",
1925+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1926+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
1927+ "dev": true
1928+ },
1929+ "pend": {
1930+ "version": "1.2.0",
1931+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
1932+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
1933+ "dev": true
1934+ },
1935+ "pkg-dir": {
1936+ "version": "4.2.0",
1937+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
1938+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
1939+ "dev": true,
1940+ "requires": {
1941+ "find-up": "^4.0.0"
1942+ },
1943+ "dependencies": {
1944+ "find-up": {
1945+ "version": "4.1.0",
1946+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
1947+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
1948+ "dev": true,
1949+ "requires": {
1950+ "locate-path": "^5.0.0",
1951+ "path-exists": "^4.0.0"
1952+ }
1953+ },
1954+ "locate-path": {
1955+ "version": "5.0.0",
1956+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
1957+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
1958+ "dev": true,
1959+ "requires": {
1960+ "p-locate": "^4.1.0"
1961+ }
1962+ },
1963+ "p-locate": {
1964+ "version": "4.1.0",
1965+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
1966+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
1967+ "dev": true,
1968+ "requires": {
1969+ "p-limit": "^2.2.0"
1970+ }
1971+ },
1972+ "path-exists": {
1973+ "version": "4.0.0",
1974+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
1975+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
1976+ "dev": true
1977+ }
1978+ }
1979+ },
1980+ "progress": {
1981+ "version": "2.0.3",
1982+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
1983+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
1984+ "dev": true
1985+ },
1986+ "promise-inflight": {
1987+ "version": "1.0.1",
1988+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
1989+ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
1990+ "dev": true
1991+ },
1992+ "promise-retry": {
1993+ "version": "2.0.1",
1994+ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
1995+ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
1996+ "dev": true,
1997+ "requires": {
1998+ "err-code": "^2.0.2",
1999+ "retry": "^0.12.0"
2000+ }
2001+ },
2002+ "proxy-from-env": {
2003+ "version": "1.1.0",
2004+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
2005+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
2006+ "dev": true
2007+ },
2008+ "pump": {
2009+ "version": "3.0.0",
2010+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
2011+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
2012+ "dev": true,
2013+ "requires": {
2014+ "end-of-stream": "^1.1.0",
2015+ "once": "^1.3.1"
2016+ }
2017+ },
2018+ "punycode": {
2019+ "version": "2.1.1",
2020+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
2021+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
2022+ },
2023+ "puppeteer": {
2024+ "version": "8.0.0",
2025+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz",
2026+ "integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==",
2027+ "dev": true,
2028+ "requires": {
2029+ "debug": "^4.1.0",
2030+ "devtools-protocol": "0.0.854822",
2031+ "extract-zip": "^2.0.0",
2032+ "https-proxy-agent": "^5.0.0",
2033+ "node-fetch": "^2.6.1",
2034+ "pkg-dir": "^4.2.0",
2035+ "progress": "^2.0.1",
2036+ "proxy-from-env": "^1.1.0",
2037+ "rimraf": "^3.0.2",
2038+ "tar-fs": "^2.0.0",
2039+ "unbzip2-stream": "^1.3.3",
2040+ "ws": "^7.2.3"
2041+ },
2042+ "dependencies": {
2043+ "ws": {
2044+ "version": "7.4.4",
2045+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
2046+ "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
2047+ "dev": true,
2048+ "requires": {}
2049+ }
2050+ }
2051+ },
2052+ "retry": {
2053+ "version": "0.12.0",
2054+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
2055+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
2056+ "dev": true
2057+ },
2058+ "rimraf": {
2059+ "version": "3.0.2",
2060+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
2061+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
2062+ "dev": true,
2063+ "requires": {
2064+ "glob": "^7.1.3"
2065+ }
2066+ },
2067+ "safe-buffer": {
2068+ "version": "5.2.1",
2069+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
2070+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
2071+ "dev": true
2072+ },
2073+ "safer-buffer": {
2074+ "version": "2.1.2",
2075+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
2076+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
2077+ "dev": true,
2078+ "optional": true
2079+ },
2080+ "semver": {
2081+ "version": "7.3.7",
2082+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
2083+ "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
2084+ "dev": true,
2085+ "requires": {
2086+ "lru-cache": "^6.0.0"
2087+ }
2088+ },
2089+ "smart-buffer": {
2090+ "version": "4.2.0",
2091+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
2092+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
2093+ "dev": true
2094+ },
2095+ "socks": {
2096+ "version": "2.7.0",
2097+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
2098+ "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
2099+ "dev": true,
2100+ "requires": {
2101+ "ip": "^2.0.0",
2102+ "smart-buffer": "^4.2.0"
2103+ }
2104+ },
2105+ "socks-proxy-agent": {
2106+ "version": "6.2.1",
2107+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz",
2108+ "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==",
2109+ "dev": true,
2110+ "requires": {
2111+ "agent-base": "^6.0.2",
2112+ "debug": "^4.3.3",
2113+ "socks": "^2.6.2"
2114+ }
2115+ },
2116+ "spago": {
2117+ "version": "0.20.9",
2118+ "resolved": "https://registry.npmjs.org/spago/-/spago-0.20.9.tgz",
2119+ "integrity": "sha512-r5TUxnYn9HawlQyMswlhIk24BGFSN2KGbqgZFZrn47GjTpMscU14xkt9CqTWgoSQYsoZieG+3dUtOxUQ7GYD7w==",
2120+ "dev": true,
2121+ "requires": {
2122+ "make-fetch-happen": "^9.1.0",
2123+ "tar": "^6.1.11"
2124+ }
2125+ },
2126+ "ssri": {
2127+ "version": "8.0.1",
2128+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
2129+ "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
2130+ "dev": true,
2131+ "requires": {
2132+ "minipass": "^3.1.1"
2133+ }
2134+ },
2135+ "string_decoder": {
2136+ "version": "1.3.0",
2137+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
2138+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
2139+ "dev": true,
2140+ "requires": {
2141+ "safe-buffer": "~5.2.0"
2142+ }
2143+ },
2144+ "tar": {
2145+ "version": "6.1.11",
2146+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
2147+ "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
2148+ "dev": true,
2149+ "requires": {
2150+ "chownr": "^2.0.0",
2151+ "fs-minipass": "^2.0.0",
2152+ "minipass": "^3.0.0",
2153+ "minizlib": "^2.1.1",
2154+ "mkdirp": "^1.0.3",
2155+ "yallist": "^4.0.0"
2156+ },
2157+ "dependencies": {
2158+ "chownr": {
2159+ "version": "2.0.0",
2160+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
2161+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
2162+ "dev": true
2163+ }
2164+ }
2165+ },
2166+ "tar-fs": {
2167+ "version": "2.1.1",
2168+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
2169+ "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
2170+ "dev": true,
2171+ "requires": {
2172+ "chownr": "^1.1.1",
2173+ "mkdirp-classic": "^0.5.2",
2174+ "pump": "^3.0.0",
2175+ "tar-stream": "^2.1.4"
2176+ }
2177+ },
2178+ "tar-stream": {
2179+ "version": "2.2.0",
2180+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
2181+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
2182+ "dev": true,
2183+ "requires": {
2184+ "bl": "^4.0.3",
2185+ "end-of-stream": "^1.4.1",
2186+ "fs-constants": "^1.0.0",
2187+ "inherits": "^2.0.3",
2188+ "readable-stream": "^3.1.1"
2189+ },
2190+ "dependencies": {
2191+ "readable-stream": {
2192+ "version": "3.6.0",
2193+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
2194+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
2195+ "dev": true,
2196+ "requires": {
2197+ "inherits": "^2.0.3",
2198+ "string_decoder": "^1.1.1",
2199+ "util-deprecate": "^1.0.1"
2200+ }
2201+ }
2202+ }
2203+ },
2204+ "through": {
2205+ "version": "2.3.8",
2206+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
2207+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
2208+ "dev": true
2209+ },
2210+ "uc.micro": {
2211+ "version": "1.0.6",
2212+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
2213+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
2214+ "dev": true
2215+ },
2216+ "unbzip2-stream": {
2217+ "version": "1.4.3",
2218+ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
2219+ "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
2220+ "dev": true,
2221+ "requires": {
2222+ "buffer": "^5.2.1",
2223+ "through": "^2.3.8"
2224+ },
2225+ "dependencies": {
2226+ "buffer": {
2227+ "version": "5.7.1",
2228+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
2229+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
2230+ "dev": true,
2231+ "requires": {
2232+ "base64-js": "^1.3.1",
2233+ "ieee754": "^1.1.13"
2234+ }
2235+ }
2236+ }
2237+ },
2238+ "unique-filename": {
2239+ "version": "1.1.1",
2240+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
2241+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
2242+ "dev": true,
2243+ "requires": {
2244+ "unique-slug": "^2.0.0"
2245+ }
2246+ },
2247+ "unique-slug": {
2248+ "version": "2.0.2",
2249+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
2250+ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
2251+ "dev": true,
2252+ "requires": {
2253+ "imurmurhash": "^0.1.4"
2254+ }
2255+ },
2256+ "util-deprecate": {
2257+ "version": "1.0.2",
2258+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
2259+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
2260+ "dev": true
2261+ },
2262+ "wrappy": {
2263+ "version": "1.0.2",
2264+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
2265+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
2266+ "dev": true
2267+ },
2268+ "yallist": {
2269+ "version": "4.0.0",
2270+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
2271+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
2272+ "dev": true
2273+ },
2274+ "yauzl": {
2275+ "version": "2.10.0",
2276+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
2277+ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
2278+ "dev": true,
2279+ "requires": {
2280+ "buffer-crc32": "~0.2.3",
2281+ "fd-slicer": "~1.1.0"
2282+ }
2283+ }
2284+ }
2285+}
package.jsonnew file+55
@@ -0,0 +1,55 @@
1+{
2+ "name": "purescript-docs-search",
3+ "type": "module",
4+ "version": "0.0.12",
5+ "description": "Search frontend for the documentation generated by the PureScript compiler.",
6+ "directories": {
7+ "test": "test"
8+ },
9+ "bin": {
10+ "purescript-docs-search": "dist/purescript-docs-search.cjs"
11+ },
12+ "files": [
13+ "dist/purescript-docs-search.cjs",
14+ "dist/docs-search-app.js",
15+ "README.md",
16+ "CHANGELOG.md"
17+ ],
18+ "scripts": {
19+ "test": "spago docs --no-search && ./dist/purescript-docs-search.cjs build-index && spago test && npm run check-version",
20+ "bundle-app": "spago bundle-app --no-build --no-install -m Docs.Search.App --to dist/docs-search-app.js",
21+ "esbuild-app": "esbuild dist/docs-search-app.js --target=es2016 --bundle --minify --outfile=dist/docs-search-app.min.js && mv dist/docs-search-app.min.js dist/docs-search-app.js",
22+ "build-app": "npm run bundle-app && npm run esbuild-app",
23+ "bundle-main": "spago bundle-app --no-build --no-install --platform node -m Docs.Search.Main --to dist/main.js",
24+ "esbuild-main": "esbuild dist/main.js --platform=node --bundle --minify --outfile=dist/main.min.js && mv dist/main.min.js dist/main.js",
25+ "add-shebang": "echo \"#!/usr/bin/env node\" > dist/purescript-docs-search.cjs && cat dist/main.js >> dist/purescript-docs-search.cjs",
26+ "chmod-main": "chmod +x dist/purescript-docs-search.cjs",
27+ "build-main": "npm run bundle-main && npm run esbuild-main && npm run add-shebang && rm dist/main.js && npm run chmod-main",
28+ "build": "spago build && npm run build-app && npm run build-main",
29+ "clean": "rm -rf dist",
30+ "check-version": "[ \"$(./dist/purescript-docs-search.cjs version)\" = \"$npm_package_version\" ]"
31+ },
32+ "repository": {
33+ "type": "git",
34+ "url": "git+https://github.com/purescript/purescript-docs-search.git"
35+ },
36+ "keywords": [
37+ "purescript"
38+ ],
39+ "author": "Kalnitsky Vladimir <klntsky@gmail.com>",
40+ "license": "BSD-3-Clause",
41+ "bugs": {
42+ "url": "https://github.com/purescript/purescript-docs-search/issues"
43+ },
44+ "homepage": "https://github.com/purescript/purescript-docs-search#readme",
45+ "dependencies": {
46+ "punycode": "^2.1.1"
47+ },
48+ "devDependencies": {
49+ "esbuild": "^0.11.10",
50+ "glob": "^7.1.6",
51+ "markdown-it": "^12.0.4",
52+ "puppeteer": "^8.0.0",
53+ "spago": "^0.20.9"
54+ }
55+}
packages.dhall+174−44
@@ -1,52 +1,182 @@
11let mkPackage =
2− https://raw.githubusercontent.com/purescript/package-sets/psc-0.13.0-20190602/src/mkPackage.dhall sha256:0b197efa1d397ace6eb46b243ff2d73a3da5638d8d0ac8473e8e4a8fc528cf57
2+ https://raw.githubusercontent.com/purescript/package-sets/psc-0.13.2-20190725/src/mkPackage.dhall
3+ sha256:0b197efa1d397ace6eb46b243ff2d73a3da5638d8d0ac8473e8e4a8fc528cf57
34
45let upstream =
5− https://raw.githubusercontent.com/purescript/package-sets/psc-0.13.0-20190602/src/packages.dhall sha256:5da1578dd297709265715a92eda5f42989dce92e121fcc889cff669a3b997c3d
6+ https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20220829/packages.dhall
7+ sha256:9ec64ed24ec308770ccdd3d0edb74a814fcfbe34c7df5c28266935f0b5e585d7
68
79let overrides = {=}
810
911let additions =
10− { search-trie =
11− mkPackage
12− [ "prelude"
13− , "arrays"
14− , "ordered-collections"
15− , "lists"
16− , "foldable-traversable"
17− ]
18− "https://github.com/klntsky/purescript-search-trie.git"
19− "fd37a12"
20− , halogen =
21− mkPackage
22− [ "aff"
23− , "avar"
24− , "console"
25− , "const"
26− , "coroutines"
27− , "dom-indexed"
28− , "foreign"
29− , "fork"
30− , "free"
31− , "freeap"
32− , "halogen-vdom"
33− , "media-types"
34− , "nullable"
35− , "ordered-collections"
36− , "parallel"
37− , "profunctor"
38− , "transformers"
39− , "unsafe-coerce"
40− , "unsafe-reference"
41− , "web-uievents"
42− ]
43− "https://github.com/slamdata/purescript-halogen.git"
44− "v5.0.0-rc.4"
45− , halogen-css =
46− mkPackage
47− [ "css", "halogen" ]
48− "https://github.com/slamdata/purescript-halogen-css.git"
49− "v8.0.0"
50− }
12+ { halogen =
13+ mkPackage
14+ [ "aff"
15+ , "avar"
16+ , "console"
17+ , "const"
18+ , "dom-indexed"
19+ , "effect"
20+ , "foreign"
21+ , "fork"
22+ , "free"
23+ , "freeap"
24+ , "halogen-subscriptions"
25+ , "halogen-vdom"
26+ , "media-types"
27+ , "nullable"
28+ , "ordered-collections"
29+ , "parallel"
30+ , "profunctor"
31+ , "transformers"
32+ , "unsafe-coerce"
33+ , "unsafe-reference"
34+ , "web-file"
35+ , "web-uievents"
36+ ]
37+ "https://github.com/purescript-halogen/purescript-halogen.git"
38+ "v7.0.0"
39+ , memoize =
40+ mkPackage
41+ [ "prelude"
42+ , "lazy"
43+ , "either"
44+ , "maybe"
45+ , "tuples"
46+ , "integers"
47+ , "lists"
48+ , "strings"
49+ ]
50+ "https://github.com/paf31/purescript-memoize.git"
51+ "9960694e82adc212fd89f8ed8778cf55fcb72aeb"
52+ , optparse =
53+ mkPackage
54+ [ "prelude"
55+ , "effect"
56+ , "exitcodes"
57+ , "strings"
58+ , "ordered-collections"
59+ , "arrays"
60+ , "console"
61+ , "transformers"
62+ , "exists"
63+ , "node-process"
64+ , "free"
65+ , "memoize"
66+ ]
67+ "https://github.com/klntsky/purescript-optparse.git"
68+ "2fe4265b7c6b09744c11190a43ca06777c752473"
69+ , exitcodes =
70+ mkPackage
71+ [ "enums" ]
72+ "https://github.com/Risto-Stevcev/purescript-exitcodes.git"
73+ "v4.0.0"
74+ , markdown-it =
75+ mkPackage
76+ [ "prelude", "effect", "options" ]
77+ "https://github.com/klntsky/purescript-markdown-it.git"
78+ "f3b7654783a83a80d7c09b6caaa7cd40b93ddce1"
79+ , string-parsers =
80+ mkPackage
81+ [ "arrays"
82+ , "assert"
83+ , "bifunctors"
84+ , "console"
85+ , "control"
86+ , "effect"
87+ , "either"
88+ , "enums"
89+ , "foldable-traversable"
90+ , "lists"
91+ , "maybe"
92+ , "minibench"
93+ , "nonempty"
94+ , "partial"
95+ , "prelude"
96+ , "strings"
97+ , "tailrec"
98+ , "transformers"
99+ , "unfoldable"
100+ ]
101+ "https://github.com/purescript-contrib/purescript-string-parsers.git"
102+ "v8.0.0"
103+ , html-parser-halogen =
104+ mkPackage
105+ [ "string-parsers", "halogen" ]
106+ "https://github.com/rnons/purescript-html-parser-halogen.git"
107+ "035a51d02ba9f8b70c3ffd9fe31a3f5bed19941c"
108+ , markdown-it-halogen =
109+ mkPackage
110+ [ "markdown-it", "html-parser-halogen" ]
111+ "https://github.com/nonbili/purescript-markdown-it-halogen.git"
112+ "08c9625015bf04214be14e45230e8ce12f3fa2bf"
113+ , search-trie =
114+ mkPackage
115+ [ "prelude"
116+ , "arrays"
117+ , "ordered-collections"
118+ , "lists"
119+ , "foldable-traversable"
120+ , "bifunctors"
121+ ]
122+ "https://github.com/klntsky/purescript-search-trie.git"
123+ "e7f7f22486a1dba22171ec885dbc2149dc815119"
124+ , css =
125+ mkPackage
126+ [ "colors"
127+ , "console"
128+ , "effect"
129+ , "exceptions"
130+ , "nonempty"
131+ , "profunctor"
132+ , "psci-support"
133+ , "strings"
134+ , "these"
135+ , "transformers"
136+ ]
137+ "https://github.com/purescript-contrib/purescript-css.git"
138+ "710d6a742beb88299faf08aaeb997ee1e24483ab"
139+ , jest =
140+ mkPackage
141+ [ "aff"
142+ , "aff-promise"
143+ , "effect"
144+ , "prelude"
145+ , "psci-support"
146+ , "foldable-traversable"
147+ ]
148+ "https://github.com/klntsky/purescript-jest.git"
149+ "7feaa5a880fc75002c4eca312993174e7220252b"
150+ , literals =
151+ mkPackage
152+ [ "assert"
153+ , "effect"
154+ , "console"
155+ , "integers"
156+ , "numbers"
157+ , "partial"
158+ , "psci-support"
159+ , "unsafe-coerce"
160+ , "typelevel-prelude"
161+ ]
162+ "https://github.com/jvliwanag/purescript-literals.git"
163+ "ae3ef4e9c1ae7c57ec77bd13906fa60ae8abba4a"
164+ , untagged-union =
165+ mkPackage
166+ [ "assert"
167+ , "console"
168+ , "effect"
169+ , "foreign"
170+ , "foreign-object"
171+ , "literals"
172+ , "maybe"
173+ , "newtype"
174+ , "psci-support"
175+ , "tuples"
176+ , "unsafe-coerce"
177+ ]
178+ "https://github.com/jvliwanag/purescript-untagged-union.git"
179+ "ed8262a966e15e751322c327e2759a9b9c0ef3f3"
180+ }
51181
52−in upstream ⫽ overrides ⫽ additions
182+in upstream // overrides // additions
preview.pngnew file

Binary file not shown.

release.shnew file+24
@@ -0,0 +1,24 @@
1+#!/usr/bin/env bash
2+
3+set -euo pipefail
4+
5+# Please use this script to cut new releases.
6+# It will create a tag and push it, which will trigger CI to create a release,
7+# build release files, push them to the release, and publish a new version on NPM
8+
9+NEW_TAG=v$(jq -r ".version" package.json)
10+
11+# Fail if the new tag already exists
12+if git rev-parse "${NEW_TAG}" >/dev/null 2>&1; then
13+ echo "Tag '${NEW_TAG}' already exists, please bump the version in package.json";
14+ exit 1;
15+fi
16+
17+echo "Creating and pushing new tag '${NEW_TAG}'.."
18+
19+git tag "${NEW_TAG}"
20+
21+git push origin "${NEW_TAG}"
22+
23+echo "Done."
24+
spago.dhall+53−31
@@ -1,33 +1,55 @@
1−{ name =
2− "my-project"
1+{ sources = [ "src/**/*.purs", "test/**/*.purs" ]
2+, name = "docs-search"
33, dependencies =
4− [ "aff-promise"
5− , "argonaut-codecs"
6− , "argonaut-core"
7− , "arrays"
8− , "console"
9− , "control"
10− , "coroutines"
11− , "effect"
12− , "foldable-traversable"
13− , "generics-rep"
14− , "halogen"
15− , "halogen-css"
16− , "lists"
17− , "maybe"
18− , "newtype"
19− , "node-buffer"
20− , "node-fs"
21− , "node-fs-aff"
22− , "node-process"
23− , "psci-support"
24− , "search-trie"
25− , "strings"
26− , "string-parsers"
27− , "test-unit"
28− , "web-dom"
29− , "web-html"
30− ]
31−, packages =
32− ./packages.dhall
4+ [ "aff"
5+ , "aff-promise"
6+ , "argonaut-codecs"
7+ , "argonaut-core"
8+ , "argonaut-generic"
9+ , "arrays"
10+ , "bower-json"
11+ , "console"
12+ , "control"
13+ , "css"
14+ , "effect"
15+ , "either"
16+ , "exceptions"
17+ , "foldable-traversable"
18+ , "foreign-object"
19+ , "halogen"
20+ , "halogen-css"
21+ , "halogen-subscriptions"
22+ , "identity"
23+ , "js-uri"
24+ , "lists"
25+ , "markdown-it"
26+ , "markdown-it-halogen"
27+ , "maybe"
28+ , "newtype"
29+ , "node-buffer"
30+ , "node-fs"
31+ , "node-fs-aff"
32+ , "node-process"
33+ , "node-readline"
34+ , "optparse"
35+ , "ordered-collections"
36+ , "parallel"
37+ , "partial"
38+ , "prelude"
39+ , "profunctor"
40+ , "profunctor-lenses"
41+ , "search-trie"
42+ , "spec"
43+ , "string-parsers"
44+ , "strings"
45+ , "transformers"
46+ , "tuples"
47+ , "unfoldable"
48+ , "web-dom"
49+ , "web-events"
50+ , "web-html"
51+ , "web-storage"
52+ , "web-uievents"
53+ ]
54+, packages = ./packages.dhall
3355}
src/Docs/Search/App.pursnew file+260
@@ -0,0 +1,260 @@
1+-- | This is the main module of the client-side Halogen app.
2+module Docs.Search.App where
3+
4+import Docs.Search.App.SearchField as SearchField
5+import Docs.Search.App.SearchResults as SearchResults
6+import Docs.Search.App.Sidebar as Sidebar
7+import Docs.Search.Config as Config
8+import Docs.Search.Extra (whenJust)
9+import Docs.Search.ModuleIndex as ModuleIndex
10+import Docs.Search.PackageIndex as PackageIndex
11+import Docs.Search.Meta as Meta
12+
13+import Prelude
14+
15+import Control.Alt (alt)
16+import Data.Maybe (Maybe(..))
17+import Data.Newtype (wrap)
18+import Data.Map as Map
19+import Data.Tuple (Tuple(..))
20+import Data.Tuple.Nested ((/\))
21+import Effect (Effect)
22+import Effect.Aff (launchAff_)
23+import Halogen as H
24+import Halogen.Aff as HA
25+import Halogen.Subscription (subscribe)
26+import Halogen.VDom.Driver (runUI)
27+import MarkdownIt as MD
28+import Web.DOM.ChildNode as ChildNode
29+import Web.DOM.Document as Document
30+import Web.DOM.Document (Document)
31+import Web.DOM.Element as Element
32+import Web.DOM.Node as Node
33+import Web.DOM.ParentNode as ParentNode
34+import Web.DOM.Text as Text
35+import Web.Event.EventTarget (addEventListener, eventListener)
36+import Web.HTML as HTML
37+import Web.HTML.Event.EventTypes (focus)
38+import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange)
39+import Web.HTML.HTMLDocument as HTMLDocument
40+import Web.HTML.HTMLElement (fromElement)
41+import Web.HTML.Window as Window
42+
43+
44+main :: Effect Unit
45+main = do
46+ window <- HTML.window
47+
48+ insertStyle
49+ insertVersionInfo
50+ mbContainers <- getContainers
51+
52+ -- Initialize a `markdown-it` instance (we need it to render the docs as markdown)
53+ markdownIt <- MD.newMarkdownIt MD.Default mempty
54+
55+ whenJust mbContainers \ { searchField
56+ , searchResults
57+ , pageContents
58+ , sidebarContainer
59+ , realSidebar
60+ , isIndexHTML
61+ } -> do
62+
63+ -- Hide real sidebar completely - we are going to recreate it as Halogen component.
64+ ChildNode.remove $ Element.toChildNode realSidebar
65+
66+ HA.runHalogenAff do
67+ packageIndex <- PackageIndex.loadPackageIndex
68+ moduleIndex <- ModuleIndex.unpackModuleIndex <$> ModuleIndex.loadModuleIndex
69+ meta <- Meta.load
70+ let scores = PackageIndex.mkScoresFromPackageIndex packageIndex
71+
72+ let initialSearchEngineState = { packageIndex
73+ , moduleIndex
74+ , index: wrap Map.empty
75+ , typeIndex: wrap Map.empty
76+ , scores
77+ }
78+ resultsComponent =
79+ SearchResults.mkComponent
80+ initialSearchEngineState
81+ pageContents
82+ markdownIt
83+ meta
84+
85+ sfio <- runUI SearchField.component unit searchField
86+ srio <- runUI resultsComponent unit searchResults
87+
88+ void $ H.liftEffect $ subscribe sfio.messages $ \sfm -> do
89+ launchAff_ $ void do
90+ srio.query (SearchResults.MessageFromSearchField sfm unit)
91+
92+ -- We need to read the URI hash only when both components are initialized and
93+ -- the search field is subscribed to the main component.
94+ void $ sfio.query $ SearchField.ReadURIHash unit
95+
96+ -- Subscribe to URI hash updates
97+ H.liftEffect do
98+
99+ listener <-
100+ eventListener \_event ->
101+ launchAff_ $ void do
102+ sfio.query $ SearchField.ReadURIHash unit
103+
104+ addEventListener hashchange listener true (Window.toEventTarget window)
105+
106+ sbio <- do
107+ component <- Sidebar.mkComponent moduleIndex isIndexHTML meta
108+ runUI component unit sidebarContainer
109+
110+ -- Subscribe to window focus events
111+ H.liftEffect do
112+
113+ listener <-
114+ eventListener \_event ->
115+ launchAff_ $ void do
116+ sbio.query $ Sidebar.UpdateModuleGrouping unit
117+
118+ addEventListener focus listener true (Window.toEventTarget window)
119+
120+
121+insertStyle :: Effect Unit
122+insertStyle = do
123+ doc <- getDocument
124+
125+ let styleContents = """
126+ .top-banner__actions {
127+ width: 10%;
128+ }
129+ .load_more {
130+ margin-top:2em;
131+ }
132+ .load_more p {
133+ font-style:italic
134+ }
135+ .load_more a {
136+ background:#eee;
137+ padding:0.4em
138+ }
139+ #load-more-link {
140+ cursor: pointer;
141+ }
142+ .result {
143+ font-size: 1.25em;
144+ }
145+ .result__body .keyword, .result__body .syntax {
146+ color: #0B71B4;
147+ }
148+ .badge {
149+ /* Add a margin between badge icons and package/module names. */
150+ margin-right: 0.25em;
151+ }
152+ .li-package > details > summary {
153+ font-weight: bold;
154+ cursor: pointer;
155+ color: #c4953a;
156+ }
157+ .li-package > details > summary:hover {
158+ color: #7b5904;
159+ }
160+ /* Make spaces narrower in the sidebar */
161+ .li-package > details > ul {
162+ margin-top: auto;
163+ margin-bottom: auto;
164+ }
165+ #group-modules__label, #group-modules__input {
166+ display: inline-block;
167+ vertical-align: middle;
168+ color: #666666;
169+ font-size: 0.8rem;
170+ font-weight: 300;
171+ letter-spacing: 1px;
172+ margin-bottom: -0.8em;
173+ }
174+ summary:focus {
175+ outline: none;
176+ }
177+ """
178+
179+ mbHead <-
180+ ParentNode.querySelector (wrap "head") (Document.toParentNode doc)
181+
182+ whenJust mbHead \head -> do
183+ contents <- Document.createTextNode styleContents doc
184+ style <- Document.createElement "style" doc
185+ void $ Node.appendChild (Text.toNode contents) (Element.toNode style)
186+ void $ Node.appendChild (Element.toNode style) (Element.toNode head)
187+
188+
189+insertVersionInfo :: Effect Unit
190+insertVersionInfo = do
191+ doc <- getDocument
192+ let docPN = Document.toParentNode doc
193+ mbVersionInfo <-
194+ ParentNode.querySelector (wrap ".footer > p") docPN
195+ whenJust (mbVersionInfo <#> Element.toNode)
196+ \versionInfo -> do
197+ prefix <- Document.createTextNode " - patched by " doc <#> Text.toNode
198+ linkElement <- Document.createElement "a" doc
199+ let linkNode = Element.toNode linkElement
200+ Element.setAttribute "href" "https://github.com/purescript/purescript-docs-search" linkElement
201+ Element.setAttribute "target" "_blank" linkElement
202+ linkText <- Document.createTextNode ("docs-search") doc <#> Text.toNode
203+ suffix <- Document.createTextNode (" " <> Config.version) doc <#> Text.toNode
204+ void $ Node.appendChild prefix versionInfo
205+ void $ Node.appendChild linkNode versionInfo
206+ void $ Node.appendChild linkText linkNode
207+ void $ Node.appendChild suffix versionInfo
208+
209+
210+-- | Query the DOM for specific elements that should always be present and determine if we are on
211+-- | `index.html` or not.
212+getContainers
213+ :: Effect (Maybe { searchField :: HTML.HTMLElement
214+ , searchResults :: HTML.HTMLElement
215+ , pageContents :: Element.Element
216+ , sidebarContainer :: HTML.HTMLElement
217+ , realSidebar :: Element.Element
218+ , isIndexHTML :: Sidebar.IsIndexHTML
219+ })
220+getContainers = do
221+ doc <- getDocument
222+ let docPN = Document.toParentNode doc
223+ mbBanner <-
224+ ParentNode.querySelector (wrap ".top-banner > .container") docPN
225+ mbEverything <-
226+ ParentNode.querySelector (wrap ".everything-except-footer") docPN
227+ mbContainer <-
228+ ParentNode.querySelector (wrap ".everything-except-footer > .container") docPN
229+ mbMainContainer <-
230+ ParentNode.querySelector (wrap ".everything-except-footer > main") docPN
231+ mbSidebarStatus <-
232+ alt <$> (map (Tuple Sidebar.NotIndexHTML) <$>
233+ ParentNode.querySelector (wrap ".col--aside") docPN)
234+ -- If there's no sidebar, that means we are currently on `index.html`.
235+ <*> (map (Tuple Sidebar.IsIndexHTML) <$>
236+ ParentNode.querySelector (wrap ".col--main") docPN)
237+ case unit of
238+ _ | Just banner <- mbBanner
239+ , Just everything <- mbEverything
240+ , Just pageContents <- mbContainer
241+ , Just mainContainer <- mbMainContainer
242+ , Just (isIndexHTML /\ realSidebar) <- mbSidebarStatus -> do
243+ search <- Document.createElement "div" doc
244+ void $ Node.appendChild (Element.toNode search) (Element.toNode banner)
245+ pure do
246+ searchField <- fromElement search
247+ searchResults <- fromElement everything
248+ sidebarContainer <- fromElement mainContainer
249+ pure { searchField
250+ , searchResults
251+ , pageContents
252+ , realSidebar
253+ , sidebarContainer
254+ , isIndexHTML
255+ }
256+ | otherwise -> pure Nothing
257+
258+
259+getDocument :: Effect Document
260+getDocument = HTML.window >>= map HTMLDocument.toDocument <<< Window.document
src/Docs/Search/App/SearchField.pursnew file+184
@@ -0,0 +1,184 @@
1+-- | This module contains a Halogen component for search field, that emits
2+-- | `SearchFieldMessage`s for various events.
3+module Docs.Search.App.SearchField where
4+
5+import Prelude
6+
7+import CSS (border, borderRadius, color, em, float, floatLeft, fontWeight, lineHeight, marginBottom, marginLeft, paddingBottom, paddingLeft, paddingRight, paddingTop, pct, px, rgb, solid, weight, width)
8+import Data.Maybe (Maybe(..), maybe)
9+import Data.Newtype (wrap)
10+import Docs.Search.URIHash as URIHash
11+import Effect (Effect)
12+import Effect.Aff (Aff)
13+import Halogen as H
14+import Halogen.HTML as HH
15+import Halogen.HTML.CSS as HS
16+import Halogen.HTML.Events as HE
17+import Halogen.HTML.Properties as HP
18+import Halogen.Query.Event as ES
19+import Web.DOM.Document as Document
20+import Web.DOM.ParentNode as ParentNode
21+import Web.HTML as HTML
22+import Web.HTML.HTMLDocument as HTMLDocument
23+import Web.HTML.HTMLElement as HTMLElement
24+import Web.HTML.HTMLInputElement as HTMLInputElement
25+import Web.HTML.Window as Window
26+import Web.UIEvent.KeyboardEvent (KeyboardEvent)
27+import Web.UIEvent.KeyboardEvent as KE
28+import Web.UIEvent.KeyboardEvent as KeyboardEvent
29+import Web.UIEvent.KeyboardEvent.EventTypes as KET
30+
31+type State = { input :: String, focused :: Boolean }
32+
33+data Action
34+ = InputAction String
35+ | EnterPressed
36+ | FocusChanged Boolean
37+ | InitKeyboardListener
38+ | HandleKey H.SubscriptionId KeyboardEvent
39+ | NoOp
40+
41+data Query a
42+ = ReadURIHash a
43+
44+data SearchFieldMessage
45+ = InputUpdated String
46+ | InputCleared
47+ | Focused
48+ | LostFocus
49+
50+component :: forall i. H.Component Query i SearchFieldMessage Aff
51+component =
52+ H.mkComponent
53+ { initialState
54+ , render
55+ , eval: H.mkEval $ H.defaultEval { handleAction = handleAction
56+ , handleQuery = handleQuery
57+ , initialize = Just InitKeyboardListener }
58+ }
59+
60+handleQuery
61+ :: forall a
62+ . Query a
63+ -> H.HalogenM State Action () SearchFieldMessage Aff (Maybe a)
64+handleQuery (ReadURIHash next) = do
65+ oldInput <- H.get <#> _.input
66+ newInput <- H.liftEffect URIHash.getInput
67+ when (oldInput /= newInput) do
68+ H.modify_ (_ { input = newInput })
69+ H.raise (InputUpdated newInput)
70+ pure Nothing
71+
72+initialState :: forall i. i -> State
73+initialState _ = { input: "", focused: false }
74+
75+handleAction :: Action -> H.HalogenM State Action () SearchFieldMessage Aff Unit
76+handleAction = case _ of
77+ NoOp -> pure unit
78+ InitKeyboardListener -> do
79+
80+ document <- H.liftEffect $ Window.document =<< HTML.window
81+ H.subscribe' \sid ->
82+ ES.eventListener
83+ KET.keyup
84+ (HTMLDocument.toEventTarget document)
85+ (map (HandleKey sid) <<< KE.fromEvent)
86+
87+ HandleKey sid ev -> do
88+
89+ when (KE.code ev == "KeyS") do
90+ state <- H.get
91+ when (not state.focused) do
92+ H.liftEffect do
93+ withSearchField HTMLInputElement.select
94+ withSearchField (HTMLInputElement.toHTMLElement >>> HTMLElement.focus)
95+
96+ when (KE.code ev == "Escape") do
97+ state <- H.get
98+ if state.focused
99+ then do
100+ H.liftEffect do
101+ withSearchField (HTMLInputElement.toHTMLElement >>> HTMLElement.blur)
102+ else clearInput
103+
104+ InputAction input -> do
105+ H.modify_ $ (_ { input = input })
106+
107+ EnterPressed -> do
108+ state <- H.get
109+ H.liftEffect do
110+ withSearchField (HTMLInputElement.toHTMLElement >>> HTMLElement.blur)
111+ H.liftEffect (URIHash.setInput state.input)
112+ H.raise $ InputUpdated state.input
113+
114+ FocusChanged isFocused -> do
115+ H.modify_ (_ { focused = isFocused })
116+ H.raise
117+ if isFocused
118+ then Focused
119+ else LostFocus
120+ when isFocused scrollToTop
121+
122+scrollToTop :: H.HalogenM State Action () SearchFieldMessage Aff Unit
123+scrollToTop = do
124+ H.liftEffect do
125+ HTML.window >>= Window.scroll 0 0
126+
127+clearInput :: H.HalogenM State Action () SearchFieldMessage Aff Unit
128+clearInput = do
129+ H.modify_ (_ { input = "" })
130+ H.liftEffect URIHash.removeHash
131+ H.raise InputCleared
132+
133+withSearchField :: (HTML.HTMLInputElement -> Effect Unit) -> Effect Unit
134+withSearchField cont = do
135+ doc <- Document.toParentNode <$>
136+ HTMLDocument.toDocument <$>
137+ (Window.document =<< HTML.window)
138+
139+ let selector = wrap "#docs-search-query-field"
140+
141+ mbEl <- ParentNode.querySelector selector doc
142+ maybe mempty cont (mbEl >>= HTMLInputElement.fromElement)
143+
144+render :: forall m. State -> H.ComponentHTML Action () m
145+render state =
146+ HH.div
147+
148+ [ HS.style do
149+ float floatLeft
150+ lineHeight (px 90.0)
151+ marginBottom (px 0.0)
152+ marginLeft (em 2.0)
153+ width (pct 30.0)
154+ ]
155+
156+ [ HH.input
157+ [ HP.value state.input
158+ , HP.placeholder "Search for definitions... (S to focus)"
159+ , HP.id "docs-search-query-field"
160+ , HP.type_ HP.InputText
161+ , HE.onKeyUp (\event ->
162+ case KeyboardEvent.code event of
163+ "Enter" -> EnterPressed
164+ _ -> NoOp)
165+ , HE.onValueInput InputAction
166+ , HE.onFocusIn $ const $ FocusChanged true
167+ , HE.onFocusOut $ const $ FocusChanged false
168+ , HS.style do
169+
170+ let pursuitColor = rgb 0x1d 0x22 0x2d
171+ rds = px 3.0
172+
173+ border solid (px 1.0) pursuitColor
174+ borderRadius rds rds rds rds
175+ color pursuitColor
176+ fontWeight $ weight 300.0
177+ lineHeight $ em 2.0
178+ paddingLeft $ em 0.8
179+ paddingRight $ em 0.21
180+ paddingTop $ em 0.512
181+ paddingBottom $ em 0.512
182+ width $ pct 100.0
183+ ]
184+ ]
src/Docs/Search/App/SearchResults.pursnew file+715
@@ -0,0 +1,715 @@
1+-- | This module contains a Halogen component for search results.
2+module Docs.Search.App.SearchResults where
3+
4+import Docs.Search.App.SearchField (SearchFieldMessage(..))
5+import Docs.Search.BrowserEngine (PartialIndex, browserSearchEngine)
6+import Docs.Search.Config as Config
7+import Docs.Search.Declarations (DeclLevel(..), declLevelToHashAnchor)
8+import Docs.Search.DocsJson (DataDeclType(..))
9+import Docs.Search.Engine (Result(..), packageInfoToString)
10+import Docs.Search.Engine as Engine
11+import Docs.Search.Extra (homePageFromRepository, (>#>))
12+import Docs.Search.ModuleIndex (ModuleResult)
13+import Docs.Search.PackageIndex (PackageResult)
14+import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
15+import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), QualifiedName(..), Type(..), TypeArgument(..), joinForAlls, joinRows)
16+import Docs.Search.TypeIndex (TypeIndex)
17+import Docs.Search.Types (Identifier(..), ModuleName(..), PackageName)
18+import Docs.Search.Meta (Meta)
19+
20+import Prelude
21+import Prim hiding (Type, Constraint)
22+import Data.Array ((!!))
23+import Data.Array as Array
24+import Data.List as List
25+import Data.Maybe (Maybe(..), isJust, fromMaybe)
26+import Data.Newtype (wrap, unwrap)
27+import Data.String.CodeUnits (stripSuffix) as String
28+import Data.String.Common (null, trim) as String
29+import Data.String.Pattern (Pattern(..)) as String
30+import Effect.Aff (Aff)
31+import Halogen as H
32+import Halogen.HTML as HH
33+import Halogen.HTML.Events as HE
34+import Halogen.HTML.Properties as HP
35+import MarkdownIt as MD
36+import MarkdownIt.Renderer.Halogen as MDH
37+import Web.DOM.Element (Element)
38+import Web.DOM.Element as Element
39+import Web.HTML as HTML
40+import Web.HTML.Location as Location
41+import Web.HTML.Window as Window
42+
43+
44+data Mode = Off | Loading | Active
45+
46+derive instance eqMode :: Eq Mode
47+
48+
49+type EngineState =
50+ Engine.EngineState PartialIndex TypeIndex
51+
52+
53+type State = { engineState :: EngineState
54+ , results :: Array Result
55+ , input :: String
56+ , contents :: Element
57+ , resultsCount :: Int
58+ , mode :: Mode
59+ , markdownIt :: MD.MarkdownIt
60+ , localPackageName :: PackageName
61+ }
62+
63+
64+data Query a
65+ = MessageFromSearchField SearchFieldMessage a
66+
67+
68+data Action
69+ = SearchResultClicked ModuleName
70+ | MoreResultsRequested
71+
72+
73+mkComponent
74+ :: forall o i
75+ . EngineState
76+ -> Element
77+ -> MD.MarkdownIt
78+ -> Meta
79+ -> H.Component Query i o Aff
80+mkComponent initialEngineState contents markdownIt { localPackageName } =
81+ H.mkComponent
82+ { initialState: const { engineState: initialEngineState
83+ , results: []
84+ , input: ""
85+ , contents
86+ , resultsCount: Config.resultsCount
87+ , mode: Off
88+ , markdownIt
89+ , localPackageName
90+ }
91+ , render
92+ , eval: H.mkEval $ H.defaultEval { handleQuery = handleQuery
93+ , handleAction = handleAction }
94+ }
95+
96+
97+handleQuery
98+ :: forall o a
99+ . Query a
100+ -> H.HalogenM State Action () o Aff (Maybe a)
101+handleQuery (MessageFromSearchField Focused next) = do
102+ pure Nothing
103+handleQuery (MessageFromSearchField LostFocus next) = do
104+ pure Nothing
105+handleQuery (MessageFromSearchField InputCleared next) = do
106+ H.modify_ (_ { results = [], input = "", mode = Off })
107+ showPageContents
108+ pure Nothing
109+handleQuery (MessageFromSearchField (InputUpdated input_) next) = do
110+ let input = String.trim input_
111+
112+ state <- H.modify (_ { input = input })
113+
114+ if String.null input
115+ then do
116+ H.modify_ (_ { mode = Off })
117+ showPageContents
118+ else do
119+ H.modify_ (_ { mode = Loading, resultsCount = Config.resultsCount })
120+
121+ void $ H.fork do
122+ { index, results } <- H.liftAff $
123+ Engine.query browserSearchEngine state.engineState state.input
124+
125+ H.modify_ (_ { results = results
126+ , mode = Active
127+ , engineState = index
128+ }
129+ )
130+
131+ hidePageContents
132+
133+ pure Nothing
134+
135+
136+handleAction
137+ :: forall o
138+ . Action
139+ -> H.HalogenM State Action () o Aff Unit
140+handleAction = case _ of
141+ MoreResultsRequested -> do
142+ H.modify_ (\state -> state { resultsCount = state.resultsCount + 25 })
143+
144+ SearchResultClicked moduleName -> do
145+ -- Decide if we are going to load a new page or to jump to a hash on the
146+ -- current page. In the latter case, hide search results and show the main
147+ -- page contents.
148+ onThisPage <- H.liftEffect do
149+ window <- HTML.window
150+ location <- Window.location window
151+ pathname <- Location.pathname location
152+ pure $ isJust $
153+ String.stripSuffix (String.Pattern $ unwrap moduleName <> ".html") pathname
154+
155+ when onThisPage do
156+ showPageContents
157+ H.modify_ (_ { input = "", mode = Off })
158+
159+
160+-- | Inverse of `hidePageContents`
161+showPageContents
162+ :: forall o
163+ . H.HalogenM State Action () o Aff Unit
164+showPageContents = do
165+ state <- H.get
166+ H.liftEffect do
167+ Element.removeAttribute "style" state.contents
168+
169+
170+-- | When search UI is active, we want to hide the main page contents.
171+hidePageContents
172+ :: forall o
173+ . H.HalogenM State Action () o Aff Unit
174+hidePageContents = do
175+ state <- H.get
176+ H.liftEffect do
177+ Element.setAttribute "style" "display: none;" state.contents
178+
179+
180+render
181+ :: forall m
182+ . State
183+ -> H.ComponentHTML Action () m
184+render { mode: Off } = HH.div_ []
185+render { mode: Loading } =
186+ renderContainer $
187+ [ HH.h1_ [ HH.text "Loading..." ] ]
188+render state@{ mode: Active, results: [] } =
189+ renderContainer $
190+
191+ [ HH.h1_ [ HH.text "Search results" ]
192+ , HH.div [ HP.classes [ wrap "result", wrap "result--empty" ] ]
193+ [ HH.text "Your search for "
194+ , HH.strong_ [ HH.text state.input ]
195+ , HH.text " did not yield any results."
196+ ]
197+ ]
198+render state@{ mode: Active } =
199+ renderContainer $
200+ [ HH.h1_ [ HH.text "Search results" ]
201+
202+ , HH.div_ $
203+ Array.concat $ shownResults <#> renderResult state
204+
205+ , HH.div [ HP.class_ (wrap "load_more"), HP.id "load-more" ]
206+ [ if Array.length shownResults < Array.length state.results
207+ then HH.a [ HP.id "load-more-link"
208+ , HE.onClick $ const MoreResultsRequested ]
209+ [ HH.text "Show more results" ]
210+ else HH.p_
211+ [ HH.text "No further results." ]
212+ ]
213+ ]
214+ where
215+ shownResults = Array.take state.resultsCount state.results
216+
217+renderContainer :: forall a b. Array (HH.HTML b a) -> HH.HTML b a
218+renderContainer =
219+ HH.div [ HP.classes [ wrap "container", wrap "clearfix" ] ] <<<
220+ pure <<<
221+ HH.div [ HP.classes [ wrap "col", wrap "col--main" ] ]
222+
223+
224+renderSummary
225+ :: forall a b
226+ . String
227+ -> HH.HTML b a
228+renderSummary text =
229+ HH.div_ [ HH.text text ]
230+
231+
232+renderResult
233+ :: forall a
234+ . State
235+ -> Result
236+ -> Array (HH.HTML a Action)
237+renderResult state (DeclResult r) =
238+ renderSearchResult state r
239+renderResult state (TypeResult r) =
240+ renderSearchResult state r
241+renderResult state (PackResult r) =
242+ renderPackageResult r
243+renderResult state (MdlResult r) =
244+ renderModuleResult r
245+
246+
247+renderPackageResult
248+ :: forall a
249+ . PackageResult
250+ -> Array (HH.HTML a Action)
251+renderPackageResult { name, description, repository } =
252+ [ HH.div [ HP.class_ (wrap "result") ]
253+ [ HH.h3 [ HP.class_ (wrap "result__title") ]
254+ [ HH.span [ HP.classes [ wrap "result__badge"
255+ , wrap "badge"
256+ , wrap "badge--package" ]
257+ , HP.title "Package"
258+ ]
259+ [ HH.text "P" ]
260+
261+ , HH.a [ HP.class_ (wrap "result__link")
262+ , HP.href $ fromMaybe "" repository # homePageFromRepository
263+ ]
264+ [ HH.text $ unwrap name ]
265+ ]
266+ ]
267+ ] <>
268+
269+ description >#> \descriptionText ->
270+ [ HH.div [ HP.class_ (wrap "result__body") ]
271+ [ HH.text descriptionText ]
272+ ]
273+
274+
275+renderModuleResult
276+ :: forall a
277+ . ModuleResult
278+ -> Array (HH.HTML a Action)
279+renderModuleResult { name, package } =
280+ [ HH.div [ HP.class_ (wrap "result") ]
281+ [ HH.h3 [ HP.class_ (wrap "result__title") ]
282+ [ HH.span [ HP.classes [ wrap "result__badge"
283+ , wrap "badge"
284+ , wrap "badge--module" ]
285+ , HP.title "Module"
286+ ]
287+ [ HH.text "M" ]
288+
289+ , HH.a [ HP.class_ (wrap "result__link")
290+ , HP.href $ unwrap name <> ".html"
291+ ]
292+ [ HH.text $ unwrap name ]
293+ ]
294+ ]
295+ ]
296+
297+
298+renderSearchResult
299+ :: forall a
300+ . State
301+ -> SearchResult
302+ -> Array (HH.HTML a Action)
303+renderSearchResult state (SearchResult result) =
304+ -- class names here and below are from Pursuit.
305+ [ HH.div [ HP.class_ (wrap "result") ]
306+ [ HH.h3 [ HP.class_ (wrap "result__title") ]
307+ [ HH.a [ HP.class_ (wrap "result__link")
308+ , HE.onClick $ const $ SearchResultClicked result.moduleName
309+ , HP.href $
310+ unwrap result.moduleName <> ".html#" <>
311+ result.hashAnchor <> ":" <> unwrap result.name
312+ ]
313+ [ HH.text $ unwrap result.name ]
314+ ]
315+ ]
316+
317+ , HH.div [ HP.class_ (wrap "result__body") ] $
318+ renderResultType result <>
319+
320+ result.comments >#> pure <<< MDH.render_ state.markdownIt
321+
322+ , HH.div [ HP.class_ (wrap "result__actions") ]
323+
324+ [ HH.span [ HP.class_ (wrap "result__actions__item") ]
325+ [ HH.span [ HP.classes [ wrap "badge"
326+ , wrap "badge--package"
327+ ]
328+ , HP.title "Package"
329+ ]
330+ [ HH.text "P" ]
331+ , HH.text $ packageInfoToString state.localPackageName result.packageInfo
332+ ]
333+
334+ , HH.span [ HP.class_ (wrap "result__actions__item") ]
335+ [ HH.span [ HP.classes [ wrap "badge"
336+ , wrap "badge--module"
337+ ]
338+ , HP.title "Module"
339+ ]
340+ [ HH.text "M" ]
341+ , HH.text $ unwrap result.moduleName
342+ ]
343+ ]
344+ ]
345+
346+
347+renderResultType
348+ :: forall a rest
349+ . { info :: ResultInfo
350+ , name :: Identifier
351+ , moduleName :: ModuleName
352+ | rest
353+ }
354+ -> Array (HH.HTML a Action)
355+renderResultType result =
356+
357+ case result.info of
358+ ValueResult { type: ty } ->
359+ wrapSignature $ renderValueSignature result ty
360+
361+ TypeClassResult info ->
362+ wrapSignature $ renderTypeClassSignature info result
363+
364+ TypeClassMemberResult info ->
365+ wrapSignature $ renderTypeClassMemberSignature info result
366+
367+ DataResult info ->
368+ wrapSignature $ renderDataSignature info result
369+
370+ TypeSynonymResult info ->
371+ wrapSignature $ renderTypeSynonymSignature info result
372+ _ -> []
373+
374+ where
375+ wrapSignature signature =
376+ [ HH.pre [ HP.class_ (wrap "result__signature") ] [ HH.code_ signature ] ]
377+
378+
379+renderValueSignature
380+ :: forall a rest
381+ . { moduleName :: ModuleName
382+ , name :: Identifier
383+ | rest
384+ }
385+ -> Type
386+ -> Array (HH.HTML a Action)
387+renderValueSignature result ty =
388+ [ HH.a [ makeHref ValueLevel false result.moduleName result.name
389+ , HE.onClick $ const $ SearchResultClicked result.moduleName ]
390+ [ HH.text $ unwrap result.name ]
391+ , HH.text " :: "
392+ , renderType ty ]
393+
394+
395+renderTypeClassSignature
396+ :: forall a rest
397+ . { fundeps :: FunDeps
398+ , arguments :: Array TypeArgument
399+ , superclasses :: Array Constraint
400+ }
401+ -> { name :: Identifier, moduleName :: ModuleName | rest }
402+ -> Array (HH.HTML a Action)
403+renderTypeClassSignature { fundeps, arguments, superclasses } { name, moduleName } =
404+ [ keyword "class"
405+ , if Array.null superclasses
406+ then
407+ HH.text ""
408+ else
409+ HH.span_ $
410+ [ syntax " ("
411+ , HH.span_ $ Array.intercalate [ HH.text ", " ] (
412+ superclasses <#> renderConstraint >>> Array.singleton
413+ )
414+ , syntax ")"
415+ , space
416+ , syntax "<="
417+ ]
418+ , space
419+ , HH.a [ makeHref TypeLevel false moduleName name
420+ , HE.onClick $ const $
421+ SearchResultClicked moduleName
422+ ]
423+ [ HH.text $ unwrap name ]
424+ , space
425+ ] <> (
426+ Array.intercalate [ space ] $
427+ arguments <#> renderTypeArgument
428+ ) <> (
429+ renderFunDeps fundeps
430+ )
431+
432+
433+renderFunDeps :: forall a. FunDeps -> Array (HH.HTML a Action)
434+renderFunDeps (FunDeps []) = []
435+renderFunDeps (FunDeps deps) =
436+ append [ syntax " | " ] $
437+ Array.intercalate [ syntax ", " ] $
438+ deps <#> renderFunDep
439+ where
440+ renderFunDep (FunDep { lhs, rhs }) =
441+ Array.intercalate [ space ] (pure <<< HH.text <$> lhs) <>
442+ [ syntax " -> " ] <>
443+ Array.intercalate [ space ] (pure <<< HH.text <$> rhs)
444+
445+
446+-- | Insert type class name and arguments
447+renderTypeClassMemberSignature
448+ :: forall a rest
449+ . { type :: Type
450+ , typeClass :: QualifiedName
451+ , typeClassArguments :: Array TypeArgument
452+ }
453+ -> { name :: Identifier | rest }
454+ -> Array (HH.HTML a Action)
455+renderTypeClassMemberSignature { type: ty, typeClass, typeClassArguments } result =
456+ [ HH.text $ unwrap result.name
457+ , HH.text " :: "
458+ , renderType ty
459+ ]
460+
461+
462+renderDataSignature
463+ :: forall a rest
464+ . { typeArguments :: Array TypeArgument
465+ , dataDeclType :: DataDeclType }
466+ -> { name :: Identifier | rest }
467+ -> Array (HH.HTML a Action)
468+renderDataSignature { typeArguments, dataDeclType } { name } =
469+ [ keyword
470+ case dataDeclType of
471+ NewtypeDataDecl -> "newtype"
472+ DataDataDecl -> "data"
473+ , space
474+ , HH.text $ unwrap name
475+ , space
476+ , HH.span_ $
477+ Array.intercalate [ space ] $
478+ typeArguments <#> renderTypeArgument
479+ ]
480+
481+
482+renderTypeSynonymSignature
483+ :: forall a rest
484+ . { type :: Type
485+ , arguments :: Array TypeArgument
486+ }
487+ -> { name :: Identifier | rest }
488+ -> Array (HH.HTML a Action)
489+renderTypeSynonymSignature { type: ty, arguments } { name } =
490+ [ keyword "type"
491+ , space
492+ , HH.text $ unwrap name
493+ , space
494+ , HH.span_ $
495+ Array.intercalate [ space ] $
496+ arguments <#> renderTypeArgument
497+ , space
498+ , syntax "="
499+ , space
500+ , renderType ty
501+ ]
502+
503+
504+renderTypeArgument :: forall a. TypeArgument -> Array (HH.HTML a Action)
505+renderTypeArgument (TypeArgument { name, mbKind }) =
506+ case mbKind of
507+ Nothing ->
508+ [ HH.text $ name ]
509+ Just kind ->
510+ [ HH.text "("
511+ , HH.text $ name
512+ , HH.text " :: "
513+ , renderType kind
514+ , HH.text ")"
515+ ]
516+
517+
518+renderType
519+ :: forall a
520+ . Type
521+ -> HH.HTML a Action
522+renderType = case _ of
523+ TypeVar str -> HH.text str
524+ TypeLevelString str -> HH.text $ "\"" <> str <> "\"" -- TODO: add escaping
525+ TypeWildcard -> HH.text "_"
526+ TypeConstructor qname -> renderQualifiedName false TypeLevel qname
527+ TypeOp qname -> renderQualifiedName true TypeLevel qname
528+
529+ TypeApp (TypeApp (TypeConstructor
530+ (QualifiedName { moduleNameParts: [ "Prim" ]
531+ , name: Identifier "Function" })) t1) t2 ->
532+ HH.span_ [ renderType t1
533+ , syntax " -> "
534+ , renderType t2
535+ ]
536+
537+ TypeApp (TypeConstructor (QualifiedName { moduleNameParts: [ "Prim" ]
538+ , name: Identifier "Record" }))
539+ row ->
540+ renderRow false row
541+
542+ TypeApp t1 t2 ->
543+ HH.span_ [ renderType t1
544+ , space
545+ , renderType t2
546+ ]
547+
548+ KindApp t1 t2 ->
549+ HH.span_ [ renderType t1, space, renderType t2 ]
550+
551+ ty@(ForAll _ _ _) ->
552+ renderForAll ty
553+
554+ ConstrainedType cnstr ty ->
555+ HH.span_
556+ [ renderConstraint cnstr
557+ , HH.text " => "
558+ , renderType ty
559+ ]
560+
561+ ty@REmpty -> renderRow true ty
562+ ty@(RCons _ _ _) -> renderRow true ty
563+
564+ Kinded t1 t2 ->
565+ HH.span_ [ renderType t1, space, syntax "::", space, renderType t2 ]
566+
567+ BinaryNoParensType op t1 t2 ->
568+ HH.span_
569+ [ renderType t1
570+ , space
571+ , renderType op
572+ , space
573+ , renderType t2
574+ ]
575+
576+ ParensInType ty ->
577+ HH.span_
578+ [ HH.text "("
579+ , renderType ty
580+ , HH.text ")"
581+ ]
582+
583+
584+renderForAll
585+ :: forall a
586+ . Type
587+ -> HH.HTML a Action
588+renderForAll ty =
589+ HH.span_ $
590+
591+ [ keyword "forall" ] <>
592+
593+ ( Array.fromFoldable foralls.binders <#>
594+ \ { name, mbKind } ->
595+ case mbKind of
596+ Nothing -> HH.text (" " <> name)
597+ Just kind ->
598+ HH.span_ [ HH.text $ " (" <> name <> " "
599+ , syntax "::"
600+ , space
601+ , renderType kind
602+ , HH.text ")" ]
603+ ) <>
604+
605+ [ syntax ". ", renderType foralls.ty ]
606+
607+ where
608+ foralls = joinForAlls ty
609+
610+
611+renderRow
612+ :: forall a
613+ . Boolean
614+ -> Type
615+ -> HH.HTML a Action
616+renderRow asRow =
617+ joinRows >>> \ { rows, ty } ->
618+
619+ HH.span_ $
620+
621+ if List.null rows
622+ then
623+ [ if asRow
624+ then HH.text "()"
625+ else
626+ fromMaybe (HH.text "{}") $
627+ ty <#> \ty' ->
628+ HH.span_
629+ [ renderQualifiedName false TypeLevel primRecord
630+ , HH.text " "
631+ , renderType ty'
632+ ]
633+ ]
634+ else
635+ [ HH.text opening ] <>
636+
637+ ( Array.intercalate [ HH.text ", " ] $ Array.fromFoldable $ rows <#>
638+ \entry ->
639+ [ HH.span_ [ HH.text $ unwrap entry.row <> " :: "
640+ , renderType entry.ty ] ]
641+ ) <>
642+
643+ (ty >#> \ty' -> [ HH.text " | ", renderType ty' ]) <>
644+
645+ [ HH.text closing ]
646+
647+ where
648+ opening = if asRow then "( " else "{ "
649+ closing = if asRow then " )" else " }"
650+
651+ primRecord :: QualifiedName
652+ primRecord = QualifiedName { moduleNameParts: [ "Prim" ], name: Identifier "Record" }
653+
654+
655+renderConstraint
656+ :: forall a
657+ . Constraint
658+ -> HH.HTML a Action
659+renderConstraint (Constraint { constraintClass, constraintArgs }) =
660+ HH.span_ $
661+ [ renderQualifiedName false TypeLevel constraintClass, space ] <>
662+ Array.intercalate [ space ] (constraintArgs <#> \ty -> [ renderType ty ])
663+
664+
665+renderQualifiedName
666+ :: forall a
667+ . Boolean
668+ -> DeclLevel
669+ -> QualifiedName
670+ -> HH.HTML a Action
671+renderQualifiedName isInfix level (QualifiedName { moduleNameParts, name })
672+ = if isBuiltIn then
673+ HH.text $ unwrap name
674+ else
675+ HH.a [ HE.onClick $ const $
676+ SearchResultClicked $ moduleName
677+ , makeHref level isInfix moduleName name
678+ ]
679+ [ HH.text $ unwrap name ]
680+ where
681+ moduleName = ModuleName $ Array.intercalate "." $ moduleNameParts
682+ isBuiltIn = moduleNameParts !! 0 == Just "Prim"
683+
684+
685+-- | Construct a `href` property value w.r.t. `DeclLevel`.
686+makeHref
687+ :: forall t rest
688+ . DeclLevel
689+ -> Boolean
690+ -> ModuleName
691+ -> Identifier
692+ -> HH.IProp ( href :: String | rest ) t
693+makeHref level isInfix moduleName name =
694+ HP.href $
695+ unwrap moduleName <> ".html#" <>
696+ declLevelToHashAnchor level <> ":" <>
697+ if isInfix then "type (" <> unwrap name <> ")" else unwrap name
698+
699+
700+keyword
701+ :: forall a
702+ . String
703+ -> HH.HTML a Action
704+keyword str = HH.span [ HP.class_ (wrap "keyword") ] [ HH.text str ]
705+
706+
707+syntax
708+ :: forall a
709+ . String
710+ -> HH.HTML a Action
711+syntax str = HH.span [ HP.class_ (wrap "syntax") ] [ HH.text str ]
712+
713+
714+space :: forall a b. HH.HTML a b
715+space = HH.text " "
src/Docs/Search/App/Sidebar.pursnew file+218
@@ -0,0 +1,218 @@
1+module Docs.Search.App.Sidebar where
2+
3+import Docs.Search.Config as Config
4+import Docs.Search.Meta (Meta)
5+import Docs.Search.ModuleIndex (ModuleIndex)
6+import Docs.Search.Types (ModuleName(..), PackageInfo(..), PackageName)
7+
8+import Prelude
9+
10+import Data.Array as Array
11+import Data.Lens ((.~))
12+import Data.Lens.Record (prop)
13+import Data.List (foldr)
14+import Data.Map (Map)
15+import Data.Map as Map
16+import Data.Maybe (Maybe(..), isJust, fromMaybe)
17+import Data.Newtype (wrap, unwrap)
18+import Data.Set (Set)
19+import Data.Set as Set
20+import Data.Tuple.Nested (type (/\), (/\))
21+import Effect (Effect)
22+import Effect.Aff (Aff)
23+import Halogen as H
24+import Halogen.HTML as HH
25+import Halogen.HTML.Events as HE
26+import Halogen.HTML.Properties as HP
27+import Type.Proxy (Proxy(..))
28+import Web.HTML as HTML
29+import Web.HTML.Window as Window
30+import Web.Storage.Storage as Storage
31+import Web.DOM.Document as Document
32+import Web.DOM.Element as Element
33+import Web.HTML.HTMLDocument as HTMLDocument
34+import Web.DOM.ParentNode as ParentNode
35+import Data.Traversable (traverse)
36+import Web.DOM.Node as Node
37+
38+
39+data Action = ToggleGrouping GroupingMode
40+
41+data Query a = UpdateModuleGrouping a
42+
43+data GroupingMode = GroupByPackage | DontGroup
44+
45+derive instance groupingModeEq :: Eq GroupingMode
46+
47+-- | Whether current page is `index.html` or not. `index.html` is special, it
48+-- | has no sidebar, hence the difference must be taken into account.
49+data IsIndexHTML = IsIndexHTML | NotIndexHTML
50+
51+derive instance isIndexHTMLEq :: Eq IsIndexHTML
52+
53+type State = { packageModules :: Map PackageInfo (Set ModuleName)
54+ , groupingMode :: GroupingMode
55+ , moduleNames :: Array ModuleName
56+ , isIndexHTML :: IsIndexHTML
57+ , localPackageName :: PackageName
58+ , currentPackage :: PackageInfo
59+ }
60+
61+
62+mkComponent
63+ :: forall i
64+ . ModuleIndex
65+ -> IsIndexHTML
66+ -> Meta
67+ -> Aff (H.Component Query i Action Aff)
68+mkComponent moduleIndex@{ packageModules } isIndexHTML { localPackageName } = do
69+ groupingMode <- H.liftEffect loadGroupingModeFromLocalStorage
70+ mbModuleName <- H.liftEffect getCurrentModuleName
71+ let currentPackage = getCurrentPackage moduleIndex mbModuleName
72+ pure $
73+ H.mkComponent
74+ { initialState: const { packageModules
75+ , groupingMode
76+ , moduleNames
77+ , isIndexHTML
78+ , localPackageName
79+ , currentPackage
80+ }
81+ , render
82+ , eval: H.mkEval $ H.defaultEval { handleAction = handleAction
83+ , handleQuery = handleQuery
84+ }
85+ }
86+ where
87+ moduleNames =
88+ Array.sort $ Array.fromFoldable $
89+ foldr Set.union mempty moduleIndex.packageModules
90+
91+
92+handleAction
93+ :: forall o
94+ . Action
95+ -> H.HalogenM State Action () o Aff Unit
96+handleAction (ToggleGrouping groupingMode) = do
97+ H.modify_ (_groupingMode .~ groupingMode)
98+
99+ H.liftEffect do
100+ window <- HTML.window
101+ localStorage <- Window.localStorage window
102+
103+ if groupingMode == DontGroup
104+ then Storage.setItem Config.groupModulesItem "false" localStorage
105+ else Storage.removeItem Config.groupModulesItem localStorage
106+
107+
108+handleQuery
109+ :: forall a i
110+ . Query a
111+ -> H.HalogenM State i () Action Aff (Maybe a)
112+handleQuery (UpdateModuleGrouping next) = do
113+ oldGroupingMode <- H.get <#> _.groupingMode
114+ newGroupingMode <- H.liftEffect loadGroupingModeFromLocalStorage
115+ when (oldGroupingMode /= newGroupingMode) do
116+ H.modify_ (_groupingMode .~ newGroupingMode)
117+ pure Nothing
118+
119+
120+render
121+ :: forall m
122+ . State
123+ -> H.ComponentHTML Action () m
124+render state@{ groupingMode, moduleNames, localPackageName } =
125+
126+ HH.div [ HP.classes [ wrap "col"
127+ , wrap $ if state.isIndexHTML == IsIndexHTML
128+ then "col--main"
129+ else "col--aside"
130+ ]
131+ ]
132+
133+ [ HH.h3_ [ HH.text $ if groupingMode == DontGroup then "Modules" else "Packages" ]
134+ , HH.input [ HP.id "group-modules__input"
135+ , HP.type_ HP.InputCheckbox
136+ , HP.checked (groupingMode == GroupByPackage)
137+ , HE.onChecked $ ToggleGrouping <<< isCheckedToGroupingMode
138+ ]
139+
140+ , HH.text " "
141+ , HH.label [ HP.for "group-modules__input"
142+ , HP.id "group-modules__label"
143+ ]
144+ [ HH.text " GROUP BY PACKAGE" ]
145+
146+ , HH.ul_ $ if groupingMode == GroupByPackage
147+ then renderPackageEntry <$> packageList
148+ else renderModuleName <$> moduleNames
149+ ]
150+ where
151+
152+ renderPackageEntry (package /\ modules) =
153+ HH.li [ HP.classes [ wrap "li-package" ] ]
154+ [ HH.details
155+ (if isCurrentPackage then [ HP.attr (wrap "open") "" ] else [])
156+ [ HH.summary_
157+ [ HH.text $
158+ case package of
159+ Package packageName -> unwrap packageName
160+ LocalPackage -> unwrap localPackageName
161+ Builtin -> "<builtins>"
162+ UnknownPackage -> "<unknown>"
163+ ]
164+ , HH.ul_ $ Set.toUnfoldable modules <#> renderModuleName
165+ ]
166+ ]
167+ where
168+
169+ isCurrentPackage =
170+ state.currentPackage == package &&
171+ -- If we don't know which package we are in, we don't want to expand
172+ -- "Unknown package" section
173+ state.currentPackage /= UnknownPackage
174+
175+ renderModuleName moduleName =
176+ HH.li_
177+ [ HH.a [ HP.href (unwrap moduleName <> ".html") ]
178+ [ HH.text $ unwrap moduleName ]
179+ ]
180+
181+ packageList :: Array (PackageInfo /\ Set ModuleName)
182+ packageList = Map.toUnfoldable state.packageModules
183+
184+
185+-- | Decide whether to group modules by package in the sidebar, using localStorage.
186+loadGroupingModeFromLocalStorage :: Effect GroupingMode
187+loadGroupingModeFromLocalStorage = do
188+ window <- HTML.window
189+ localStorage <- Window.localStorage window
190+ mbDontGroupModules <- Storage.getItem Config.groupModulesItem localStorage
191+ pure $ if isJust mbDontGroupModules then DontGroup else GroupByPackage
192+
193+
194+-- | Extract current module name from page title element.
195+getCurrentModuleName :: Effect (Maybe ModuleName)
196+getCurrentModuleName = do
197+ win <- HTML.window
198+ docPN <- Document.toParentNode <$> HTMLDocument.toDocument <$> Window.document win
199+ mbPageTitleEl <- ParentNode.querySelector (wrap ".page-title__title") docPN
200+ map ModuleName <$> traverse (Node.textContent <<< Element.toNode) mbPageTitleEl
201+
202+
203+-- | Given a module name, get `PackageInfo` for a package this module name belongs to.
204+getCurrentPackage :: ModuleIndex -> Maybe ModuleName -> PackageInfo
205+getCurrentPackage { modulePackages } (Just moduleName) =
206+ fromMaybe UnknownPackage $ Map.lookup moduleName modulePackages
207+getCurrentPackage { modulePackages } Nothing = UnknownPackage
208+
209+
210+-- | Convert checkbox status to sidebar mode
211+isCheckedToGroupingMode :: Boolean -> GroupingMode
212+isCheckedToGroupingMode = if _ then GroupByPackage else DontGroup
213+
214+
215+-- Some optics:
216+
217+_groupingMode :: forall a b rest. (a -> b) -> { groupingMode :: a | rest } -> { groupingMode :: b | rest }
218+_groupingMode = prop (Proxy :: Proxy "groupingMode")
src/Docs/Search/BrowserEngine.jsnew file+30
@@ -0,0 +1,30 @@
1+/* global exports */
2+
3+export function loadIndex_ (partId) {
4+ return function (url) {
5+ return function () {
6+ return new Promise(function(resolve, reject) {
7+ if (typeof window.DocsSearchIndex[partId] === 'undefined') {
8+
9+ var script = document.createElement('script');
10+ script.type = 'text/javascript';
11+ script.src = url;
12+
13+ script.addEventListener('load', function () {
14+ if (typeof window.DocsSearchIndex[partId] == 'undefined') {
15+ reject();
16+ } else {
17+ resolve(window.DocsSearchIndex[partId]);
18+ }
19+ });
20+
21+ script.addEventListener('error', reject);
22+
23+ document.body.appendChild(script);
24+ } else {
25+ resolve(window.DocsSearchIndex[partId]);
26+ }
27+ });
28+ };
29+ };
30+};
src/Docs/Search/BrowserEngine.pursnew file+130
@@ -0,0 +1,130 @@
1+-- | A search engine that is used in the browser.
2+module Docs.Search.BrowserEngine where
3+
4+import Docs.Search.Config as Config
5+import Docs.Search.PackageIndex (queryPackageIndex)
6+import Docs.Search.Engine (Engine, EngineState, Index)
7+import Docs.Search.SearchResult (SearchResult)
8+import Docs.Search.TypeIndex (TypeIndex)
9+import Docs.Search.TypeIndex as TypeIndex
10+import Docs.Search.Types (PartId(..), URL)
11+import Docs.Search.ModuleIndex as ModuleIndex
12+
13+import Prelude
14+
15+import Data.Char as Char
16+import Control.Promise (Promise, toAffE)
17+import Data.Argonaut.Core (Json)
18+import Data.Argonaut.Decode (decodeJson)
19+import Data.Array as Array
20+import Data.Either (hush)
21+import Data.List (List, (:))
22+import Data.List as List
23+import Data.Map (Map)
24+import Data.Map as Map
25+import Data.Maybe (Maybe(..))
26+import Data.Newtype (class Newtype)
27+import Data.Search.Trie (Trie)
28+import Data.Search.Trie as Trie
29+import Data.String.CodeUnits as String
30+import Data.Tuple (Tuple(..))
31+import Effect (Effect)
32+import Effect.Aff (Aff, try)
33+
34+
35+newtype PartialIndex
36+ = PartialIndex (Map PartId Index)
37+
38+derive instance newtypePartialIndex :: Newtype PartialIndex _
39+
40+type BrowserEngineState = EngineState PartialIndex TypeIndex
41+
42+
43+-- | This function dynamically injects a script with the required index part and returns
44+-- | a new `PartialIndex` that contains newly loaded definitions.
45+-- |
46+-- | We split the index because of its size, and also to speed up queries.
47+query
48+ :: PartialIndex
49+ -> String
50+ -> Aff { index :: PartialIndex, results :: Array SearchResult }
51+query index@(PartialIndex indexMap) input = do
52+ let
53+ path =
54+ List.fromFoldable $
55+ String.toCharArray $
56+ input
57+
58+ partId = getPartId path
59+
60+ case Map.lookup partId indexMap of
61+ Just trie ->
62+ pure { index, results: flatten $ Trie.queryValues path trie }
63+ Nothing -> do
64+
65+
66+ eiPartJson <-
67+ try $ toAffE $ loadIndex_ partId $ Config.mkIndexPartLoadPath partId
68+
69+ let
70+ mbNewTrie :: Maybe (Trie Char (List SearchResult))
71+ mbNewTrie = do
72+ json <- hush eiPartJson
73+ results <- hush (decodeJson json) :: Maybe (Array (Tuple String (Array SearchResult)))
74+ pure $ Array.foldr insertResults mempty results
75+
76+ case mbNewTrie of
77+ Just newTrie -> do
78+ pure { index: PartialIndex $ Map.insert partId newTrie indexMap
79+ , results: flatten $ Trie.queryValues path newTrie
80+ }
81+ Nothing -> do
82+ pure { index, results: mempty }
83+
84+ where
85+ flatten = Array.concat <<< Array.fromFoldable <<< map Array.fromFoldable
86+
87+
88+insertResults
89+ :: Tuple String (Array SearchResult)
90+ -> Trie Char (List SearchResult)
91+ -> Trie Char (List SearchResult)
92+insertResults (Tuple path newResults) =
93+ Trie.alter pathList insert
94+ where
95+ pathList = List.fromFoldable $ String.toCharArray path
96+
97+ insert
98+ :: Maybe (List SearchResult)
99+ -> Maybe (List SearchResult)
100+ insert mbOldResults =
101+ case mbOldResults of
102+ Nothing -> Just $ List.fromFoldable newResults
103+ Just old -> Just $ List.fromFoldable newResults <> old
104+
105+
106+browserSearchEngine
107+ :: Engine Aff PartialIndex TypeIndex
108+browserSearchEngine =
109+ { queryIndex: query
110+ , queryTypeIndex: TypeIndex.query
111+ , queryPackageIndex
112+ , queryModuleIndex: ModuleIndex.queryModuleIndex
113+ }
114+
115+
116+
117+-- | Find in which part of the index this path can be found.
118+getPartId :: List Char -> PartId
119+getPartId (a : b : _) =
120+ PartId $ (Char.toCharCode a + Char.toCharCode b) `mod` Config.numberOfIndexParts
121+getPartId (a : _) =
122+ PartId $ Char.toCharCode a `mod` Config.numberOfIndexParts
123+getPartId _ = PartId 0
124+
125+
126+-- | Load a part of the index by injecting a <script> tag into the DOM.
127+foreign import loadIndex_
128+ :: PartId
129+ -> URL
130+ -> Effect (Promise Json)
src/Docs/Search/Config.pursnew file+85
@@ -0,0 +1,85 @@
1+module Docs.Search.Config where
2+
3+import Docs.Search.Types (GlobalIdentifier, PackageName, PartId, URL, FilePath)
4+
5+import Prelude
6+
7+import Data.Newtype (wrap)
8+
9+version :: String
10+version = "0.0.12"
11+
12+mkShapeScriptPath :: String -> String
13+mkShapeScriptPath shape = "./index/types/" <> shape <> ".js"
14+
15+-- | In how many parts the index should be splitted?
16+numberOfIndexParts :: Int
17+numberOfIndexParts = 50
18+
19+mkIndexPartPath :: PartId -> String
20+mkIndexPartPath partId = "html/index/declarations/" <> show partId <> ".js"
21+
22+mkIndexPartLoadPath :: PartId -> URL
23+mkIndexPartLoadPath partId = wrap $ "./index/declarations/" <> show partId <> ".js"
24+
25+moduleIndexPath :: FilePath
26+moduleIndexPath = wrap "generated-docs/html/index/modules.js"
27+
28+-- | Used to load mode index to the browser scope.
29+moduleIndexLoadPath :: String
30+moduleIndexLoadPath = "./index/modules.js"
31+
32+typeIndexDirectory :: FilePath
33+typeIndexDirectory = wrap "generated-docs/html/index/types"
34+
35+metaPath :: FilePath
36+metaPath = wrap "generated-docs/html/index/meta.js"
37+
38+metaLoadPath :: URL
39+metaLoadPath = wrap "./index/meta.js"
40+
41+metaItem :: GlobalIdentifier
42+metaItem = wrap "DocsSearchMeta"
43+
44+-- | localStorage key to save sidebar checkbox value to.
45+groupModulesItem :: String
46+groupModulesItem = "PureScriptDocsSearchGroupModules"
47+
48+packageInfoPath :: FilePath
49+packageInfoPath = wrap "generated-docs/html/index/packages.js"
50+
51+packageInfoItem :: GlobalIdentifier
52+packageInfoItem = wrap "DocsSearchPackageIndex"
53+
54+packageInfoLoadPath :: URL
55+packageInfoLoadPath = wrap "./index/packages.js"
56+
57+-- | How many results to show by default?
58+resultsCount :: Int
59+resultsCount = 25
60+
61+-- | Penalties used to determine how "far" a type query is from a given type.
62+-- See Docs.Search.TypeQuery
63+penalties ::
64+ { excessiveConstraint :: Int
65+ , generalize :: Int
66+ , instantiate :: Int
67+ , match :: Int
68+ , matchConstraint :: Int
69+ , missingConstraint :: Int
70+ , rowsMismatch :: Int
71+ , typeVars :: Int
72+ }
73+penalties =
74+ { typeVars: 2
75+ , match: 2
76+ , matchConstraint: 1
77+ , instantiate: 2
78+ , generalize: 2
79+ , rowsMismatch: 3
80+ , missingConstraint: 1
81+ , excessiveConstraint: 1
82+ }
83+
84+defaultPackageName :: PackageName
85+defaultPackageName = wrap "<local package>"
src/Docs/Search/Declarations.pursnew file+327
@@ -0,0 +1,327 @@
1+module Docs.Search.Declarations where
2+
3+import Docs.Search.DocsJson (ChildDeclType(..), ChildDeclaration(..), DeclType(..), Declaration(..), DocsJson(..), SourceSpan)
4+import Docs.Search.Score (Scores, getPackageScore, getPackageScoreForPackageName)
5+import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
6+import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..), joinForAlls)
7+import Docs.Search.Types (ModuleName(..), PackageName(..), PackageInfo(..), Identifier(..))
8+
9+import Prelude
10+import Prim hiding (Type)
11+import Control.Alt ((<|>))
12+import Data.Array ((!!))
13+import Data.Array as Array
14+import Data.Foldable (foldl, foldr)
15+import Data.List (List, (:))
16+import Data.List as List
17+import Data.Maybe (Maybe(..), fromMaybe)
18+import Data.Newtype (class Newtype, unwrap, wrap)
19+import Data.Search.Trie (Trie, alter)
20+import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray)
21+import Data.String.Common (split) as String
22+import Data.String.Common (toLower)
23+import Data.String.Pattern (Pattern(..))
24+
25+
26+newtype Declarations = Declarations (Trie Char (List SearchResult))
27+
28+derive instance newtypeDeclarations :: Newtype Declarations _
29+derive newtype instance semigroupDeclarations :: Semigroup Declarations
30+derive newtype instance monoidDeclarations :: Monoid Declarations
31+
32+
33+mkDeclarations :: Scores -> Array DocsJson -> Declarations
34+mkDeclarations scores = Declarations <<< foldr (insertDocsJson scores) mempty
35+
36+
37+insertDocsJson
38+ :: Scores
39+ -> DocsJson
40+ -> Trie Char (List SearchResult)
41+ -> Trie Char (List SearchResult)
42+insertDocsJson scores (DocsJson { name, declarations }) trie
43+ = foldr (insertDeclaration scores $ ModuleName name) trie declarations
44+
45+
46+insertDeclaration
47+ :: Scores
48+ -> ModuleName
49+ -> Declaration
50+ -> Trie Char (List SearchResult)
51+ -> Trie Char (List SearchResult)
52+insertDeclaration scores moduleName entry@(Declaration { title }) trie
53+ = foldr insertSearchResult trie (resultsForDeclaration scores moduleName entry)
54+
55+
56+insertSearchResult
57+ :: { path :: String
58+ , result :: SearchResult
59+ }
60+ -> Trie Char (List SearchResult)
61+ -> Trie Char (List SearchResult)
62+insertSearchResult { path, result } trie =
63+ let path' = List.fromFoldable $ toCharArray $ toLower path in
64+ alter path' (Just <<< updateResults) trie
65+ where
66+ updateResults mbOldResults
67+ | Just oldResults <- mbOldResults =
68+ result : oldResults
69+ | otherwise =
70+ List.singleton result
71+
72+
73+-- | For each declaration, extract its own `SearchResult` and `SearchResult`s
74+-- | corresponding to its children (e.g. a class declaration contains class members).
75+resultsForDeclaration
76+ :: Scores
77+ -> ModuleName
78+ -> Declaration
79+ -> List { path :: String
80+ , result :: SearchResult
81+ }
82+resultsForDeclaration scores moduleName indexEntry@(Declaration entry) =
83+ case mkInfo declLevel indexEntry of
84+ Nothing -> mempty
85+ Just info' ->
86+ let result = SearchResult { name: Identifier title
87+ , comments
88+ , hashAnchor: declLevelToHashAnchor declLevel
89+ , moduleName
90+ , sourceSpan
91+ , packageInfo
92+ , score:
93+ fromMaybe zero $ getPackageScoreForPackageName scores <$> mbPackageName
94+ , info: info'
95+ }
96+ in
97+ ( List.singleton $
98+ { path: name
99+ , result
100+ }
101+ ) <>
102+ ( List.fromFoldable children >>=
103+ resultsForChildDeclaration scores packageInfo moduleName result
104+ )
105+ where
106+ { info, title, sourceSpan, comments, children } = entry
107+ { name, declLevel } = getLevelAndName info.declType title
108+ packageInfo = extractPackageName moduleName sourceSpan
109+ mbPackageName =
110+ case packageInfo of
111+ Package packageName -> Just packageName
112+ _ -> Nothing
113+
114+
115+mkInfo :: DeclLevel -> Declaration -> Maybe ResultInfo
116+mkInfo declLevel (Declaration { info, title }) =
117+ case info.declType of
118+
119+ DeclValue ->
120+ info."type" <#> \ty -> ValueResult { type: ty }
121+
122+ DeclData ->
123+ make <$> info.typeArguments <*> info.dataDeclType
124+ where
125+ make typeArguments dataDeclType =
126+ DataResult { typeArguments, dataDeclType }
127+
128+ DeclExternData ->
129+ info.kind <#> \kind -> ExternDataResult { kind }
130+
131+ DeclTypeSynonym ->
132+ make <$> info."type" <*> info.arguments
133+ where
134+ make ty args = TypeSynonymResult { "type": ty, arguments: args }
135+
136+ DeclTypeClass
137+ | Just fundeps <- info.fundeps
138+ , Just arguments <- info.arguments
139+ , Just superclasses <- info.superclasses ->
140+ Just $ TypeClassResult { fundeps, arguments, superclasses }
141+ | otherwise -> Nothing
142+
143+ DeclAlias ->
144+ case declLevel of
145+ TypeLevel -> Just TypeAliasResult
146+ ValueLevel -> Just ValueAliasResult
147+ _ -> Nothing
148+
149+ DeclExternKind ->
150+ Just ExternKindResult
151+
152+
153+-- | Level of a declaration, used to determine which URI hash anchor to use in
154+-- | links ("v", "t" or "k").
155+data DeclLevel = ValueLevel | TypeLevel | KindLevel
156+
157+
158+declLevelToHashAnchor :: DeclLevel -> String
159+declLevelToHashAnchor = case _ of
160+ ValueLevel -> "v"
161+ TypeLevel -> "t"
162+ KindLevel -> "k"
163+
164+
165+getLevelAndName
166+ :: DeclType
167+ -> String
168+ -> { declLevel :: DeclLevel
169+ , name :: String
170+ }
171+getLevelAndName DeclValue name = { name, declLevel: ValueLevel }
172+getLevelAndName DeclData name = { name, declLevel: TypeLevel }
173+getLevelAndName DeclTypeSynonym name = { name, declLevel: TypeLevel }
174+getLevelAndName DeclTypeClass name = { name, declLevel: TypeLevel }
175+-- "declType": "alias" does not specify the level of the declaration.
176+-- But for type aliases, name of the declaration is always wrapped into
177+-- "type (" and ")".
178+getLevelAndName DeclAlias title =
179+ fromMaybe (withAnchor ValueLevel title) $
180+ (withAnchor ValueLevel <$>
181+ (stripPrefix (Pattern "(") >=>
182+ stripSuffix (Pattern ")")) title) <|>
183+ (withAnchor TypeLevel <$>
184+ (stripPrefix (Pattern "type (") >=>
185+ stripSuffix (Pattern ")")) title)
186+ where
187+ withAnchor declLevel name = { declLevel, name }
188+getLevelAndName DeclExternData name = { name, declLevel: TypeLevel }
189+getLevelAndName DeclExternKind name = { name, declLevel: KindLevel }
190+
191+
192+-- | Extract package name from `sourceSpan.name`, which contains path to
193+-- | the source file. If `ModuleName` string starts with `Prim.`, it's a
194+-- | built-in (guaranteed by the compiler).
195+extractPackageName :: ModuleName -> Maybe SourceSpan -> PackageInfo
196+extractPackageName (ModuleName moduleName) _
197+ | String.split (Pattern ".") moduleName !! 0 == Just "Prim" = Builtin
198+extractPackageName _ Nothing = UnknownPackage
199+extractPackageName _ (Just { name }) =
200+ fromMaybe LocalPackage do
201+ topLevelDir <- dirs !! 0
202+ if topLevelDir == ".spago"
203+ then Package <<< PackageName <$> dirs !! 1
204+ else do
205+ bowerDirIx <- Array.findIndex (_ == "bower_components") dirs
206+ Package <<< PackageName <$> dirs !! (bowerDirIx + 1)
207+ where dirs = String.split (Pattern "/") name
208+
209+
210+-- | Extract `SearchResults` from a `ChildDeclaration`.
211+resultsForChildDeclaration
212+ :: Scores
213+ -> PackageInfo
214+ -> ModuleName
215+ -> SearchResult
216+ -> ChildDeclaration
217+ -> List { path :: String, result :: SearchResult }
218+resultsForChildDeclaration scores packageInfo moduleName parentResult
219+ child@(ChildDeclaration { title, info, comments, mbSourceSpan })
220+ | Just resultInfo <- mkChildInfo parentResult child =
221+ { path: title
222+ , result: SearchResult { name: Identifier title
223+ , comments
224+ -- `ChildDeclaration`s are always either data
225+ -- constructors, type class members or instances.
226+ -- The former two are both value-level, and
227+ -- the latter are not included in the index.
228+ , hashAnchor: "v"
229+ , moduleName
230+ , sourceSpan: mbSourceSpan
231+ , packageInfo
232+ , score: getPackageScore scores packageInfo
233+ , info: resultInfo
234+ }
235+ } # List.singleton
236+ | otherwise = mempty
237+
238+
239+mkChildInfo
240+ :: SearchResult
241+ -> ChildDeclaration
242+ -> Maybe ResultInfo
243+mkChildInfo
244+ (SearchResult { info: parentInfo, moduleName, name: resultName })
245+ (ChildDeclaration { info } )
246+
247+ | ChildDeclDataConstructor <- info.declType
248+ , DataResult { dataDeclType, typeArguments } <- parentInfo =
249+ let
250+ parentTypeCtor :: Type
251+ parentTypeCtor = TypeConstructor
252+ $ QualifiedName { moduleNameParts:
253+ String.split (wrap ".") (unwrap moduleName)
254+ , name: resultName }
255+
256+ parentTypeArgs :: Array Type
257+ parentTypeArgs = typeArguments <#> unwrap >>> \{ name } -> TypeVar name
258+
259+ parentType :: Type
260+ parentType = foldl TypeApp parentTypeCtor parentTypeArgs
261+
262+ typeArrow :: Type -> Type
263+ typeArrow =
264+ TypeApp (TypeConstructor (QualifiedName { moduleNameParts: [ "Prim" ]
265+ , name: Identifier "Function" }))
266+
267+ makeType :: Array Type -> Type
268+ makeType = foldr (\a b -> TypeApp (typeArrow a) b) parentType
269+ in
270+ info.arguments <#> \arguments ->
271+ DataConstructorResult { dataDeclType
272+ , "type": makeType arguments
273+ }
274+
275+ | ChildDeclTypeClassMember <- info.declType
276+ , TypeClassResult { arguments } <- parentInfo =
277+ -- We need to reconstruct a "real" type of a type class member.
278+ -- For example, if `unconstrainedType` is the type of `pure`, i.e.
279+ -- `forall a. a -> m a`, then `restoredType` should be:
280+ -- `forall m a. Control.Applicative.Applicative m => a -> m a`.
281+
282+ info."type" <#>
283+ \(unconstrainedType :: Type) ->
284+ let
285+ -- First, we get a list of nested `forall` quantifiers for
286+ -- `unconstrainedType` and a version of `unconstrainedType` without
287+ -- them (`ty`).
288+ ({ ty, binders }) = joinForAlls unconstrainedType
289+
290+ -- Then we construct a qualified name of the type class.
291+ constraintClass =
292+ QualifiedName { moduleNameParts:
293+ String.split (wrap ".") $ unwrap moduleName
294+ , name: resultName }
295+
296+ -- We concatenate two lists:
297+ -- * a list of type parameters of the type class, and
298+ -- * a list of quantified variables of the unconstrained type
299+ allArguments :: Array { name :: String, mbKind :: Maybe Type }
300+ allArguments =
301+ (arguments <#> unwrap) <> List.toUnfoldable binders
302+
303+ restoreType :: Type -> Type
304+ restoreType =
305+ foldr
306+ (\({ name, mbKind }) -> compose (\type'' -> ForAll name mbKind type''))
307+ identity
308+ allArguments
309+
310+ -- Finally, we have a restored type. It allows us to search for
311+ -- type members the same way we search for functions. And types
312+ -- of class member results appear with the correct
313+ -- class constraints.
314+ restoredType =
315+ restoreType $
316+ ConstrainedType
317+ (Constraint { constraintClass
318+ , constraintArgs: arguments <#> unwrap >>> (_.name) >>> TypeVar
319+ }) ty
320+
321+ in TypeClassMemberResult
322+ { type: restoredType
323+ , typeClass: constraintClass
324+ , typeClassArguments: arguments
325+ }
326+
327+ | otherwise = Nothing
src/Docs/Search/DocsJson.pursnew file+224
@@ -0,0 +1,224 @@
1+-- | A module containing everything that is necessary to decode `docs.json` files.
2+module Docs.Search.DocsJson where
3+
4+import Prelude
5+import Prim hiding (Type, Constraint)
6+import Docs.Search.TypeDecoder (Constraint, FunDeps, Type, TypeArgument)
7+
8+import Data.Argonaut.Core (fromString, stringify, toString)
9+import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:), (.:?))
10+import Data.Argonaut.Decode.Error (JsonDecodeError(..))
11+import Data.Argonaut.Encode (class EncodeJson, encodeJson)
12+import Data.Either (Either(..))
13+import Data.Generic.Rep (class Generic)
14+import Data.Show.Generic (genericShow)
15+import Data.Maybe (Maybe(..))
16+import Data.Newtype (class Newtype, unwrap)
17+
18+
19+newtype DocsJson
20+ = DocsJson { name :: String
21+ , declarations :: Array Declaration
22+ }
23+
24+derive instance eqDocsJson :: Eq DocsJson
25+derive instance genericDocsJson :: Generic DocsJson _
26+derive instance newtypeDocsJson :: Newtype DocsJson _
27+
28+instance showDocsJson :: Show DocsJson where
29+ show = genericShow
30+
31+instance decodeJsonDocsJson :: DecodeJson DocsJson where
32+ decodeJson json = DocsJson <$> decodeJson json
33+
34+instance encodeJsonDocsJson :: EncodeJson DocsJson where
35+ encodeJson = encodeJson <<< unwrap
36+
37+type SourceSpan = { start :: Array Int
38+ , end :: Array Int
39+ , name :: String
40+ }
41+
42+newtype Declaration
43+ = Declaration { title :: String
44+ , comments :: Maybe String
45+ , info :: { declType :: DeclType
46+ , dataDeclType :: Maybe DataDeclType
47+ , kind :: Maybe Type
48+ , typeArguments :: Maybe (Array TypeArgument)
49+ , type :: Maybe Type
50+ , superclasses :: Maybe (Array Constraint)
51+ , arguments :: Maybe (Array TypeArgument)
52+ , fundeps :: Maybe FunDeps
53+ }
54+ , sourceSpan :: Maybe SourceSpan
55+ , children :: Array ChildDeclaration
56+ }
57+
58+derive instance eqDeclaration :: Eq Declaration
59+derive instance genericDeclaration :: Generic Declaration _
60+derive instance newtypeDeclaration :: Newtype Declaration _
61+
62+instance showDeclaration :: Show Declaration where
63+ show = genericShow
64+
65+instance decodeJsonDeclaration :: DecodeJson Declaration where
66+ decodeJson json = Declaration <$> do
67+ handle <- decodeJson json
68+ title <- handle .: "title"
69+ comments <- handle .:? "comments"
70+ children <- handle .: "children"
71+ info <- handle .: "info" >>= \info -> do
72+ ty <- info .:? "type"
73+ kind <- info .:? "kind"
74+ typeArguments <- info .:? "typeArguments"
75+ arguments <- info .:? "arguments"
76+ superclasses <- info .:? "superclasses"
77+ fundeps <- info .:? "fundeps"
78+ declType <- info .: "declType"
79+ dataDeclType <- info .:? "dataDeclType"
80+ pure { type: ty, kind, declType, typeArguments, superclasses, fundeps
81+ , arguments, dataDeclType }
82+ sourceSpan <- handle .: "sourceSpan"
83+ pure { title, comments, info, sourceSpan, children }
84+
85+instance encodeJsonDeclaration :: EncodeJson Declaration where
86+ encodeJson = encodeJson <<< unwrap
87+
88+
89+newtype ChildDeclaration
90+ = ChildDeclaration { title :: String
91+ , comments :: Maybe String
92+ , info :: { declType :: ChildDeclType
93+ , arguments :: Maybe (Array Type)
94+ , type :: Maybe Type
95+ }
96+ , mbSourceSpan :: Maybe { start :: Array Int
97+ , end :: Array Int
98+ , name :: String
99+ }
100+ }
101+
102+derive instance eqChildDeclaration :: Eq ChildDeclaration
103+derive instance genericChildDeclaration :: Generic ChildDeclaration _
104+derive instance newtypeChildDeclaration :: Newtype ChildDeclaration _
105+
106+instance showChildDeclaration :: Show ChildDeclaration where
107+ show = genericShow
108+
109+instance decodeJsonChildDeclaration :: DecodeJson ChildDeclaration where
110+ decodeJson json = ChildDeclaration <$> do
111+ handle <- decodeJson json
112+ title <- handle .: "title"
113+ comments <- handle .:? "comments"
114+ info <- handle .: "info" >>= \info -> do
115+ arguments <- info .:? "arguments"
116+ ty <- info .:? "type"
117+ declType <- info .: "declType"
118+ pure { arguments, declType, type: ty }
119+ mbSourceSpan <- handle .:? "sourceSpan"
120+ pure { title, comments, info, mbSourceSpan }
121+
122+instance encodeJsonChildDeclaration :: EncodeJson ChildDeclaration where
123+ encodeJson = encodeJson <<< unwrap
124+
125+
126+-- See `src/Language/Purescript/Docs/Types.hs`
127+data DeclType
128+ = DeclValue
129+ | DeclData
130+ | DeclExternData
131+ | DeclTypeSynonym
132+ | DeclTypeClass
133+ | DeclAlias
134+ | DeclExternKind
135+
136+derive instance eqDeclType :: Eq DeclType
137+derive instance genericDeclType :: Generic DeclType _
138+
139+instance showDeclType :: Show DeclType where
140+ show = genericShow
141+
142+instance encodeJsonDeclType :: EncodeJson DeclType where
143+ encodeJson = fromString <<< case _ of
144+ DeclValue -> "value"
145+ DeclData -> "data"
146+ DeclExternData -> "externData"
147+ DeclTypeSynonym -> "typeSynonym"
148+ DeclTypeClass -> "typeClass"
149+ DeclAlias -> "alias"
150+ DeclExternKind -> "kind"
151+
152+instance decodeJsonDeclType :: DecodeJson DeclType where
153+ decodeJson json =
154+ case toString json of
155+ Nothing -> errorWith $ "Couldn't decode DeclType: " <> stringify json
156+ Just string ->
157+ case string of
158+ "value" -> Right DeclValue
159+ "data" -> Right DeclData
160+ "externData" -> Right DeclExternData
161+ "typeSynonym" -> Right DeclTypeSynonym
162+ "typeClass" -> Right DeclTypeClass
163+ "alias" -> Right DeclAlias
164+ "kind" -> Right DeclExternKind
165+ _ -> errorWith $ "Couldn't decode DeclType: " <> string
166+
167+
168+data ChildDeclType
169+ = ChildDeclInstance
170+ | ChildDeclDataConstructor
171+ | ChildDeclTypeClassMember
172+
173+derive instance eqChildDeclType :: Eq ChildDeclType
174+derive instance genericChildDeclType :: Generic ChildDeclType _
175+
176+instance showChildDeclType :: Show ChildDeclType where
177+ show = genericShow
178+
179+instance encodeJsonChildDeclType :: EncodeJson ChildDeclType where
180+ encodeJson = fromString <<< case _ of
181+ ChildDeclInstance -> "instance"
182+ ChildDeclDataConstructor -> "dataConstructor"
183+ ChildDeclTypeClassMember -> "typeClassMember"
184+
185+instance decodeJsonChildDeclType :: DecodeJson ChildDeclType where
186+ decodeJson json =
187+ case toString json of
188+ Nothing -> errorWith $ "Couldn't decode ChildDeclType: " <> stringify json
189+ Just tag ->
190+ case tag of
191+ "instance" -> Right ChildDeclInstance
192+ "dataConstructor" -> Right ChildDeclDataConstructor
193+ "typeClassMember" -> Right ChildDeclTypeClassMember
194+ _ -> errorWith $ "Couldn't decode ChildDeclType: " <> tag
195+
196+
197+data DataDeclType
198+ = NewtypeDataDecl
199+ | DataDataDecl
200+
201+derive instance eqDataDeclType :: Eq DataDeclType
202+derive instance genericDataDeclType :: Generic DataDeclType _
203+
204+instance showDataDeclType :: Show DataDeclType where
205+ show = genericShow
206+
207+instance encodeJsonDataDeclType :: EncodeJson DataDeclType where
208+ encodeJson = fromString <<< case _ of
209+ NewtypeDataDecl -> "newtype"
210+ DataDataDecl -> "data"
211+
212+instance decodeJsonDataDeclType :: DecodeJson DataDeclType where
213+ decodeJson json =
214+ case toString json of
215+ Just tag ->
216+ case tag of
217+ "newtype" -> Right NewtypeDataDecl
218+ "data" -> Right DataDataDecl
219+ _ -> errorWith $ "Couldn't decode DataDeclType: " <> tag
220+ Nothing -> errorWith $ "Couldn't decode DataDeclType: " <> stringify json
221+
222+
223+errorWith :: forall a. String -> Either JsonDecodeError a
224+errorWith = Left <<< TypeMismatch
src/Docs/Search/Engine.pursnew file+167
@@ -0,0 +1,167 @@
1+module Docs.Search.Engine where
2+
3+import Docs.Search.ModuleIndex (ModuleIndex, ModuleResult)
4+import Docs.Search.PackageIndex (PackageIndex, PackageResult)
5+import Docs.Search.Score (Scores)
6+import Docs.Search.SearchResult (SearchResult, typeOfResult)
7+import Docs.Search.TypeQuery (TypeQuery(..), parseTypeQuery, penalty)
8+import Docs.Search.Types (PackageInfo(..), ModuleName(..), PackageName(..), PackageScore)
9+
10+import Prelude
11+
12+import Data.Array as Array
13+import Data.Either (hush)
14+import Data.Function (on)
15+import Data.List (List)
16+import Data.Maybe (Maybe(..))
17+import Data.Newtype (unwrap)
18+import Data.Search.Trie (Trie)
19+import Data.String.Common (toLower) as String
20+
21+
22+type Index = Trie Char (List SearchResult)
23+
24+
25+type Query m index input result =
26+ index -> input -> m { index :: index, results :: Array result }
27+
28+
29+type Engine m index typeIndex =
30+ { queryIndex
31+ :: Query m index String SearchResult
32+ , queryTypeIndex
33+ :: Query m typeIndex TypeQuery SearchResult
34+ , queryPackageIndex
35+ :: Query m PackageIndex String PackageResult
36+ , queryModuleIndex
37+ :: Scores -> ModuleIndex -> String -> Array ModuleResult
38+ }
39+
40+
41+type EngineState index typeIndex
42+ = { index :: index
43+ , typeIndex :: typeIndex
44+ , packageIndex :: PackageIndex
45+ , moduleIndex :: ModuleIndex
46+ , scores :: Scores
47+ }
48+
49+
50+mkEngineState
51+ :: forall index typeIndex
52+ . index
53+ -> typeIndex
54+ -> PackageIndex
55+ -> ModuleIndex
56+ -> Scores
57+ -> EngineState index typeIndex
58+mkEngineState index typeIndex packageIndex moduleIndex scores =
59+ { index, typeIndex, packageIndex, moduleIndex, scores }
60+
61+
62+data Result
63+ = DeclResult SearchResult
64+ | TypeResult SearchResult
65+ | PackResult PackageResult
66+ | MdlResult ModuleResult
67+
68+
69+getResultScore :: Result -> PackageScore
70+getResultScore (DeclResult r) = (unwrap r).score
71+getResultScore (TypeResult r) = (unwrap r).score
72+getResultScore (PackResult r) = r.score
73+getResultScore (MdlResult r) = r.score
74+
75+
76+getResultPackageInfo :: Result -> PackageInfo
77+getResultPackageInfo (DeclResult r) = (unwrap r).packageInfo
78+getResultPackageInfo (TypeResult r) = (unwrap r).packageInfo
79+getResultPackageInfo (PackResult r) = Package r.name
80+getResultPackageInfo (MdlResult r) = r.package
81+
82+
83+getResultModuleName :: Result -> ModuleName
84+getResultModuleName (DeclResult r) = (unwrap r).moduleName
85+getResultModuleName (TypeResult r) = (unwrap r).moduleName
86+getResultModuleName (PackResult r) = ModuleName ""
87+getResultModuleName (MdlResult r) = r.name
88+
89+
90+getResultName :: Result -> String
91+getResultName (DeclResult r) = unwrap (unwrap r).name
92+getResultName (TypeResult r) = unwrap (unwrap r).name
93+getResultName (PackResult r) = unwrap r.name
94+getResultName (MdlResult r) = unwrap r.name
95+
96+
97+sortByPopularity
98+ :: forall index typeIndex
99+ . EngineState index typeIndex
100+ -> Array Result
101+ -> Array Result
102+sortByPopularity { packageIndex } =
103+ Array.sortBy (
104+ compare `on` (getResultScore >>> negate) <>
105+ compare `on` getResultPackageInfo <>
106+ compare `on` getResultModuleName <>
107+ -- Identifier name comes last: we want to make sure no `Result`s are
108+ -- equal, to avoid having unstable ordering.
109+ compare `on` getResultName
110+ )
111+
112+
113+query
114+ :: forall m index typeIndex
115+ . Monad m
116+ => Engine m index typeIndex
117+ -> Query m (EngineState index typeIndex) String Result
118+query engine state input =
119+ case hush (parseTypeQuery input) >>= isValuableTypeQuery of
120+
121+ -- A declaration/package query
122+ Nothing -> do
123+ let lowerCased = String.toLower input
124+
125+ response <- engine.queryIndex state.index lowerCased
126+ packageResponse <- engine.queryPackageIndex state.packageIndex lowerCased
127+ let moduleResponse =
128+ engine.queryModuleIndex state.scores state.moduleIndex lowerCased
129+
130+ pure { results: sortByPopularity state $
131+ (packageResponse.results <#> PackResult) <>
132+ (moduleResponse <#> MdlResult) <>
133+ (response.results <#> DeclResult)
134+
135+ -- No need to update package index (it never changes).
136+ , index: state { index = response.index }
137+ }
138+
139+ -- A type query
140+ Just typeQuery -> do
141+
142+ response <- engine.queryTypeIndex state.typeIndex typeQuery
143+
144+ pure { results: sortByDistance typeQuery (response.results) <#> TypeResult
145+ , index: state { typeIndex = response.index }
146+ }
147+
148+
149+isValuableTypeQuery :: TypeQuery -> Maybe TypeQuery
150+isValuableTypeQuery (QVar _) = Nothing
151+isValuableTypeQuery (QConst _) = Nothing
152+isValuableTypeQuery other = Just other
153+
154+
155+sortByDistance
156+ :: TypeQuery
157+ -> Array SearchResult
158+ -> Array SearchResult
159+sortByDistance typeQuery =
160+ Array.sortWith (map (penalty typeQuery) <<< typeOfResult)
161+
162+
163+packageInfoToString :: PackageName -> PackageInfo -> String
164+packageInfoToString _ (Package (PackageName p)) = p
165+packageInfoToString _ Builtin = "<builtin>"
166+packageInfoToString localPackageName LocalPackage = unwrap localPackageName
167+packageInfoToString _ UnknownPackage = "<unknown package>"
src/Docs/Search/Extra.pursnew file+47
@@ -0,0 +1,47 @@
1+module Docs.Search.Extra where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Foldable (class Foldable, foldMap, foldl)
7+import Data.List (List, (:))
8+import Data.List as List
9+import Data.List.NonEmpty (NonEmptyList, cons', uncons)
10+import Data.String.CodeUnits as String
11+import Data.Newtype (wrap)
12+import Data.Maybe (Maybe(..), fromMaybe)
13+
14+whenJust :: forall a m. Monad m => Maybe a -> (a -> m Unit) -> m Unit
15+whenJust (Just a) f = f a
16+whenJust _ _ = pure unit
17+
18+foldMapFlipped :: forall a m f. Foldable f => Monoid m => f a -> (a -> m) -> m
19+foldMapFlipped = flip foldMap
20+
21+infixr 7 foldMapFlipped as >#>
22+
23+foldl1 :: forall a. (a -> a -> a) -> NonEmptyList a -> a
24+foldl1 f as =
25+ case uncons as of
26+ { head, tail } -> foldl f head tail
27+
28+foldr1 :: forall a. (a -> a -> a) -> NonEmptyList a -> a
29+foldr1 f = go List.Nil
30+ where
31+ go acc x = case uncons x of
32+ { head, tail } -> case List.uncons tail of
33+ Nothing -> List.foldl (flip f) head acc
34+ Just { head: head1, tail: tail1 } ->
35+ go (head : acc) (cons' head1 tail1)
36+
37+-- | Try to guess repository main page on github from git URL.
38+homePageFromRepository :: String -> String
39+homePageFromRepository repo =
40+ fromMaybe repo $ String.stripSuffix (wrap ".git") $
41+ fromMaybe repo $ String.stripPrefix (wrap "git:") repo <#> ("https:" <> _)
42+
43+stringToList :: String -> List Char
44+stringToList = List.fromFoldable <<< String.toCharArray
45+
46+listToString :: List Char -> String
47+listToString = String.fromCharArray <<< Array.fromFoldable
src/Docs/Search/IndexBuilder.jsnew file+13
@@ -0,0 +1,13 @@
1+/* global __dirname require exports */
2+
3+import globMain from "glob";
4+
5+export function getDirname () {
6+ return __dirname;
7+};
8+
9+export function glob (pattern) {
10+ return function () {
11+ return globMain.sync(pattern);
12+ };
13+};
src/Docs/Search/IndexBuilder.pursnew file+450
@@ -0,0 +1,450 @@
1+module Docs.Search.IndexBuilder where
2+
3+import Docs.Search.BrowserEngine (getPartId)
4+import Docs.Search.Config as Config
5+import Docs.Search.Declarations (Declarations(..), mkDeclarations)
6+import Docs.Search.DocsJson (DocsJson)
7+import Docs.Search.Extra ((>#>))
8+import Docs.Search.Meta (Meta)
9+import Docs.Search.ModuleIndex (PackedModuleIndex, mkPackedModuleIndex)
10+import Docs.Search.ModuleParser (parseModuleName)
11+import Docs.Search.PackageIndex (PackageInfo, mkPackageInfo)
12+import Docs.Search.Score (mkScores)
13+import Docs.Search.SearchResult (SearchResult)
14+import Docs.Search.TypeIndex (TypeIndex, mkTypeIndex)
15+import Docs.Search.Types (ModuleName, PackageName, PartId)
16+
17+import Prelude
18+
19+import Data.Argonaut.Core (stringify)
20+import Data.Argonaut.Decode (decodeJson)
21+import Data.Argonaut.Decode.Error (printJsonDecodeError)
22+import Data.Argonaut.Encode (encodeJson)
23+import Data.Argonaut.Parser (jsonParser)
24+import Data.Array (concat)
25+import Data.Array as Array
26+import Data.Either (Either(..), either)
27+import Data.Foldable (sum)
28+import Data.List (List)
29+import Data.List as List
30+import Data.Map (Map)
31+import Data.Map as Map
32+import Data.Maybe (Maybe(..), fromMaybe)
33+import Data.Newtype (unwrap)
34+import Data.Profunctor.Choice (left)
35+import Data.Search.Trie as Trie
36+import Data.Set as Set
37+import Data.String.CodePoints (contains) as String
38+import Data.String.CodeUnits (singleton) as String
39+import Data.String.Common (replace) as String
40+import Data.String.Pattern (Pattern(..), Replacement(..))
41+import Data.Traversable (for, for_)
42+import Data.Tuple (Tuple(..), fst)
43+import Data.Tuple.Nested ((/\))
44+import Effect (Effect)
45+import Effect.Aff (Aff, launchAff_, parallel, sequential)
46+import Effect.Class (liftEffect)
47+import Effect.Console (log)
48+import Node.Encoding (Encoding(UTF8))
49+import Node.FS.Aff (mkdir, readFile, readTextFile, readdir, stat, writeFile, writeTextFile)
50+import Node.FS.Stats (isDirectory, isFile)
51+import Node.FS.Sync (exists)
52+import Node.Process as Process
53+import Web.Bower.PackageMeta (PackageMeta(..))
54+
55+
56+type Config =
57+ { docsFiles :: Array String
58+ , bowerFiles :: Array String
59+ , generatedDocs :: String
60+ , noPatch :: Boolean
61+ , packageName :: PackageName
62+ , sourceFiles :: Array String
63+ }
64+
65+
66+run :: Config -> Effect Unit
67+run = launchAff_ <<< run'
68+
69+
70+run' :: Config -> Aff Unit
71+run' cfg = do
72+
73+ checkDirectories cfg
74+
75+ liftEffect do
76+ log "Building the search index..."
77+
78+ docsJsons /\ moduleNames /\ packageMetas <- sequential $
79+ (\d h m -> d /\ h /\ m)
80+ <$> parallel (decodeDocsJsons cfg)
81+ <*> parallel (parseModuleHeaders cfg.sourceFiles)
82+ <*> parallel (decodeBowerJsons cfg)
83+
84+ let countOfPackages = Array.length packageMetas
85+ countOfModules = Array.length docsJsons
86+
87+ liftEffect do
88+ log $
89+ "Indexing " <>
90+ show countOfModules <>
91+ " modules from " <>
92+ show countOfPackages <>
93+ " packages..."
94+
95+ let scores = mkScores packageMetas
96+ index = mkDeclarations scores docsJsons
97+ typeIndex = mkTypeIndex scores docsJsons
98+ packageInfo = mkPackageInfo scores packageMetas
99+ moduleIndex = mkPackedModuleIndex index moduleNames
100+ meta = { localPackageName: cfg.packageName }
101+
102+ createDirectories cfg
103+
104+ void $ sequential do
105+ ignore <$> parallel (writeIndex cfg index)
106+ <*> parallel (writeTypeIndex typeIndex)
107+ <*> parallel (writePackageInfo packageInfo)
108+ <*> parallel (writeModuleIndex moduleIndex)
109+ <*> parallel (writeMeta meta)
110+ <*> parallel (when (not cfg.noPatch) $ patchDocs cfg)
111+ <*> parallel (copyAppFile cfg)
112+
113+ let countOfDefinitions = Trie.size $ unwrap index
114+ countOfTypeDefinitions =
115+ sum $ fromMaybe 0 <$> map Array.length <$> Map.values (unwrap typeIndex)
116+
117+ liftEffect do
118+ log $
119+ "Added " <>
120+ show countOfDefinitions <>
121+ " definitions and " <>
122+ show countOfTypeDefinitions <>
123+ " type definitions from " <>
124+ show countOfPackages <>
125+ " packages to the search index."
126+
127+ where ignore _ _ _ _ _ _ _ = unit
128+
129+
130+-- | Exit early if something is missing.
131+checkDirectories :: Config -> Aff Unit
132+checkDirectories cfg = do
133+
134+ let dirs = [ cfg.generatedDocs
135+ , cfg.generatedDocs <> "/html"
136+ ]
137+
138+ for_ dirs \dir -> do
139+ whenM (not <$> directoryExists dir) $
140+ liftEffect do
141+ logAndExit "Build the documentation first!"
142+
143+
144+-- | Read and decode given `docs.json` files.
145+decodeDocsJsons
146+ :: forall rest
147+ . { docsFiles :: Array String | rest }
148+ -> Aff (Array DocsJson)
149+decodeDocsJsons cfg@{ docsFiles } = do
150+
151+ paths <- getPathsByGlobs docsFiles
152+
153+ when (Array.null paths) do
154+ liftEffect do
155+ logAndExit $
156+ "The following globs do not match any files: " <> showGlobs cfg.docsFiles <>
157+ ".\nBuild the documentation first!"
158+
159+ docsJsons <- Array.catMaybes <$> for paths \jsonFile -> do
160+ doesExist <- fileExists jsonFile
161+
162+ if doesExist then do
163+
164+ contents <- readTextFile UTF8 jsonFile
165+ let eiResult = jsonParser contents >>= left printJsonDecodeError <<< decodeJson
166+
167+ case eiResult of
168+ Left error -> do
169+ liftEffect $ log $
170+ "\"docs.json\" decoding failed failed for " <> jsonFile <> ": " <> error
171+ pure Nothing
172+ Right result -> pure result
173+
174+ else do
175+ liftEffect $ do
176+ log $
177+ "File does not exist: " <> jsonFile
178+ pure Nothing
179+
180+ when (Array.null docsJsons) do
181+ liftEffect $ logAndExit $
182+ "Couldn't decode any of the files matched by the following globs: " <> showGlobs cfg.docsFiles
183+
184+ pure docsJsons
185+
186+-- | This function accepts an array of globs pointing to project sources
187+-- | and returns a list of module names extracted from these files.
188+-- | Unfortunately, we can't get all module names from `docs.json`s, because
189+-- | reexport-only modules do not have a single declaration in them, so we can't
190+-- | count them normally in `mkPackedModuleIndex.extract`, where we only look at
191+-- | exported declarations.
192+parseModuleHeaders :: Array String -> Aff (Array ModuleName)
193+parseModuleHeaders globs = do
194+ files <- getPathsByGlobs globs
195+
196+ -- we are not checking if the globs match at least one file, because
197+ -- we want to support scenarios where there is no "current project"
198+
199+ concat <$> for files \filePath -> do
200+ fileContents <- readTextFile UTF8 filePath
201+ case parseModuleName fileContents of
202+ Nothing -> do
203+ liftEffect $ log $
204+ "Module header decoding failed for " <> filePath <>
205+ ", unable to extract module name"
206+ pure []
207+ Just res -> pure [res]
208+
209+decodeBowerJsons
210+ :: forall rest
211+ . { bowerFiles :: Array String | rest }
212+ -> Aff (Array PackageMeta)
213+decodeBowerJsons { bowerFiles } = do
214+ paths <- getPathsByGlobs bowerFiles
215+
216+ when (Array.null paths) do
217+ liftEffect do
218+ logAndExit $
219+ "The following globs do not match any files: " <> showGlobs bowerFiles <>
220+ ".\nAre you in a project directory?"
221+
222+ Array.nubBy compareNames <$>
223+ Array.catMaybes <$>
224+ for paths \jsonFileName ->
225+ join <$> withExisting jsonFileName
226+ \contents ->
227+ either (logError jsonFileName) pure
228+ (jsonParser contents >>= left printJsonDecodeError <<< decodeJson)
229+
230+ where
231+ compareNames
232+ (PackageMeta { name: name1 })
233+ (PackageMeta { name: name2 }) = compare name1 name2
234+
235+ logError fileName error = do
236+ liftEffect $ log $
237+ "\"bower.json\" decoding failed failed for " <> fileName <> ": " <> error
238+ pure Nothing
239+
240+
241+-- | Write type index parts to files.
242+writeTypeIndex :: TypeIndex -> Aff Unit
243+writeTypeIndex typeIndex =
244+ for_ entries \(Tuple typeShape results) -> do
245+ writeTextFile UTF8 (unwrap Config.typeIndexDirectory <> "/" <> typeShape <> ".js")
246+ (mkHeader typeShape <> stringify (encodeJson results))
247+ where
248+ mkHeader typeShape =
249+ "// This file was generated by purescript-docs-search\n" <>
250+ "window.DocsSearchTypeIndex[\"" <> typeShape <> "\"] = "
251+ entries :: Array _
252+ entries = Map.toUnfoldableUnordered (unwrap typeIndex)
253+
254+
255+writePackageInfo :: PackageInfo -> Aff Unit
256+writePackageInfo packageInfo = do
257+
258+ writeTextFile UTF8 (unwrap Config.packageInfoPath) $
259+ header <> stringify (encodeJson packageInfo)
260+
261+ where
262+ header = "window.DocsSearchPackageIndex = "
263+
264+
265+writeModuleIndex :: PackedModuleIndex -> Aff Unit
266+writeModuleIndex moduleIndex = do
267+ writeTextFile UTF8 (unwrap Config.moduleIndexPath) $
268+ header <> stringify (encodeJson moduleIndex)
269+ where
270+ header = "window.DocsSearchModuleIndex = "
271+
272+writeMeta :: Meta -> Aff Unit
273+writeMeta meta = do
274+ writeTextFile UTF8 (unwrap Config.metaPath) $
275+ header <> stringify (encodeJson meta)
276+ where
277+ header = "window." <> unwrap Config.metaItem <> " = "
278+
279+-- | Get a mapping from index parts to index contents.
280+getIndex :: Declarations -> Map PartId (Array (Tuple String (Array SearchResult)))
281+getIndex (Declarations trie) =
282+ Array.foldr insert Map.empty parts
283+ where
284+ insert part = Map.insertWith append (getPartId part.prefix) part.results
285+
286+ parts
287+ :: Array { prefix :: List Char
288+ , results :: Array (Tuple String (Array SearchResult))
289+ }
290+ parts = prefixes <#> \prefix ->
291+ let results =
292+ Array.fromFoldable $ toTuple <$>
293+ if List.length prefix == 2 then
294+ Trie.query prefix trie
295+ else
296+ -- Entries with path lengths > 1 have been added already.
297+ List.filter (\(Tuple path _value) -> List.length path == 1) (
298+ Trie.query prefix trie
299+ )
300+ in
301+ { prefix, results }
302+
303+ toTuple (Tuple path value) =
304+ Tuple (path >#> String.singleton) (Array.fromFoldable value)
305+
306+ prefixes :: Array (List Char)
307+ prefixes =
308+ Set.toUnfoldable $
309+ List.foldr (\path -> Set.insert (List.take 2 path)) mempty $
310+ fst <$> Trie.entriesUnordered trie
311+
312+
313+writeIndex :: Config -> Declarations -> Aff Unit
314+writeIndex { generatedDocs } = getIndex >>> \resultsMap -> do
315+ for_ (Map.toUnfoldableUnordered resultsMap :: Array _)
316+ \(Tuple indexPartId results) -> do
317+ let header =
318+ "// This file was generated by purescript-docs-search.\n" <>
319+ "window.DocsSearchIndex[\"" <> show indexPartId <> "\"] = "
320+
321+ writeTextFile UTF8 (generatedDocs <> Config.mkIndexPartPath indexPartId) $
322+ header <> stringify (encodeJson results)
323+
324+
325+patchHTML :: String -> Tuple Boolean String
326+patchHTML html =
327+ let
328+ pattern = Pattern "</body>"
329+ patch = "<!-- Docs search index. -->" <>
330+ "<script type=\"text/javascript\" src=\"./docs-search-app.js\"></script>" <>
331+ "<script type=\"text/javascript\">" <>
332+ "window.DocsSearchTypeIndex = {};" <>
333+ "window.DocsSearchIndex = {};" <>
334+ "</script>" <>
335+ "</body>"
336+ in if not $ String.contains (Pattern patch) html
337+ then Tuple true $ String.replace pattern (Replacement patch) html
338+ else Tuple false html
339+
340+
341+-- | Iterate through the HTML files generated by the PureScript compiler, and
342+-- | modify them using `patchHTML`.
343+patchDocs :: Config -> Aff Unit
344+patchDocs cfg = do
345+ let dirname = cfg.generatedDocs
346+
347+ files <- readdir (dirname <> "html")
348+
349+ for_ files \file -> do
350+ let path = dirname <> "html/" <> file
351+
352+ whenM (fileExists path) do
353+ contents <- readTextFile UTF8 path
354+ case patchHTML contents of
355+ Tuple true patchedContents -> do
356+ writeTextFile UTF8 path patchedContents
357+ _ -> pure unit
358+
359+
360+-- | Create directories for two indices, or fail with a message
361+-- | in case the docs were not generated.
362+createDirectories :: Config -> Aff Unit
363+createDirectories { generatedDocs } = do
364+ let htmlDocs = generatedDocs <> "/html"
365+ indexDir = generatedDocs <> "/html/index"
366+ declIndexDir = generatedDocs <> "/html/index/declarations"
367+ typeIndexDir = generatedDocs <> "/html/index/types"
368+
369+ whenM (not <$> directoryExists generatedDocs) $ liftEffect do
370+ logAndExit "Generate the documentation first!"
371+
372+ whenM (not <$> directoryExists htmlDocs) $ liftEffect do
373+ logAndExit "Generate the documentation first!"
374+
375+ whenM (not <$> directoryExists indexDir) do
376+ mkdir indexDir
377+
378+ whenM (not <$> directoryExists declIndexDir) do
379+ mkdir declIndexDir
380+
381+ whenM (not <$> directoryExists typeIndexDir) do
382+ mkdir typeIndexDir
383+
384+
385+-- | Copy the client-side application, responsible for handling user input and rendering
386+-- | the results, to the destination path.
387+copyAppFile :: Config -> Aff Unit
388+copyAppFile { generatedDocs } = do
389+ appDir <- liftEffect getDirname
390+ let appFile = appDir <> "/docs-search-app.js"
391+ whenM (not <$> fileExists appFile) do
392+ liftEffect do
393+ logAndExit $
394+ "Client-side app was not found at " <> appFile <> ".\n" <>
395+ "Check your installation."
396+ buffer <- readFile appFile
397+ writeFile (generatedDocs <> "/html/docs-search-app.js") buffer
398+
399+
400+directoryExists :: String -> Aff Boolean
401+directoryExists path = do
402+ doesExist <- liftEffect $ exists path
403+ case doesExist of
404+ false -> pure false
405+ true -> isDirectory <$> stat path
406+
407+
408+fileExists :: String -> Aff Boolean
409+fileExists path = do
410+ doesExist <- liftEffect $ exists path
411+ case doesExist of
412+ false -> pure false
413+ true -> isFile <$> stat path
414+
415+
416+withExisting :: forall a. String -> (String -> Aff a) -> Aff (Maybe a)
417+withExisting file f = do
418+ doesExist <- fileExists file
419+
420+ if doesExist
421+ then do
422+ contents <- readTextFile UTF8 file
423+ res <- f contents
424+ pure $ Just res
425+ else do
426+ liftEffect $ do
427+ log $
428+ "File does not exist: " <> file
429+ pure Nothing
430+
431+
432+logAndExit :: forall a. String -> Effect a
433+logAndExit message = do
434+ log message
435+ Process.exit 1
436+
437+
438+showGlobs :: Array String -> String
439+showGlobs = Array.intercalate ", "
440+
441+
442+getPathsByGlobs :: Array String -> Aff (Array String)
443+getPathsByGlobs globs =
444+ liftEffect $ Array.concat <$> for globs glob
445+
446+
447+-- | Get __dirname.
448+foreign import getDirname :: Effect String
449+
450+foreign import glob :: String -> Effect (Array String)
src/Docs/Search/Interactive.pursnew file+340
@@ -0,0 +1,340 @@
1+-- | Definitions for the "search REPL".
2+module Docs.Search.Interactive where
3+
4+import Docs.Search.Declarations (Declarations, mkDeclarations)
5+import Docs.Search.DocsJson (DataDeclType(..))
6+import Docs.Search.Engine (mkEngineState, packageInfoToString, Result(..))
7+import Docs.Search.Engine as Engine
8+import Docs.Search.Extra (stringToList, (>#>))
9+import Docs.Search.IndexBuilder (parseModuleHeaders)
10+import Docs.Search.IndexBuilder as IndexBuilder
11+import Docs.Search.ModuleIndex (ModuleResult, mkPackedModuleIndex, unpackModuleIndex)
12+import Docs.Search.NodeEngine (nodeEngine)
13+import Docs.Search.PackageIndex (PackageResult, mkPackageIndex, mkPackageInfo)
14+import Docs.Search.Score (mkScores)
15+import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
16+import Docs.Search.Terminal (bold, cyan, green, yellow)
17+import Docs.Search.TypeDecoder (Constraint, FunDeps, QualifiedName, Type, TypeArgument)
18+import Docs.Search.TypeIndex (resultsWithTypes)
19+import Docs.Search.TypePrinter (keyword, showConstraint, showFunDeps, showType, showTypeArgument, space, syntax)
20+import Docs.Search.Types (ModuleName, PackageName, PackageInfo, Identifier)
21+
22+import Prelude
23+
24+import Control.Parallel (parallel, sequential)
25+import Data.Array as Array
26+import Data.Identity (Identity(..))
27+import Data.List as List
28+import Data.Maybe (fromMaybe)
29+import Data.Newtype (un, unwrap, wrap)
30+import Data.Search.Trie as Trie
31+import Data.String (length) as String
32+import Data.String.Common (split, toLower, trim) as String
33+import Data.Tuple.Nested ((/\))
34+import Effect (Effect)
35+import Effect.Aff (launchAff_)
36+import Effect.Class (liftEffect)
37+import Effect.Console (log, clear) as Console
38+import Node.ReadLine (createConsoleInterface, question)
39+import Prim hiding (Type, Constraint)
40+
41+
42+type Config =
43+ { docsFiles :: Array String
44+ , bowerFiles :: Array String
45+ , packageName :: PackageName
46+ , sourceFiles :: Array String
47+ }
48+
49+
50+run :: Config -> Effect Unit
51+run cfg = launchAff_ $ do
52+
53+ liftEffect do
54+ Console.log "Loading search index..."
55+
56+ docsJsons /\ moduleNames /\ packageMetas <- sequential $
57+ (\d h m -> d /\ h /\ m)
58+ <$> parallel (IndexBuilder.decodeDocsJsons cfg)
59+ <*> parallel (parseModuleHeaders cfg.sourceFiles)
60+ <*> parallel (IndexBuilder.decodeBowerJsons cfg)
61+
62+ let scores = mkScores packageMetas
63+ index = mkDeclarations scores docsJsons
64+ typeIndex = docsJsons >>= resultsWithTypes scores
65+ packageIndex = mkPackageIndex $ mkPackageInfo scores packageMetas
66+ moduleIndex = unpackModuleIndex $ mkPackedModuleIndex index moduleNames
67+ engineState = mkEngineState (unwrap index) typeIndex packageIndex moduleIndex scores
68+
69+ let countOfDefinitions = Trie.size $ unwrap index
70+ countOfTypeDefinitions = Array.length typeIndex
71+ countOfPackages = Array.length packageMetas
72+
73+ liftEffect do
74+ Console.log $
75+ "Loaded " <>
76+ show countOfDefinitions <>
77+ " definitions and " <>
78+ show countOfTypeDefinitions <>
79+ " type definitions from " <>
80+ show countOfPackages <> " packages."
81+
82+
83+ liftEffect do
84+ let
85+ call handler interface = do
86+ question "> " (handler interface) interface
87+
88+ inputHandler interface input = do
89+
90+ let results =
91+ Engine.query nodeEngine engineState input <#> (_.results) # un Identity
92+
93+ let total = Array.length results
94+
95+ Console.clear
96+
97+ Console.log $
98+ if total > 0 then do
99+ Array.intercalate "\n\n\n" $
100+ showResult cfg <$> Array.reverse results
101+ else
102+ "Your search for " <> bold input <> " did not yield any results."
103+
104+ call inputHandler interface
105+
106+ interface <- createConsoleInterface (mkCompleter index)
107+
108+ call inputHandler interface
109+
110+
111+mkCompleter
112+ :: Declarations
113+ -> String
114+ -> Effect { completions :: Array String
115+ , matched :: String }
116+mkCompleter index input = do
117+ let path = stringToList $ String.toLower input
118+ paths =
119+ Array.fromFoldable $
120+ (\result -> unwrap (unwrap result).name) <$>
121+ List.concat (Trie.queryValues path (unwrap index))
122+
123+ pure { completions: paths, matched: input }
124+
125+
126+showResult :: Config -> Result -> String
127+showResult cfg = case _ of
128+ DeclResult r -> showSearchResult cfg r
129+ TypeResult r -> showSearchResult cfg r
130+ PackResult r -> showPackageResult r
131+ MdlResult r -> showModuleResult r
132+
133+
134+showSearchResult :: Config -> SearchResult -> String
135+showSearchResult cfg (SearchResult result@{ name, comments, moduleName, packageInfo }) =
136+ showSignature result <> "\n" <>
137+
138+ (fromMaybe "\n" $
139+ comments <#> \comment ->
140+ "\n" <> leftShift 3 (String.trim comment) <> "\n\n") <>
141+
142+ bold (
143+ cyan (rightPad 40 $ packageInfoToString cfg.packageName packageInfo)
144+ ) <>
145+ space <>
146+ showModuleName moduleName
147+
148+
149+showPackageResult :: PackageResult -> String
150+showPackageResult { name, description } =
151+ bold (cyan "package") <> " " <> bold (yellow $ unwrap name) <>
152+
153+ (description >#> \text -> "\n\n" <> leftShift 3 text <> "\n")
154+
155+
156+showModuleResult :: ModuleResult -> String
157+showModuleResult { name, package } =
158+ bold (cyan "module") <> " " <> showModuleName name
159+
160+
161+showModuleName :: ModuleName -> String
162+showModuleName = bold <<< green <<< unwrap
163+
164+
165+showSignature
166+ :: forall rest
167+ . { name :: Identifier
168+ , moduleName :: ModuleName
169+ , packageInfo :: PackageInfo
170+ , info :: ResultInfo
171+ | rest
172+ }
173+ -> String
174+showSignature result@{ name, info } =
175+ case info of
176+ ValueResult { type: ty } ->
177+ yellow (unwrap name) <> syntax " :: " <> showType ty
178+
179+ TypeClassResult info' ->
180+ showTypeClassSignature info' result
181+
182+ TypeClassMemberResult info' ->
183+ showTypeClassMemberSignature info' result
184+
185+ DataResult info' ->
186+ showDataSignature info' result
187+
188+ TypeSynonymResult info' ->
189+ showTypeSynonymSignature info' result
190+
191+ ExternDataResult info' ->
192+ showExternDataSignature info' result
193+
194+ ValueAliasResult ->
195+ yellow ("(" <> unwrap name <> ")")
196+
197+ DataConstructorResult info' ->
198+ showDataConstructorSignature info' result
199+
200+ _ -> yellow $ unwrap name
201+
202+
203+showTypeClassSignature
204+ :: forall rest
205+ . { fundeps :: FunDeps
206+ , arguments :: Array TypeArgument
207+ , superclasses :: Array Constraint
208+ }
209+ -> { name :: Identifier, moduleName :: ModuleName | rest }
210+ -> String
211+showTypeClassSignature { fundeps, arguments, superclasses } { name, moduleName } =
212+
213+ keyword "class" <>
214+ ( if Array.null superclasses
215+ then
216+ ""
217+ else
218+ syntax " (" <> (
219+ Array.intercalate (syntax ", " ) (
220+ superclasses <#> showConstraint
221+ )
222+ ) <>
223+ syntax ") " <>
224+ syntax "<="
225+ ) <>
226+ space <>
227+ yellow (unwrap name) <>
228+ space <> (
229+ Array.intercalate space $
230+ arguments <#> showTypeArgument
231+ ) <> (
232+ showFunDeps fundeps
233+ )
234+
235+
236+showTypeClassMemberSignature
237+ :: forall rest
238+ . { "type" :: Type
239+ , typeClass :: QualifiedName
240+ , typeClassArguments :: Array TypeArgument
241+ }
242+ -> { name :: Identifier | rest }
243+ -> String
244+showTypeClassMemberSignature { "type": ty, typeClass, typeClassArguments } result =
245+ yellow (unwrap result.name) <>
246+ syntax " :: " <>
247+ showType ty
248+
249+
250+showDataSignature
251+ :: forall rest
252+ . { typeArguments :: Array TypeArgument
253+ , dataDeclType :: DataDeclType }
254+ -> { name :: Identifier | rest }
255+ -> String
256+showDataSignature { typeArguments, dataDeclType } { name } =
257+ ( keyword
258+ case dataDeclType of
259+ NewtypeDataDecl -> "newtype"
260+ DataDataDecl -> "data"
261+ ) <>
262+ space <>
263+ yellow (unwrap name) <>
264+ space <> (
265+ Array.intercalate space $
266+ typeArguments <#> showTypeArgument
267+ )
268+
269+
270+showTypeSynonymSignature
271+ :: forall rest
272+ . { type :: Type
273+ , arguments :: Array TypeArgument
274+ }
275+ -> { name :: Identifier | rest }
276+ -> String
277+showTypeSynonymSignature { type: ty, arguments } { name } =
278+ keyword "type" <>
279+ space <>
280+ yellow (unwrap name) <>
281+ space <> (
282+ Array.intercalate space $
283+ arguments <#> showTypeArgument
284+ ) <>
285+ space <>
286+ syntax "=" <>
287+ space <>
288+ showType ty
289+
290+
291+showExternDataSignature
292+ :: forall rest
293+ . { kind :: Type }
294+ -> { name :: Identifier | rest }
295+ -> String
296+showExternDataSignature { kind } { name } =
297+ keyword "foreign data" <>
298+ space <>
299+ yellow (unwrap name) <>
300+ syntax " :: " <>
301+ showType kind
302+
303+
304+showDataConstructorSignature
305+ :: forall rest
306+ . { dataDeclType :: DataDeclType
307+ , "type" :: Type
308+ }
309+ -> { name :: Identifier
310+ | rest
311+ }
312+ -> String
313+showDataConstructorSignature { dataDeclType, type: ctorType } { name } =
314+ ( keyword
315+ case dataDeclType of
316+ NewtypeDataDecl -> "newtype constructor"
317+ DataDataDecl -> "data constructor"
318+ ) <>
319+ space <>
320+ yellow (unwrap name) <>
321+ space <>
322+ syntax "::" <>
323+ space <>
324+ showType ctorType
325+
326+
327+leftShift :: Int -> String -> String
328+leftShift shift str =
329+ Array.intercalate "\n" $
330+ leftPad shift <$>
331+ String.trim <$>
332+ String.split (wrap "\n") str
333+
334+
335+leftPad :: Int -> String -> String
336+leftPad w str = Array.fold (Array.replicate w " ") <> str
337+
338+
339+rightPad :: Int -> String -> String
340+rightPad w str = str <> Array.fold (Array.replicate (w - String.length str) " ")
src/Docs/Search/Loader.jsnew file+26
@@ -0,0 +1,26 @@
1+/* global exports */
2+
3+export function loadFromScript (globalIdentifier) {
4+ return function (url) {
5+ return function () {
6+ return new Promise(function (resolve, reject) {
7+ if (typeof window[globalIdentifier] === 'undefined') {
8+ var script = document.createElement('script');
9+ script.type = 'text/javascript';
10+ script.src = url;
11+ script.addEventListener('load', function () {
12+ if (typeof window[globalIdentifier] === 'undefined') {
13+ reject(new Error("Couldn't load package index."));
14+ } else {
15+ resolve(window[globalIdentifier]);
16+ }
17+ });
18+ script.addEventListener('error', reject);
19+ document.body.appendChild(script);
20+ } else {
21+ resolve(window[globalIdentifier]);
22+ }
23+ });
24+ };
25+ };
26+};
src/Docs/Search/Loader.pursnew file+38
@@ -0,0 +1,38 @@
1+module Docs.Search.Loader where
2+
3+import Docs.Search.Types (GlobalIdentifier, URL)
4+
5+import Prelude
6+
7+import Control.Monad.Error.Class (throwError)
8+import Control.Promise (Promise, toAffE)
9+import Data.Argonaut.Core (Json)
10+import Data.Argonaut.Decode.Error (printJsonDecodeError)
11+import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
12+import Data.Either (either)
13+import Data.Newtype (unwrap)
14+import Effect (Effect)
15+import Effect.Aff (Aff)
16+import Effect.Exception (error)
17+
18+
19+
20+load
21+ :: forall a
22+ . DecodeJson a
23+ => GlobalIdentifier
24+ -> URL
25+ -> Aff a
26+load globalIdentifier url = do
27+ json <- toAffE (loadFromScript globalIdentifier url)
28+ either throw pure $ decodeJson json
29+ where
30+ throw err = throwError $ error $
31+ "Couldn't load content from window." <>
32+ unwrap globalIdentifier <> ": " <> printJsonDecodeError err
33+
34+
35+foreign import loadFromScript
36+ :: GlobalIdentifier
37+ -> URL
38+ -> Effect (Promise Json)
src/Docs/Search/Main.pursnew file+163
@@ -0,0 +1,163 @@
1+-- | The main module of the CLI interface app.
2+module Docs.Search.Main where
3+
4+import Docs.Search.Config as Config
5+import Docs.Search.IndexBuilder as IndexBuilder
6+import Docs.Search.Interactive as Interactive
7+import Docs.Search.Types (PackageName(..))
8+
9+import Prelude
10+
11+import Data.Generic.Rep (class Generic)
12+import Data.List as List
13+import Data.List.NonEmpty as NonEmpty
14+import Data.Maybe (Maybe, fromMaybe, optional)
15+import Data.Newtype (unwrap)
16+import Data.Show.Generic (genericShow)
17+import Data.Unfoldable (class Unfoldable)
18+import Effect (Effect)
19+import Effect.Console (log)
20+import Options.Applicative (Parser, command, execParser, flag, fullDesc, help, helper, info, long, metavar, progDesc, strOption, subparser, value, (<**>))
21+import Options.Applicative as CA
22+
23+
24+main :: Effect Unit
25+main = do
26+
27+ args <- getArgs
28+ let defaultCommands = Search { docsFiles: defaultDocsFiles
29+ , bowerFiles: defaultBowerFiles
30+ , packageName: Config.defaultPackageName
31+ , sourceFiles: defaultSourceFiles
32+ }
33+
34+ case fromMaybe defaultCommands args of
35+ BuildIndex cfg -> IndexBuilder.run cfg
36+ Search cfg -> Interactive.run cfg
37+ Version -> log Config.version
38+
39+getArgs :: Effect (Maybe Commands)
40+getArgs = execParser opts
41+ where
42+ opts =
43+ info (commands <**> helper)
44+ ( fullDesc
45+ <> progDesc "Search frontend for the documentation generated by the PureScript compiler."
46+ )
47+
48+data Commands
49+ = BuildIndex IndexBuilder.Config
50+ | Search Interactive.Config
51+ | Version
52+
53+derive instance genericCommands :: Generic Commands _
54+
55+instance showCommands :: Show Commands where
56+ show = genericShow
57+
58+commands :: Parser (Maybe Commands)
59+commands = optional $ subparser
60+ ( command "build-index"
61+ ( info (buildIndex <**> helper)
62+ ( progDesc "Build the index used to search for definitions and patch the generated docs so that they include a search field."
63+ )
64+ )
65+ <> command "search"
66+ ( info (startInteractive <**> helper)
67+ ( progDesc "Run the search engine."
68+ )
69+ )
70+ <> command "version"
71+ ( info (pure Version <**> helper)
72+ ( progDesc "Show purescript-docs-search version."
73+ )
74+ )
75+ )
76+
77+
78+buildIndex :: Parser Commands
79+buildIndex = ado
80+ docsFiles <- docsFilesOption
81+ bowerFiles <- bowerFilesOption
82+ packageName <- packageNameOption
83+ sourceFiles <- sourceFilesOption
84+ generatedDocs <- strOption
85+ ( long "generated-docs"
86+ <> metavar "DIR"
87+ <> value "./generated-docs/"
88+ <> help "Path to the generated documentation HTML that will be patched. Search app will be injected into each HTML document."
89+ )
90+ noPatch <- flag false true
91+ ( long "no-patch"
92+ <> help "Do not patch the HTML docs, only build indices"
93+ )
94+ in BuildIndex { docsFiles, bowerFiles, generatedDocs, noPatch, packageName, sourceFiles }
95+
96+
97+startInteractive :: Parser Commands
98+startInteractive = ado
99+ docsFiles <- docsFilesOption
100+ bowerFiles <- bowerFilesOption
101+ packageName <- packageNameOption
102+ sourceFiles <- sourceFilesOption
103+ in Search { docsFiles, bowerFiles, packageName, sourceFiles }
104+
105+docsFilesOption :: Parser (Array String)
106+docsFilesOption = fromMaybe defaultDocsFiles <$>
107+ optional
108+ ( some
109+ ( strOption
110+ ( long "docs-files"
111+ <> metavar "GLOB"
112+ <> help "Glob that captures `docs.json` files that should be used to build the index"
113+ )
114+ )
115+ )
116+
117+bowerFilesOption :: Parser (Array String)
118+bowerFilesOption = fromMaybe defaultBowerFiles <$>
119+ optional
120+ ( some
121+ ( strOption
122+ ( long "bower-jsons"
123+ <> metavar "GLOB"
124+ <> help "Glob that captures `bower.json` files. These files are used to build dependency trees to compute package popularity scores based on how many dependants a package has."
125+ )
126+ )
127+ )
128+
129+packageNameOption :: Parser PackageName
130+packageNameOption =
131+ PackageName <$> strOption
132+ ( long "package-name"
133+ <> metavar "PACKAGE"
134+ <> value (unwrap Config.defaultPackageName)
135+ <> help "Local package name as it should appear in the search results"
136+ )
137+
138+sourceFilesOption :: Parser (Array String)
139+sourceFilesOption = fromMaybe defaultSourceFiles <$>
140+ optional
141+ ( some
142+ ( strOption
143+ ( long "source-files"
144+ <> metavar "GLOB"
145+ <> help "Path to project source files, used for more precise module indexing (see #62). Default: src/**/*.purs"
146+ )
147+ )
148+ )
149+
150+defaultDocsFiles :: Array String
151+defaultDocsFiles = [ "output/**/docs.json" ]
152+
153+defaultBowerFiles :: Array String
154+defaultBowerFiles = [ ".spago/*/*/bower.json", "bower_components/purescript-*/bower.json" ]
155+
156+defaultSourceFiles :: Array String
157+defaultSourceFiles = [ "src/**/*.purs" ]
158+
159+many :: forall a f. Unfoldable f => Parser a -> Parser (f a)
160+many x = CA.many x <#> List.toUnfoldable
161+
162+some :: forall a f. Unfoldable f => Parser a -> Parser (f a)
163+some x = CA.some x <#> NonEmpty.toUnfoldable
src/Docs/Search/Meta.pursnew file+21
@@ -0,0 +1,21 @@
1+module Docs.Search.Meta where
2+
3+import Docs.Search.Config as Config
4+import Docs.Search.Loader as Loader
5+import Docs.Search.Types (PackageName)
6+
7+import Prelude
8+
9+import Effect.Aff (Aff, catchError)
10+
11+
12+type Meta =
13+ { localPackageName :: PackageName
14+ }
15+
16+
17+load :: Aff Meta
18+load =
19+ Loader.load Config.metaItem Config.metaLoadPath
20+ `catchError` const (pure defaultMeta)
21+ where defaultMeta = { localPackageName: Config.defaultPackageName }
src/Docs/Search/ModuleIndex.jsnew file+24
@@ -0,0 +1,24 @@
1+/* global exports */
2+
3+export function load (url) {
4+ return function () {
5+ return new Promise(function (resolve, reject) {
6+ if (typeof window.DocsSearchModuleIndex === 'undefined') {
7+ var script = document.createElement('script');
8+ script.type = 'text/javascript';
9+ script.src = url;
10+ script.addEventListener('load', function () {
11+ if (typeof window.DocsSearchModuleIndex === 'undefined') {
12+ reject(new Error("Couldn't load module index"));
13+ } else {
14+ resolve(window.DocsSearchModuleIndex);
15+ }
16+ });
17+ script.addEventListener('error', reject);
18+ document.body.appendChild(script);
19+ } else {
20+ resolve(window.DocsSearchModuleIndex);
21+ }
22+ });
23+ };
24+};
src/Docs/Search/ModuleIndex.pursnew file+126
@@ -0,0 +1,126 @@
1+module Docs.Search.ModuleIndex where
2+
3+import Docs.Search.Config as Config
4+import Docs.Search.Declarations (Declarations(..))
5+import Docs.Search.Extra (stringToList)
6+import Docs.Search.Score (Scores, getPackageScore)
7+import Docs.Search.SearchResult (SearchResult(..))
8+import Docs.Search.Types (ModuleName, PackageInfo(..), PackageScore)
9+
10+import Prelude
11+
12+import Control.Monad.State (execState, modify_)
13+import Control.Promise (Promise, toAffE)
14+import Data.Argonaut.Core (Json)
15+import Data.Argonaut.Decode (decodeJson)
16+import Data.Array as Array
17+import Data.Either (hush)
18+import Data.Foldable (foldl)
19+import Data.Lens ((%~))
20+import Data.Lens.Record (prop)
21+import Data.List (List, (:))
22+import Data.Map (Map)
23+import Data.Map as Map
24+import Data.Maybe (Maybe(..), fromMaybe)
25+import Data.Newtype (unwrap)
26+import Data.Search.Trie (Trie)
27+import Data.Search.Trie as Trie
28+import Data.Set (Set)
29+import Data.Set as Set
30+import Data.String.CodeUnits (toCharArray) as String
31+import Data.String.Common (split, toLower) as String
32+import Data.String.Pattern (Pattern(..))
33+import Data.Traversable (foldr, for_)
34+import Data.Tuple.Nested ((/\))
35+import Effect (Effect)
36+import Effect.Aff (Aff)
37+import Type.Proxy (Proxy(..))
38+
39+-- | Module index that is actually stored in a JS file.
40+type PackedModuleIndex = Map PackageInfo (Set ModuleName)
41+
42+-- | "Expanded" module index that can be queried quickly.
43+type ModuleIndex = { packageModules :: Map PackageInfo (Set ModuleName)
44+ , modulePackages :: Map ModuleName PackageInfo
45+ , index :: Trie Char ModuleName
46+ }
47+
48+
49+type ModuleResult
50+ = { name :: ModuleName
51+ , package :: PackageInfo
52+ , score :: PackageScore
53+ }
54+
55+
56+unpackModuleIndex :: PackedModuleIndex -> ModuleIndex
57+unpackModuleIndex packageModules =
58+ flip execState { packageModules, modulePackages: Map.empty, index: mempty } do
59+ for_ (Map.toUnfoldableUnordered packageModules :: Array _)
60+ \(package /\ moduleNames) -> do
61+ for_ moduleNames \moduleName -> do
62+ modify_ $ _modulePackages %~ Map.insert moduleName package
63+ for_ (extractModuleNameParts moduleName) \part -> do
64+ let partPath = Array.toUnfoldable $ String.toCharArray part
65+ modify_ $ _index %~ Trie.insert partPath moduleName
66+
67+
68+-- | E.g. `"Data.Array.ST" -> ["data.array.st", "array.st", "st"]`.
69+extractModuleNameParts :: ModuleName -> List String
70+extractModuleNameParts =
71+ unwrap >>> String.toLower >>>
72+ String.split (Pattern ".") >>>
73+ foldl (\acc el -> el : map (_ <> "." <> el) acc) mempty
74+
75+
76+queryModuleIndex
77+ :: Scores
78+ -> ModuleIndex
79+ -> String
80+ -> Array ModuleResult
81+queryModuleIndex scores { index, modulePackages } query =
82+ let path = stringToList $ String.toLower query in
83+ Trie.queryValues path index #
84+ Array.fromFoldable #
85+ Array.nub <#>
86+ (\name -> do
87+ package <- Map.lookup name modulePackages
88+ pure { name, package
89+ , score: getPackageScore scores package }) #
90+ Array.catMaybes
91+
92+
93+-- | Constructs a mapping from packages to modules
94+mkPackedModuleIndex :: Declarations -> Array ModuleName -> PackedModuleIndex
95+mkPackedModuleIndex (Declarations trie) moduleNames =
96+ addLocalPackageModuleNames $
97+ foldr (Map.unionWith Set.union) Map.empty $ extract <$> Trie.values trie
98+ where
99+ -- Add modules from src/ that may not contain any definitions, only
100+ -- re-exports
101+ addLocalPackageModuleNames = flip Map.alter LocalPackage $
102+ Just <<< append (Set.fromFoldable moduleNames) <<< fromMaybe Set.empty
103+ extract
104+ :: List SearchResult
105+ -> Map PackageInfo (Set ModuleName)
106+ extract = foldr (Map.unionWith Set.union) Map.empty <<< map mkEntry
107+ where
108+ mkEntry (SearchResult { packageInfo, moduleName }) =
109+ Map.singleton packageInfo (Set.singleton moduleName)
110+
111+loadModuleIndex :: Aff PackedModuleIndex
112+loadModuleIndex = do
113+ json <- toAffE $ load Config.moduleIndexLoadPath
114+ pure $ fromMaybe Map.empty $ hush $ decodeJson json
115+
116+
117+foreign import load
118+ :: String
119+ -> Effect (Promise Json)
120+
121+
122+_modulePackages :: forall a b rest. (a -> b) -> { modulePackages :: a | rest } -> { modulePackages :: b | rest }
123+_modulePackages = prop (Proxy :: Proxy "modulePackages")
124+
125+_index :: forall a b rest. (a -> b) -> { index :: a | rest } -> { index :: b | rest }
126+_index = prop (Proxy :: Proxy "index")
src/Docs/Search/ModuleParser.pursnew file+50
@@ -0,0 +1,50 @@
1+module Docs.Search.ModuleParser where
2+
3+import Prelude
4+
5+import Control.Alt ((<|>))
6+import Data.Either (hush)
7+import Data.Foldable (intercalate)
8+import Data.Maybe (Maybe)
9+import Data.Newtype (wrap)
10+import Docs.Search.Types (ModuleName)
11+import Effect (Effect)
12+import StringParser (Parser, char, choice, fix, many, manyTill, noneOf, regex, runParser, sepBy, sepBy1, string, try, whiteSpace)
13+
14+parseModuleName :: String -> Maybe ModuleName
15+parseModuleName = map wrap <<< hush <<< runParser do
16+ void $ many whiteSpaceOrComment
17+ moduleHeader
18+
19+whiteSpaceOrComment :: Parser Unit
20+whiteSpaceOrComment =
21+ choice
22+ [ try $ multiLineComment
23+ , try $ singleLineComment
24+ , try $ void whiteSpace
25+ ]
26+
27+multiLineComment :: Parser Unit
28+multiLineComment = do
29+ void $ string "{-"
30+ void $ manyTill (void $ noneOf []) (string "-}")
31+
32+singleLineComment :: Parser Unit
33+singleLineComment = do
34+ void $ string "--"
35+ void $ manyTill (void $ noneOf ['\n']) (char '\n')
36+
37+moduleHeader :: Parser String
38+moduleHeader = do
39+ void $ string "module"
40+ void $ many whiteSpaceOrComment
41+ moduleName
42+
43+moduleName :: Parser String
44+moduleName = sepBy1 moduleNameWord (string ".") <#> intercalate "."
45+
46+moduleNameWord :: Parser String
47+moduleNameWord = do
48+ first <- regex "[A-Z]"
49+ rest <- regex "[A-Za-z0-9]*"
50+ pure $ first <> rest
src/Docs/Search/NodeEngine.pursnew file+48
@@ -0,0 +1,48 @@
1+-- | A search engine that can be used in nodejs invironment.
2+module Docs.Search.NodeEngine where
3+
4+import Docs.Search.Extra (stringToList)
5+import Docs.Search.PackageIndex as PackageIndex
6+import Docs.Search.ModuleIndex as ModuleIndex
7+import Docs.Search.Engine (Engine, Query, Index, sortByDistance)
8+import Docs.Search.SearchResult (SearchResult)
9+import Docs.Search.TypeQuery (TypeQuery)
10+
11+import Prelude
12+
13+import Data.Array as Array
14+import Data.Identity (Identity)
15+import Data.List as List
16+import Data.Search.Trie as Trie
17+import Data.String.Common (toLower)
18+
19+
20+type NodeEngine = Engine Identity Index (Array SearchResult)
21+
22+
23+nodeEngine :: NodeEngine
24+nodeEngine =
25+ { queryIndex
26+ , queryTypeIndex
27+ , queryPackageIndex: PackageIndex.queryPackageIndex
28+ , queryModuleIndex: ModuleIndex.queryModuleIndex
29+ }
30+
31+
32+queryIndex :: Query Identity Index String SearchResult
33+queryIndex index input =
34+ pure { index, results:
35+ Array.fromFoldable $
36+ List.concat $
37+ Trie.queryValues
38+ (stringToList $ toLower input)
39+ index
40+ }
41+
42+
43+queryTypeIndex
44+ :: Query Identity (Array SearchResult) TypeQuery SearchResult
45+queryTypeIndex index typeQuery =
46+ pure { index
47+ , results: Array.take 100 $ sortByDistance typeQuery index
48+ }
src/Docs/Search/PackageIndex.pursnew file+94
@@ -0,0 +1,94 @@
1+module Docs.Search.PackageIndex where
2+
3+import Docs.Search.Config as Config
4+import Docs.Search.Extra (stringToList)
5+import Docs.Search.Score (Scores, getPackageScoreForPackageName, normalizePackageName)
6+import Docs.Search.Types (PackageName, RawPackageName(..), PackageScore)
7+import Docs.Search.Loader as Loader
8+
9+import Prelude
10+
11+import Data.Array as Array
12+import Data.Map (Map)
13+import Data.Map as Map
14+import Data.Maybe (Maybe)
15+import Data.Newtype (unwrap)
16+import Data.Search.Trie (Trie)
17+import Data.Search.Trie as Trie
18+import Effect.Aff (Aff)
19+import Web.Bower.PackageMeta (PackageMeta(..))
20+
21+
22+type PackageResult
23+ = { name :: PackageName
24+ , description :: Maybe String
25+ , score :: PackageScore
26+ , dependencies :: Array PackageName
27+ , repository :: Maybe String
28+ }
29+
30+type PackageIndex = Trie Char PackageResult
31+
32+type PackageInfo = Array PackageResult
33+
34+
35+mkPackageInfo :: Scores -> Array PackageMeta -> PackageInfo
36+mkPackageInfo packageScores pms =
37+ Array.fromFoldable $
38+ Map.values $
39+ Array.foldr insert Map.empty pms
40+
41+ where
42+ insert
43+ :: PackageMeta
44+ -> Map PackageName PackageResult
45+ -> Map PackageName PackageResult
46+ insert
47+ (PackageMeta { name
48+ , description
49+ , dependencies
50+ , devDependencies
51+ , repository }) =
52+ Map.insert
53+ packageName
54+ { name: packageName
55+ , description: description
56+ , score: getPackageScoreForPackageName packageScores packageName
57+ , dependencies:
58+ unwrap dependencies <#>
59+ _.packageName >>> RawPackageName >>> normalizePackageName
60+ , repository: repository <#> (_.url)
61+ }
62+
63+ where packageName = normalizePackageName $ RawPackageName name
64+
65+mkScoresFromPackageIndex :: PackageIndex -> Scores
66+mkScoresFromPackageIndex =
67+ Trie.values >>> Array.fromFoldable >>>
68+ Array.foldr (\ { name, score } -> Map.insert name score) Map.empty
69+
70+
71+loadPackageIndex :: Aff PackageIndex
72+loadPackageIndex =
73+ mkPackageIndex <$> Loader.load Config.packageInfoItem Config.packageInfoLoadPath
74+
75+
76+mkPackageIndex :: PackageInfo -> PackageIndex
77+mkPackageIndex =
78+ Array.foldr
79+ (\package -> Trie.insert (stringToList $ unwrap package.name) package)
80+ mempty
81+
82+
83+queryPackageIndex
84+ :: forall m
85+ . Monad m
86+ => PackageIndex
87+ -> String
88+ -> m { index :: PackageIndex
89+ , results :: Array PackageResult
90+ }
91+queryPackageIndex index query =
92+ pure { index
93+ , results: Array.fromFoldable $ Trie.queryValues (stringToList query) index
94+ }
src/Docs/Search/Score.pursnew file+52
@@ -0,0 +1,52 @@
1+module Docs.Search.Score where
2+
3+import Docs.Search.Types (RawPackageName(..), PackageName(..), PackageInfo(..), PackageScore(..))
4+
5+import Prelude
6+
7+import Data.Array as Array
8+import Data.Map (Map)
9+import Data.Map as Map
10+import Data.Maybe (fromMaybe)
11+import Data.Newtype (unwrap, wrap)
12+import Data.String.CodeUnits as String
13+import Web.Bower.PackageMeta (Dependencies, PackageMeta)
14+
15+
16+type Scores = Map PackageName PackageScore
17+
18+normalizePackageName :: RawPackageName -> PackageName
19+normalizePackageName (RawPackageName p) =
20+ fromMaybe (PackageName p) $ map wrap $ String.stripPrefix (wrap "purescript-") p
21+
22+
23+-- | Construct a mapping from package names to their scores, based on number
24+-- of reverse dependencies.
25+mkScores :: Array PackageMeta -> Scores
26+mkScores =
27+ Array.foldr
28+ (\pm ->
29+ updateScoresFor (unwrap pm).dependencies >>>
30+ updateScoresFor (unwrap pm).devDependencies
31+ )
32+ Map.empty
33+
34+ where
35+ updateScoresFor :: Dependencies -> Scores -> Scores
36+ updateScoresFor deps scores =
37+ Array.foldr
38+ (\dep -> Map.insertWith add dep one)
39+ scores
40+ (deps # unwrap >>> map (_.packageName >>> RawPackageName >>> normalizePackageName))
41+
42+
43+getPackageScore :: Scores -> PackageInfo -> PackageScore
44+getPackageScore scores = case _ of
45+ Package p -> getPackageScoreForPackageName scores p
46+ Builtin -> PackageScore 100000
47+ LocalPackage -> PackageScore 200000
48+ UnknownPackage -> zero
49+
50+
51+getPackageScoreForPackageName :: Scores -> PackageName -> PackageScore
52+getPackageScoreForPackageName scores p = fromMaybe zero $ Map.lookup p scores
src/Docs/Search/SearchResult.pursnew file+85
@@ -0,0 +1,85 @@
1+module Docs.Search.SearchResult where
2+
3+import Docs.Search.DocsJson (DataDeclType)
4+import Docs.Search.TypeDecoder (Constraint, FunDeps, QualifiedName, Type, TypeArgument)
5+import Docs.Search.Types (ModuleName, PackageInfo, Identifier, PackageScore)
6+
7+import Prelude
8+import Prim hiding (Type, Constraint)
9+import Data.Argonaut.Decode (class DecodeJson)
10+import Data.Argonaut.Decode.Generic (genericDecodeJson)
11+import Data.Argonaut.Encode (class EncodeJson)
12+import Data.Argonaut.Encode.Generic (genericEncodeJson)
13+import Data.Generic.Rep (class Generic)
14+import Data.Maybe (Maybe(..))
15+import Data.Newtype (class Newtype, un)
16+
17+
18+-- | Metadata that makes sense only for certain types of search results.
19+data ResultInfo
20+ = DataResult { typeArguments :: Array TypeArgument
21+ , dataDeclType :: DataDeclType }
22+ | ExternDataResult { kind :: Type }
23+ | TypeSynonymResult { arguments :: Array TypeArgument
24+ , type :: Type }
25+ | DataConstructorResult { dataDeclType :: DataDeclType
26+ , type :: Type }
27+ | TypeClassMemberResult { type :: Type
28+ , typeClass :: QualifiedName
29+ , typeClassArguments :: Array TypeArgument }
30+ | TypeClassResult { fundeps :: FunDeps
31+ , arguments :: Array TypeArgument
32+ , superclasses :: Array Constraint }
33+ | ValueResult { type :: Type }
34+ | ValueAliasResult
35+ | TypeAliasResult
36+ | ExternKindResult
37+
38+derive instance genericResultInfo :: Generic ResultInfo _
39+
40+instance encodeJsonResultInfo :: EncodeJson ResultInfo where
41+ encodeJson = genericEncodeJson
42+
43+instance decodeJsonResultInfo :: DecodeJson ResultInfo where
44+ decodeJson = genericDecodeJson
45+
46+
47+-- | Extract the type field.
48+typeOf :: ResultInfo -> Maybe Type
49+typeOf (TypeSynonymResult { type: res }) =
50+ Just res
51+typeOf (TypeClassMemberResult { type: res }) =
52+ Just res
53+typeOf (ValueResult { type: res }) =
54+ Just res
55+typeOf _ = Nothing
56+
57+
58+-- | Common metadata for all types of search results.
59+newtype SearchResult
60+ = SearchResult
61+ { name :: Identifier
62+ , comments :: Maybe String
63+ , hashAnchor :: String
64+ , moduleName :: ModuleName
65+ , packageInfo :: PackageInfo
66+ , score :: PackageScore
67+ , sourceSpan :: Maybe { start :: Array Int
68+ , end :: Array Int
69+ , name :: String
70+ }
71+ , info :: ResultInfo
72+ }
73+
74+derive instance genericSearchResult :: Generic SearchResult _
75+derive instance newtypeSearchResult :: Newtype SearchResult _
76+
77+instance encodeJsonSearchResult :: EncodeJson SearchResult where
78+ encodeJson = genericEncodeJson
79+
80+instance decodeJsonSearchResult :: DecodeJson SearchResult where
81+ decodeJson = genericDecodeJson
82+
83+
84+typeOfResult :: SearchResult -> Maybe Type
85+typeOfResult = un SearchResult >>> (_.info) >>> typeOf
src/Docs/Search/Terminal.pursnew file+23
@@ -0,0 +1,23 @@
1+module Docs.Search.Terminal where
2+
3+import Prelude
4+
5+bold :: String -> String
6+bold str =
7+ "\x1b[1m" <> str <> "\x1b[0m"
8+
9+yellow :: String -> String
10+yellow str =
11+ "\x1b[33m" <> str <> "\x1b[0m"
12+
13+grey :: String -> String
14+grey str =
15+ "\x1b[37m" <> str <> "\x1b[0m"
16+
17+cyan :: String -> String
18+cyan str =
19+ "\x1b[36m" <> str <> "\x1b[0m"
20+
21+green :: String -> String
22+green str =
23+ "\x1b[32m" <> str <> "\x1b[0m"
src/Spago/Search/TypeDecoder.purs → src/Docs/Search/TypeDecoder.pursrenamed+150−89
@@ -1,16 +1,21 @@
1−module Spago.Search.TypeDecoder where
1+-- | A decoder for types located in 'Language.PureScript.Types'.
2+module Docs.Search.TypeDecoder where
23
4+import Docs.Search.Types (Identifier)
35
46import Prelude
5−
7+import Prim hiding (Type, Constraint)
8+import Control.Alt ((<|>))
69import Data.Argonaut.Core (Json, caseJsonObject, fromArray, fromObject, jsonEmptyObject, stringify, toArray)
710import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:))
811import Data.Argonaut.Encode (class EncodeJson, encodeJson)
9−import Data.Array ((!!))
12+import Data.Argonaut.Decode.Error (JsonDecodeError(..))
1013import Data.Either (Either(..))
1114import Data.Generic.Rep (class Generic)
12−import Data.Generic.Rep.Show (genericShow)
13−import Data.Maybe (Maybe(..), fromMaybe)
15+import Data.Show.Generic (genericShow)
16+import Data.List (List(..), (:))
17+import Data.List as List
18+import Data.Maybe (Maybe(..))
1419import Data.Newtype (class Newtype)
1520import Data.Tuple (Tuple(..))
1621import Foreign.Object as Object
@@ -22,71 +27,29 @@ derive instance newtypeQualifiedName :: Newtype QualifiedName _
2227instance showQualifiedName :: Show QualifiedName where
2328 show = genericShow
2429
25−newtype QualifiedName = QualifiedName { moduleName :: Array String
26− , name :: String
27− }
30+newtype QualifiedName
31+ = QualifiedName { moduleNameParts :: Array String
32+ , name :: Identifier
33+ }
2834
2935instance decodeJsonQualifiedName :: DecodeJson QualifiedName where
3036 decodeJson json = do
3137 decodeTuple
32− (\moduleName name -> QualifiedName { moduleName, name })
38+ (\moduleNameParts name -> QualifiedName { moduleNameParts, name })
3339 (mkJsonError "QualifiedName" json)
3440 json
3541
3642instance encodeJsonQualifiedName :: EncodeJson QualifiedName where
37− encodeJson (QualifiedName { moduleName, name }) =
38− encodeTuple moduleName name
43+ encodeJson (QualifiedName { moduleNameParts, name }) =
44+ encodeTuple moduleNameParts name
3945
40−mkJsonError :: String -> Json -> (forall i. i -> String)
46+mkJsonError :: String -> Json -> (forall i. i -> JsonDecodeError)
4147mkJsonError name json _ =
42− "Couldn't parse " <> name <> " from " <> stringify json
48+ TypeMismatch $ "Couldn't parse " <> name <> " from " <> stringify json
4349
44−mkJsonError' :: String -> Json -> String
50+mkJsonError' :: String -> Json -> JsonDecodeError
4551mkJsonError' name json = mkJsonError name json unit
4652
47−-- | The data type of kinds
48−data Kind
49− -- | Kinds for labelled, unordered rows without duplicates
50− = Row Kind
51− -- | Function kinds
52− | FunKind Kind Kind
53− -- | A named kind
54− | NamedKind QualifiedName
55−
56−derive instance eqKind :: Eq Kind
57−derive instance genericKind :: Generic Kind _
58−
59−instance showKind :: Show Kind where
60− show x = genericShow x
61−
62−instance decodeJsonKind :: DecodeJson Kind where
63− decodeJson json = do
64− handle <- decodeJson json
65− tag <- handle .: "tag"
66− case tag of
67− "NamedKind" -> do
68− contents <- handle .: "contents"
69− pure $ NamedKind contents
70− "Row" -> do
71− contents <- handle .: "contents"
72− pure $ Row contents
73− "FunKind" -> do
74− contents <- handle .: "contents"
75− case contents of
76− [k1, k2] ->
77− Right $ FunKind k1 k2
78− _ -> Left $ mkJsonError' "FunKind" json
79− _ -> Left $ mkJsonError' "Kind" json
80−
81−instance encodeJsonKind :: EncodeJson Kind where
82− encodeJson = case _ of
83− Row k ->
84− encodeTaggedContents "Row" (encodeJson k)
85− FunKind k1 k2 ->
86− encodeTaggedContents "FunKind" (encodeTuple k1 k2)
87− NamedKind qname ->
88− encodeTaggedContents "NamedKind" (encodeJson qname)
89−
9053-- | A typeclass constraint
9154newtype Constraint = Constraint
9255 { constraintClass :: QualifiedName
@@ -135,8 +98,10 @@ data Type
13598 | TypeOp QualifiedName
13699 -- | A type application
137100 | TypeApp Type Type
101+ -- | Explicit kind application
102+ | KindApp Type Type
138103 -- | Forall quantifier
139− | ForAll String Type (Maybe Kind)
104+ | ForAll String (Maybe Type) Type
140105 -- | A type withset of type class constraints
141106 | ConstrainedType Constraint Type
142107 {-
@@ -146,11 +111,9 @@ data Type
146111 -- | An empty row
147112 | REmpty
148113 -- | A non-empty row
149− | RCons String Type Type
150− {-
114+ | RCons Identifier Type Type
151115 -- | A type with a kind annotation
152− | Kinded Type Kind
153− -}
116+ | Kinded Type Type
154117 -- | Binary operator application. During the rebracketing phase of desugaring,
155118 -- this data constructor will be removed.
156119 | BinaryNoParensType Type Type Type
@@ -176,8 +139,25 @@ instance decodeJsonType :: DecodeJson Type where
176139 "TypeApp" ->
177140 decodeContents (decodeTuple TypeApp (const err)) (Left err) json
178141 where err = mkJsonError' "TypeApp" json
142+ "KindApp" ->
143+ decodeContents (decodeTuple KindApp (const err)) (Left err) json
144+ where err = mkJsonError' "KindApp" json
179145 "ForAll" ->
180− decodeContents (decodeTriple ForAll err) (Left $ err unit) json
146+ decodeContents
147+ (decodeTriple
148+ (\(v :: String) (t :: Type) (_ :: Maybe Int) ->
149+ ForAll v Nothing t) err)
150+ (Left $ err unit)
151+ json
152+ <|>
153+ -- Ignore SkolemScope
154+ decodeContents
155+ (decodeQuadriple
156+ (\f (k :: Type) a (_ :: Maybe Int) ->
157+ ForAll f (Just k) a)
158+ err)
159+ (Left $ err unit)
160+ json
181161 where err = mkJsonError "ForAll" json
182162 "ConstrainedType" ->
183163 decodeContents (decodeTuple ConstrainedType err) (Left $ err unit) json
@@ -186,6 +166,9 @@ instance decodeJsonType :: DecodeJson Type where
186166 "RCons" ->
187167 decodeContents (decodeTriple RCons (const err)) (Left err) json
188168 where err = mkJsonError' "RCons" json
169+ "KindedType" ->
170+ decodeContents (decodeTuple Kinded (const err)) (Left err) json
171+ where err = mkJsonError' "KindedType" json
189172 "BinaryNoParensType" ->
190173 decodeContents (decodeTriple BinaryNoParensType (const err)) (Left err) json
191174 where err = mkJsonError' "BinaryNoParens" json
@@ -198,19 +181,25 @@ instance decodeJsonType :: DecodeJson Type where
198181
199182instance encodeJsonType :: EncodeJson Type where
200183 encodeJson = case _ of
201− TypeVar val -> encodeTaggedContents "TypeVar" (encodeJson val)
202− TypeLevelString val -> encodeTaggedContents "TypeLevelString" (encodeJson val)
203− TypeConstructor val -> encodeTaggedContents "TypeConstructor" (encodeJson val)
204− TypeOp val -> encodeTaggedContents "TypeOp" (encodeJson val)
205− TypeApp t1 t2 -> encodeTaggedContents "TypeApp" (encodeTuple t1 t2)
206− ForAll str ty mbk -> encodeTaggedContents "ForAll" (encodeTriple str ty mbk)
207− ConstrainedType c t -> encodeTaggedContents "ConstrainedType" (encodeTuple c t)
208− REmpty -> encodeTaggedContents "REmpty" jsonEmptyObject
209− RCons s t1 t2 -> encodeTaggedContents "RCons" (encodeTriple s t1 t2)
184+ TypeVar val -> tagged "TypeVar" (encodeJson val)
185+ TypeLevelString val -> tagged "TypeLevelString" (encodeJson val)
186+ TypeConstructor val -> tagged "TypeConstructor" (encodeJson val)
187+ TypeOp val -> tagged "TypeOp" (encodeJson val)
188+ TypeApp t1 t2 -> tagged "TypeApp" (encodeTuple t1 t2)
189+ KindApp t1 t2 -> tagged "KindApp" (encodeTuple t1 t2)
190+ ForAll str Nothing ty -> tagged "ForAll" (encodeTriple str ty emptySkolemScope)
191+ ForAll str (Just k) ty -> tagged "ForAll" (encodeQuadriple str k ty emptySkolemScope)
192+ ConstrainedType c t -> tagged "ConstrainedType" (encodeTuple c t)
193+ REmpty -> tagged "REmpty" jsonEmptyObject
194+ RCons s t1 t2 -> tagged "RCons" (encodeTriple s t1 t2)
195+ Kinded t1 t2 -> tagged "KindedType" (encodeTuple t1 t2)
196+ ParensInType t -> tagged "ParensInType" (encodeJson t)
197+ TypeWildcard -> tagged "TypeWildcard" jsonEmptyObject
210198 BinaryNoParensType t1 t2 t3 ->
211− encodeTaggedContents "BinaryNoParensType" (encodeTriple t1 t2 t3)
212− ParensInType t -> encodeTaggedContents "ParensInType" (encodeJson t)
213− TypeWildcard -> encodeTaggedContents "TypeWildcard" jsonEmptyObject
199+ tagged "BinaryNoParensType" (encodeTriple t1 t2 t3)
200+
201+emptySkolemScope :: Maybe Int
202+emptySkolemScope = Nothing
214203
215204newtype FunDep
216205 = FunDep
@@ -248,7 +237,7 @@ instance encodeJsonFunDeps :: EncodeJson FunDeps where
248237newtype TypeArgument
249238 = TypeArgument
250239 { name :: String
251− , mbKind :: Maybe Kind
240+ , mbKind :: Maybe Type
252241 }
253242
254243derive newtype instance eqTypeArgument :: Eq TypeArgument
@@ -272,17 +261,16 @@ decodeTuple
272261 . DecodeJson fst
273262 => DecodeJson sec
274263 => (fst -> sec -> res)
275− -> (forall a. a -> String)
264+ -> (forall a. a -> JsonDecodeError)
276265 -> Json
277− -> Either String res
266+ -> Either JsonDecodeError res
278267decodeTuple cont err json =
279− fromMaybe (Left $ err unit) $
280− toArray json >>= \jsons ->
281− jsons !! 0 >>= \json1 ->
282− jsons !! 1 >>= \json2 -> pure $ do
283− fst <- decodeJson json1
284− sec <- decodeJson json2
285− pure $ cont fst sec
268+ case toArray json of
269+ Just [ json1, json2 ] -> do
270+ fst <- decodeJson json1
271+ sec <- decodeJson json2
272+ pure $ cont fst sec
273+ _ -> Left $ err unit
286274
287275-- | Decode a heterogeneous triple.
288276decodeTriple
@@ -291,9 +279,9 @@ decodeTriple
291279 => DecodeJson sec
292280 => DecodeJson trd
293281 => (fst -> sec -> trd -> res)
294− -> (forall a. a -> String)
282+ -> (forall a. a -> JsonDecodeError)
295283 -> Json
296− -> Either String res
284+ -> Either JsonDecodeError res
297285decodeTriple cont err json =
298286 case toArray json of
299287 Just [ json1, json2, json3 ] -> do
@@ -303,6 +291,28 @@ decodeTriple cont err json =
303291 pure $ cont fst sec trd
304292 _ -> Left $ err unit
305293
294+-- | Decode a heterogeneous quadriple.
295+decodeQuadriple
296+ :: forall fst sec trd frt res
297+ . DecodeJson fst
298+ => DecodeJson sec
299+ => DecodeJson trd
300+ => DecodeJson frt
301+ => (fst -> sec -> trd -> frt -> res)
302+ -> (forall a. a -> JsonDecodeError)
303+ -> Json
304+ -> Either JsonDecodeError res
305+decodeQuadriple cont err json =
306+ case toArray json of
307+ Just [ json1, json2, json3, json4 ] -> do
308+ fst <- decodeJson json1
309+ sec <- decodeJson json2
310+ trd <- decodeJson json3
311+ frt <- decodeJson json4
312+ pure $ cont fst sec trd frt
313+ _ -> Left $ err unit
314+
315+
306316-- | Decode a `.contents` property.
307317decodeContents :: forall r. (Json -> r) -> r -> Json -> r
308318decodeContents go err json =
@@ -336,9 +346,60 @@ encodeTriple
336346encodeTriple fst sec trd =
337347 fromArray [ encodeJson fst, encodeJson sec, encodeJson trd ]
338348
339−encodeTaggedContents :: String -> Json -> Json
340−encodeTaggedContents tag contents =
349+encodeQuadriple
350+ :: forall fst sec trd frt
351+ . EncodeJson fst
352+ => EncodeJson sec
353+ => EncodeJson trd
354+ => EncodeJson frt
355+ => fst
356+ -> sec
357+ -> trd
358+ -> frt
359+ -> Json
360+encodeQuadriple fst sec trd frt =
361+ fromArray [ encodeJson fst, encodeJson sec, encodeJson trd, encodeJson frt ]
362+
363+tagged :: String -> Json -> Json
364+tagged tag contents =
341365 fromObject $ Object.fromFoldable
342366 [ Tuple "tag" (encodeJson tag)
343367 , Tuple "contents" contents
344368 ]
369+
370+
371+joinForAlls
372+ :: Type
373+ -> { binders :: List { name :: String
374+ , mbKind :: Maybe Type }
375+ , ty :: Type
376+ }
377+joinForAlls ty = go Nil ty
378+ where
379+ go acc (ForAll name mbKind ty') =
380+ go ({ name, mbKind } : acc) ty'
381+ go acc ty' = { binders: acc, ty: ty' }
382+
383+joinRows :: Type -> { rows :: List { row :: Identifier
384+ , ty :: Type
385+ }
386+ , ty :: Maybe Type }
387+joinRows = go Nil
388+ where
389+ go acc (RCons row ty rest) =
390+ go ({ row, ty } : acc) rest
391+ go acc ty = { rows: List.reverse acc
392+ , ty:
393+ case ty of
394+ REmpty -> Nothing
395+ ty' -> Just ty'
396+ }
397+
398+-- | Only returns a list of type class names (lists of arguments are omitted).
399+joinConstraints :: Type -> { constraints :: List Identifier
400+ , ty :: Type }
401+joinConstraints = go Nil
402+ where
403+ go acc (ConstrainedType (Constraint { constraintClass: QualifiedName { name } }) ty) =
404+ go (name : acc) ty
405+ go acc ty = { constraints: List.sort acc, ty }
src/Docs/Search/TypeIndex.jsnew file+26
@@ -0,0 +1,26 @@
1+/* global exports */
2+
3+export function lookup_ (shape) {
4+ return function (url) {
5+ return function () {
6+ return new Promise(function (resolve, reject) {
7+ if (typeof window.DocsSearchTypeIndex[shape] === 'undefined') {
8+ var script = document.createElement('script');
9+ script.type = 'text/javascript';
10+ script.src = url;
11+ script.addEventListener('load', function () {
12+ if (typeof window.DocsSearchTypeIndex[shape] === 'undefined') {
13+ reject(new Error("Couldn't load index for type shape " + shape));
14+ } else {
15+ resolve(window.DocsSearchTypeIndex[shape]);
16+ }
17+ });
18+ script.addEventListener('error', reject);
19+ document.body.appendChild(script);
20+ } else {
21+ resolve(window.DocsSearchTypeIndex[shape]);
22+ }
23+ });
24+ };
25+ };
26+};
src/Docs/Search/TypeIndex.pursnew file+111
@@ -0,0 +1,111 @@
1+-- | Partial type index, can be loaded on demand in the browser.
2+module Docs.Search.TypeIndex where
3+
4+import Docs.Search.Config as Config
5+import Docs.Search.Declarations (resultsForDeclaration)
6+import Docs.Search.DocsJson (DocsJson(..))
7+import Docs.Search.Score (Scores)
8+import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
9+import Docs.Search.TypeDecoder (Type)
10+import Docs.Search.TypeQuery (TypeQuery)
11+import Docs.Search.Types (ModuleName(..))
12+import Docs.Search.TypeShape (shapeOfType, shapeOfTypeQuery, stringifyShape)
13+
14+import Prelude
15+import Prim hiding (Type)
16+import Control.Promise (Promise, toAffE)
17+import Data.Argonaut.Core (Json)
18+import Data.Argonaut.Decode (decodeJson)
19+import Data.Array as Array
20+import Data.Either (hush)
21+import Data.Foldable (fold, foldr)
22+import Data.Map (Map)
23+import Data.Map as Map
24+import Data.Maybe (Maybe(..), fromMaybe', isJust)
25+import Data.Newtype (class Newtype, over)
26+import Effect (Effect)
27+import Effect.Aff (Aff, try)
28+
29+
30+newtype TypeIndex = TypeIndex (Map String (Maybe (Array SearchResult)))
31+
32+derive instance newtypeTypeIndex :: Newtype TypeIndex _
33+
34+
35+mkTypeIndex :: Scores -> Array DocsJson -> TypeIndex
36+mkTypeIndex scores docsJsons =
37+ TypeIndex $ map Just $ foldr insert Map.empty docsJsons
38+ where
39+ insert :: DocsJson -> Map String (Array SearchResult) -> Map String (Array SearchResult)
40+ insert docsJson mp =
41+ Array.foldr (\result ->
42+ case getType result of
43+ Just ty ->
44+ Map.insertWith append (stringifyShape $ shapeOfType ty) (pure result)
45+ Nothing -> identity
46+ ) mp (allResults scores docsJson)
47+
48+
49+allResults :: Scores -> DocsJson -> Array SearchResult
50+allResults scores (DocsJson { name, declarations }) =
51+ declarations >>= (resultsForDeclaration scores (ModuleName name) >>>
52+ map (_.result) >>> Array.fromFoldable)
53+
54+
55+resultsWithTypes :: Scores -> DocsJson -> Array SearchResult
56+resultsWithTypes scores = Array.filter (getType >>> isJust) <<< allResults scores
57+
58+
59+getType :: SearchResult -> Maybe Type
60+getType (SearchResult { info }) =
61+ case info of
62+ ValueResult dict ->
63+ Just dict.type
64+
65+ TypeClassMemberResult dict ->
66+ Just dict.type
67+
68+ TypeSynonymResult dict ->
69+ Just dict.type
70+
71+ _ -> Nothing
72+
73+
74+lookup
75+ :: String
76+ -> TypeIndex
77+ -> Aff { index :: TypeIndex, results :: Array SearchResult }
78+lookup key index@(TypeIndex map) =
79+ case Map.lookup key map of
80+ Just results -> pure { index, results: fold results }
81+ Nothing -> do
82+ eiJson <- try (toAffE (lookup_ key $ Config.mkShapeScriptPath key))
83+ pure $ fromMaybe'
84+ (\_ -> { index: insert key Nothing index, results: [] })
85+ do
86+ json <- hush eiJson
87+ results <- hush (decodeJson json)
88+ pure { index: insert key (Just results) index, results }
89+
90+ where
91+ insert
92+ :: String
93+ -> Maybe (Array SearchResult)
94+ -> TypeIndex
95+ -> TypeIndex
96+ insert k v = over TypeIndex (Map.insert k v)
97+
98+
99+query
100+ :: TypeIndex
101+ -> TypeQuery
102+ -> Aff { index :: TypeIndex, results :: Array SearchResult }
103+query typeIndex typeQuery = do
104+ res <- lookup (stringifyShape $ shapeOfTypeQuery typeQuery) typeIndex
105+ pure $ res { results = res.results }
106+
107+
108+foreign import lookup_
109+ :: String
110+ -> String
111+ -> Effect (Promise Json)
src/Docs/Search/TypePrinter.pursnew file+169
@@ -0,0 +1,169 @@
1+module Docs.Search.TypePrinter where
2+
3+import Prelude
4+import Prim hiding (Type, Constraint)
5+import Docs.Search.Extra ((>#>))
6+import Docs.Search.Terminal (cyan)
7+import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), QualifiedName(..), Type(..), TypeArgument(..), joinForAlls, joinRows)
8+import Docs.Search.Types (Identifier(..))
9+
10+import Data.Array as Array
11+import Data.List as List
12+import Data.Maybe (Maybe(..), fromMaybe)
13+import Data.Newtype (unwrap)
14+
15+
16+-- | A pretty-printer for types, for TTY with colors.
17+showType :: Type -> String
18+showType = case _ of
19+ TypeVar str -> str
20+ TypeLevelString str -> "\"" <> str <> "\"" -- TODO: add escaping
21+ TypeWildcard -> "_"
22+ TypeConstructor qname -> showQualifiedName qname
23+ TypeOp qname -> showQualifiedName qname
24+
25+ TypeApp (TypeApp (TypeConstructor
26+ (QualifiedName { moduleNameParts: [ "Prim" ]
27+ , name: Identifier "Function" }))
28+ t1)
29+ t2 ->
30+ showType t1 <> syntax " -> " <> showType t2
31+
32+ TypeApp (TypeConstructor (QualifiedName { moduleNameParts: [ "Prim" ]
33+ , name: Identifier "Record" }))
34+ row ->
35+ showRow false row
36+
37+ TypeApp t1 t2 ->
38+ showType t1 <> " " <> showType t2
39+
40+ KindApp t1 t2 ->
41+ showType t1 <> " " <> showType t2
42+
43+ ty@(ForAll _ _ _) ->
44+ showForAll ty
45+
46+ ConstrainedType cnstr ty ->
47+ showConstraint cnstr <>
48+ syntax " => " <>
49+ showType ty
50+
51+ ty@REmpty -> showRow true ty
52+ ty@(RCons _ _ _) -> showRow true ty
53+
54+ Kinded t1 t2 -> showType t1 <> " :: " <> showType t2
55+
56+ BinaryNoParensType op t1 t2 ->
57+ showType t1 <>
58+ space <>
59+ showType op <>
60+ space <>
61+ showType t2
62+
63+ ParensInType ty ->
64+ "(" <>
65+ showType ty <>
66+ ")"
67+
68+
69+showTypeArgument :: TypeArgument -> String
70+showTypeArgument (TypeArgument { name, mbKind }) =
71+ case mbKind of
72+ Nothing ->
73+ name
74+ Just kind ->
75+ "(" <>
76+ name <>
77+ " :: " <>
78+ showType kind <>
79+ ")"
80+
81+
82+showFunDeps :: FunDeps -> String
83+showFunDeps (FunDeps []) = ""
84+showFunDeps (FunDeps deps) =
85+ append (syntax " | ") $
86+ Array.intercalate (syntax ", ") $
87+ deps <#> renderFunDep
88+ where
89+ renderFunDep (FunDep { lhs, rhs }) =
90+ Array.intercalate space lhs <>
91+ syntax " -> " <>
92+ Array.intercalate space rhs
93+
94+
95+showQualifiedName
96+ :: QualifiedName
97+ -> String
98+showQualifiedName (QualifiedName { name })
99+ = unwrap name
100+
101+
102+showRow
103+ :: Boolean
104+ -> Type
105+ -> String
106+showRow asRow =
107+ joinRows >>> \ { rows, ty } ->
108+ if List.null rows
109+ then
110+ if asRow
111+ then "()"
112+ else fromMaybe "{}" $ ty <#> \ty' -> "Record " <> showType ty'
113+ else
114+ opening <>
115+ ( Array.intercalate ", " $ Array.fromFoldable $ rows <#>
116+ \entry ->
117+ unwrap entry.row <> syntax " :: " <> showType entry.ty
118+ ) <>
119+
120+ (ty >#> \ty' -> " | " <> showType ty') <>
121+
122+ closing
123+
124+ where
125+ opening = if asRow then "(" else "{ "
126+ closing = if asRow then ")" else " }"
127+
128+
129+showForAll
130+ :: Type
131+ -> String
132+showForAll ty =
133+ keyword "forall" <>
134+
135+ ( Array.fold $ Array.fromFoldable $ foralls.binders <#>
136+ \ { name, mbKind } ->
137+ case mbKind of
138+ Nothing -> " " <> name
139+ Just kind ->
140+ " (" <> name <> " "
141+ <> syntax "::"
142+ <> space
143+ <> showType kind
144+ <> ")"
145+ ) <>
146+
147+ syntax ". " <>
148+ showType foralls.ty
149+
150+ where
151+ foralls = joinForAlls ty
152+
153+
154+showConstraint
155+ :: Constraint
156+ -> String
157+showConstraint (Constraint { constraintClass, constraintArgs }) =
158+ showQualifiedName constraintClass <> space <>
159+ Array.intercalate space (constraintArgs <#> showType)
160+
161+
162+syntax :: String -> String
163+syntax = cyan
164+
165+space :: String
166+space = " "
167+
168+keyword :: String -> String
169+keyword = cyan
src/Docs/Search/TypeQuery.pursnew file+440
@@ -0,0 +1,440 @@
1+-- | `TypeQuery` is a representation of a user-provided type.
2+module Docs.Search.TypeQuery
3+ ( TypeQuery(..)
4+ , Substitution(..)
5+ , parseTypeQuery
6+ , typeQueryParser
7+ , getFreeVariables
8+ , typeVarPenalty
9+ , penalty
10+ )
11+where
12+
13+import Docs.Search.Config as Config
14+import Docs.Search.Extra (foldl1, foldr1)
15+import Docs.Search.TypeDecoder (QualifiedName(..), Type(..), joinConstraints, joinRows)
16+import Docs.Search.Types (Identifier(..))
17+
18+import Prelude
19+import Prim hiding (Type)
20+import Control.Alt ((<|>))
21+import Data.Array as Array
22+import Data.Either (Either)
23+import Data.Generic.Rep (class Generic)
24+import Data.Show.Generic (genericShow)
25+import Data.List (List(..), many, some, (:))
26+import Data.List as List
27+import Data.List.NonEmpty (NonEmptyList)
28+import Data.List.NonEmpty as NonEmptyList
29+import Data.Map (Map)
30+import Data.Map as Map
31+import Data.Ord (abs)
32+import Data.Set (Set)
33+import Data.Set as Set
34+import Data.String.CodeUnits (fromCharArray)
35+import Data.String.Common (trim) as String
36+import Data.Tuple (Tuple(..), fst, snd)
37+import StringParser (ParseError, Parser, runParser, try)
38+import StringParser.CodePoints (alphaNum, anyLetter, char, eof, lowerCaseChar, skipSpaces, string, upperCaseChar)
39+import StringParser.Combinators (fix, sepBy, sepBy1, sepEndBy, sepEndBy1)
40+
41+
42+-- | We need type queries because we don't have a full-featured type parser
43+-- | available.
44+data TypeQuery
45+ = QVar Identifier
46+ | QConst Identifier
47+ | QFun TypeQuery TypeQuery
48+ | QApp TypeQuery TypeQuery
49+ | QForAll (NonEmptyList Identifier) TypeQuery
50+ | QConstraint Identifier (List TypeQuery) TypeQuery
51+ | QRow (List (Tuple Identifier TypeQuery))
52+
53+derive instance eqTypeQuery :: Eq TypeQuery
54+derive instance genericTypeQuery :: Generic TypeQuery _
55+
56+instance showTypeQuery :: Show TypeQuery where
57+ show x = genericShow x
58+
59+
60+parseTypeQuery :: String -> Either ParseError TypeQuery
61+parseTypeQuery = String.trim >>> runParser (typeQueryParser <* eof)
62+
63+
64+typeQueryParser :: Parser TypeQuery
65+typeQueryParser = fix \typeQuery ->
66+ let rowFields =
67+ QRow <$> sepBy (Tuple <$> (skipSpaces *> ident <* skipSpaces <* string "::") <*>
68+ (skipSpaces *> typeQuery <* skipSpaces))
69+ (string "," *> skipSpaces)
70+
71+ row = string "(" *> rowFields <* string ")"
72+
73+ record = QApp (QConst $ Identifier "Record") <$>
74+ (string "{" *> rowFields <* string "}")
75+
76+ binders =
77+ string "forall" *> some space *> sepEndBy1 ident skipSpaces <* string "." <* skipSpaces
78+
79+ for_all = QForAll <$> binders <*> typeQuery
80+
81+ parens =
82+ string "(" *> skipSpaces *> typeQuery <* skipSpaces <* string ")"
83+
84+ atom = skipSpaces *> (
85+ for_all <|>
86+ try parens <|>
87+ row <|>
88+ record <|>
89+ concrete <|>
90+ any
91+ )
92+
93+ apps =
94+ foldl1 QApp <$> sepEndBy1 atom (some space)
95+
96+ funs =
97+ foldr1 QFun <$> sepBy1 apps (string "->" *> skipSpaces)
98+
99+ constrained =
100+ QConstraint <$> (upperCaseIdent <* skipSpaces) <*>
101+ (sepEndBy ((QVar <$> ident) <|> parens)
102+ (many space) <* string "=>" <* skipSpaces) <*>
103+ typeQuery
104+ in
105+ try constrained <|> funs
106+
107+
108+any :: Parser TypeQuery
109+any = do
110+ QVar <$> lowerCaseIdent
111+
112+concrete :: Parser TypeQuery
113+concrete =
114+ QConst <$> upperCaseIdent
115+
116+ident :: Parser Identifier
117+ident = do
118+ head <- anyLetter
119+ rest <- Array.many (alphaNum <|> char '\'')
120+ pure $ Identifier <$> fromCharArray $ pure head <> rest
121+
122+upperCaseIdent :: Parser Identifier
123+upperCaseIdent = do
124+ head <- upperCaseChar
125+ rest <- Array.many (alphaNum <|> char '\'')
126+ pure $ Identifier $ fromCharArray $ pure head <> rest
127+
128+lowerCaseIdent :: Parser Identifier
129+lowerCaseIdent = do
130+ head <- lowerCaseChar
131+ rest <- Array.many (alphaNum <|> char '\'')
132+ pure $ Identifier $ fromCharArray $ pure head <> rest
133+
134+space :: Parser Char
135+space = char ' '
136+
137+
138+-- | Used only in `getFreeVariables`.
139+data FreeVarCounterQueueEntry = Unbind (Set.Set Identifier) | Next TypeQuery
140+
141+getFreeVariables :: TypeQuery -> Set.Set Identifier
142+getFreeVariables query = go Set.empty Set.empty (List.singleton $ Next query)
143+ where
144+ insertIfUnbound bound var free =
145+ if Set.member var bound
146+ then free
147+ else Set.insert var free
148+
149+ go bound free Nil = free
150+ go bound free (Unbind vars : rest) =
151+ go (Set.difference bound vars) free rest
152+
153+ go bound free (Next (QVar var) : rest) =
154+ go bound (insertIfUnbound bound var free) rest
155+
156+ go bound free (Next (QConst str) : rest) =
157+ go bound free rest
158+ go bound free (Next (QFun q1 q2) : rest) =
159+ go bound free (Next q1 : Next q2 : rest)
160+ go bound free (Next (QApp q1 q2) : rest) =
161+ go bound free (Next q1 : Next q2 : rest)
162+
163+ go bound free (Next (QForAll nl q) : rest) =
164+ go (Set.union bound newBound) free queue
165+ where
166+ newBound = NonEmptyList.foldr Set.insert mempty nl
167+ queue = (Next q : Unbind (Set.difference newBound bound) : rest)
168+
169+ go bound free (Next (QConstraint _ vars q) : rest) =
170+ go bound free ((Next <$> vars) <> (Next q : rest))
171+
172+ go bound free (Next (QRow lst) : rest) =
173+ go bound free ((lst <#> snd >>> Next) <> rest)
174+
175+
176+data Substitution
177+ = Instantiate Identifier Type
178+ | Match Identifier Identifier
179+ | Generalize TypeQuery Identifier
180+ | Substitute Identifier Identifier
181+ | MatchConstraints (Set Identifier) (Set Identifier)
182+ | MissingConstraint
183+ | ExcessiveConstraint
184+ | RowsMismatch Int Int
185+ | Mismatch TypeQuery Type
186+ -- ^ Type and type query significantly differ.
187+ | TypeMismatch Type
188+ -- ^ A query of size 1 corresponds to some type.
189+ | QueryMismatch TypeQuery
190+ -- ^ A type of size 1 corresponds to some query.
191+
192+derive instance genericSubstitution :: Generic Substitution _
193+
194+instance showSubstitution :: Show Substitution where
195+ show x = genericShow x
196+
197+
198+-- | A mock-up of unification algorithm, that does not unify anything, actually.
199+-- | We use it to estimate how far a type is from a type query, by looking into
200+-- | the resulting list.
201+unify :: TypeQuery -> Type -> List Substitution
202+unify query type_ = go Nil (List.singleton { q: query, t: type_ })
203+ where
204+ go :: List Substitution -> List { q :: TypeQuery, t :: Type } -> List Substitution
205+ go acc Nil = acc
206+ go acc ({ q, t: ParensInType t } : rest) =
207+ go acc ({ q, t } : rest)
208+
209+ -- * ForAll
210+ go acc ({ q, t: ForAll _ _ t } : rest) =
211+ go acc ({ q, t } : rest)
212+ go acc ({ q: (QForAll _ q), t } : rest) =
213+ go acc ({ q, t } : rest)
214+
215+ -- * Constraints
216+ go acc ({ q: q@(QConstraint _ _ _)
217+ , t: t@(ConstrainedType _ _) } : rest) =
218+ let qcs = Set.fromFoldable (joinQueryConstraints q).constraints
219+ tcs = Set.fromFoldable (joinConstraints t).constraints
220+ in
221+ -- TODO: use edit distance instead
222+ go (MatchConstraints qcs tcs : acc) rest
223+ go acc ({ q: QConstraint _ _ q, t } : rest) =
224+ go (ExcessiveConstraint : acc) ({ q, t } : rest)
225+ go acc ({ q, t: ConstrainedType _ t } : rest) =
226+ go (MissingConstraint : acc) ({ q, t } : rest)
227+
228+ -- * Type variables
229+ go acc ({ q: QVar q, t: TypeVar v } : rest) =
230+ go (Substitute q (Identifier v) : acc) rest
231+ go acc ({ q, t: TypeVar v } : rest ) =
232+ go (Generalize q (Identifier v) : acc) rest
233+ go acc ({ q: QVar v, t } : rest) =
234+ go (Instantiate v t : acc) rest
235+
236+ -- * Names
237+ go acc ({ q: QConst qname, t: TypeConstructor (QualifiedName { name }) } : rest) =
238+ go (Match qname name : acc) rest
239+ go acc ({ q: QConst _, t } : rest) =
240+ go (TypeMismatch t : acc) rest
241+ go acc ({ q, t: TypeConstructor (QualifiedName _) } : rest) =
242+ go (QueryMismatch q : acc) rest
243+
244+ -- type operators can't appear in type queries: this is always a mismatch
245+ go acc ({ q, t: TypeOp (QualifiedName _) } : rest) =
246+ go (QueryMismatch q : acc) rest
247+ go acc ({ q, t: t@(BinaryNoParensType _ _ _) } : rest) =
248+ go (Mismatch q t : acc) rest
249+
250+ -- * Functions
251+ go acc ({ q: QFun q1 q2
252+ , t: TypeApp (TypeApp (TypeConstructor
253+ (QualifiedName { moduleNameParts: [ "Prim" ]
254+ , name: Identifier "Function" })) t1) t2 } : rest) =
255+ go acc ({ q: q1, t: t1 } : { q: q2, t: t2 } : rest)
256+ go acc ({ q: q@(QFun _ _), t } : rest) =
257+ go (Mismatch q t : acc) rest
258+
259+ -- * Rows
260+ go acc ({ q: QApp (QConst (Identifier "Record")) (QRow qRows)
261+ , t: TypeApp (TypeConstructor
262+ (QualifiedName { moduleNameParts: [ "Prim" ]
263+ , name: Identifier "Record" })) row } : rest) =
264+ let { rows } = joinRows row
265+ qRowsLength = List.length qRows
266+ rowsLength = List.length rows in
267+ if rowsLength == qRowsLength then
268+ let
269+ sortedQRows = List.sortBy (\x y -> compare (fst x) (fst y)) qRows
270+ sortedRows = List.sortBy (\x y -> compare x.row y.row) rows in
271+ go
272+ -- match row names
273+ (List.zipWith (\(Tuple qRowName _) { row: rowName } ->
274+ Match qRowName rowName) sortedQRows sortedRows <> acc)
275+ -- match row types
276+ (List.zipWith (\(Tuple _ q) { ty: t } ->
277+ { q, t }) sortedQRows sortedRows <> rest)
278+ else
279+ go (RowsMismatch qRowsLength rowsLength : acc) rest
280+
281+ go acc ({ q: q@(QRow _), t } : rest) =
282+ go (Mismatch q t : acc) rest
283+
284+ -- * Type application
285+ go acc ({ q: QApp q1 q2, t: TypeApp t1 t2 } : rest) =
286+ go acc ({ q: q1, t: t1 } : { q: q2, t: t2 } : rest)
287+
288+ go acc ({ q, t: TypeLevelString _ } : rest) =
289+ go (QueryMismatch q : acc) rest
290+
291+ go acc ({ q, t: TypeWildcard } : rest) =
292+ go (QueryMismatch q : acc) rest
293+
294+ go acc ({ q, t: t@(RCons _ _ _) } : rest) =
295+ go (Mismatch q t : acc) rest
296+
297+ go acc ({ q, t: REmpty } : rest) =
298+ go (QueryMismatch q : acc) rest
299+
300+ go acc ({ q, t: Kinded _ _} : rest) =
301+ go (QueryMismatch q : acc) rest
302+
303+ go acc ({ t: t@(KindApp _ _) } : rest) =
304+ go (TypeMismatch t : acc) rest
305+
306+
307+-- | Sum various penalties.
308+penalty :: TypeQuery -> Type -> Int
309+penalty typeQuery ty =
310+ let substs = unify typeQuery ty in
311+ typeVarPenalty substs * Config.penalties.typeVars +
312+ namesPenalty substs +
313+ mismatchPenalty substs
314+
315+
316+-- | Penalty for type variables mismatch.
317+-- | Congruent types should receive zero penalty points.
318+typeVarPenalty :: List Substitution -> Int
319+typeVarPenalty substs =
320+ penaltyFor (varSubstMapWith (flip insertion)) +
321+ penaltyFor (varSubstMapWith insertion)
322+ where
323+ penaltyFor varSubstMap =
324+ abs $
325+ List.length (List.foldMap List.fromFoldable varSubstMap) - Map.size varSubstMap
326+
327+ insertion v1 v2 = Map.insertWith append v1 (Set.singleton v2)
328+
329+ varSubstMapWith
330+ :: (Identifier ->
331+ Identifier ->
332+ Map Identifier (Set Identifier) ->
333+ Map Identifier (Set Identifier))
334+ -> Map Identifier (Set Identifier)
335+ varSubstMapWith f =
336+ List.foldr (case _ of
337+ Substitute v1 v2 ->
338+ f v1 v2
339+ _ -> identity
340+ ) Map.empty substs
341+
342+
343+-- | Penalty for name mismatches.
344+namesPenalty :: List Substitution -> Int
345+namesPenalty = go 0
346+ where
347+ go p Nil = p
348+ go p (Match a b : rest)
349+ | a == b = go p rest
350+ | otherwise = go (p + Config.penalties.match) rest
351+ go p (MatchConstraints qcs tcs : rest)
352+ = let p' = Set.size (Set.union qcs tcs) -
353+ Set.size (Set.intersection qcs tcs) in
354+ go (p + Config.penalties.matchConstraint * p') rest
355+ go p (RowsMismatch n m : rest) = go (Config.penalties.rowsMismatch * abs (n - m)) rest
356+ go p (_ : rest) = go p rest
357+
358+
359+-- | Penalty for generalization and instantiation.
360+mismatchPenalty :: List Substitution -> Int
361+mismatchPenalty = go 0
362+ where
363+ go n Nil = n
364+ go n (Instantiate q t : rest) = go (n + typeSize t *
365+ Config.penalties.instantiate) rest
366+ go n (Generalize q t : rest) = go (n + typeQuerySize q *
367+ Config.penalties.generalize) rest
368+ go n (ExcessiveConstraint : rest) = go (n + Config.penalties.excessiveConstraint) rest
369+ go n (MissingConstraint : rest) = go (n + Config.penalties.missingConstraint) rest
370+ go n (Mismatch q t : rest) = go (n + typeQuerySize q + typeSize t) rest
371+ go n (TypeMismatch t : rest) = go (n + typeSize t) rest
372+ go n (QueryMismatch q : rest) = go (n + typeQuerySize q) rest
373+ go n (_ : rest) = go n rest
374+
375+
376+-- | Only returns a list of type class names (lists of arguments are omitted).
377+joinQueryConstraints :: TypeQuery -> { constraints :: List Identifier
378+ , ty :: TypeQuery }
379+joinQueryConstraints = go Nil
380+ where
381+ go acc (QConstraint name _ query) =
382+ go (name : acc) query
383+ go acc ty = { constraints: List.sort acc, ty }
384+
385+
386+typeQuerySize :: TypeQuery -> Int
387+typeQuerySize = go 0 <<< List.singleton
388+ where
389+ go n Nil = n
390+ go n (QVar _ : rest) =
391+ go (n + 1) rest
392+ go n (QConst _ : rest) =
393+ go (n + 1) rest
394+ go n (QFun q1 q2 : rest) =
395+ go (n + 1) (q1 : q2 : rest)
396+ go n (QApp q1 q2 : rest) =
397+ go (n + 1) (q1 : q2 : rest)
398+ go n (QForAll _ q : rest) =
399+ go (n + 1) (q : rest)
400+ go n (QConstraint _ _ q : rest) =
401+ go (n + 1) (q : rest)
402+ go n (QRow qs : rest) =
403+ go n ((qs <#> snd) <> rest)
404+
405+
406+typeSize :: Type -> Int
407+typeSize = go 0 <<< List.singleton
408+ where
409+ go n Nil = n
410+ go n (TypeVar _ : rest) =
411+ go (n + 1) rest
412+ go n (TypeLevelString _ : rest) =
413+ go (n + 1) rest
414+ go n (TypeWildcard : rest) =
415+ go (n + 1) rest
416+ go n (TypeConstructor _ : rest) =
417+ go (n + 1) rest
418+ go n (TypeOp _ : rest) =
419+ go (n + 1) rest
420+ go n (KindApp t1 t2 : res) = go n (t1 : t2 : res)
421+ go n (TypeApp (TypeApp (TypeConstructor
422+ (QualifiedName { moduleNameParts: [ "Prim" ]
423+ , name: Identifier "Function" })) t1) t2 : rest) =
424+ go (n + 1) (t1 : t2 : rest)
425+ go n (TypeApp q1 q2 : rest) =
426+ go (n + 1) (q1 : q2 : rest)
427+ go n (ForAll _ _ t : rest) =
428+ go (n + 1) (t : rest)
429+ go n (ConstrainedType _ t : rest) =
430+ go (n + 1) (t : rest)
431+ go n (RCons _ t1 t2 : rest) =
432+ go (n + 1) (t1 : t2 : rest)
433+ go n (REmpty : rest) =
434+ go (n + 1) rest
435+ go n (Kinded t1 t2 : rest) =
436+ go n (t1 : t2 : rest)
437+ go n (BinaryNoParensType op t1 t2 : rest) =
438+ go (n + 1) (t1 : t2 : rest)
439+ go n (ParensInType t : rest) =
440+ go n (t : rest)
src/Docs/Search/TypeShape.jsnew file+14
@@ -0,0 +1,14 @@
1+/* global exports */
2+
3+export function hash (string) {
4+ var hash = Math.floor(Number.MAX_SAFE_INTEGER / 2);
5+ if (string.length == 0) {
6+ return hash;
7+ }
8+ for (var i = 0; i < string.length; i++) {
9+ var char = string.charCodeAt(i);
10+ hash = ((hash<<5)-hash)+char;
11+ hash = hash & hash; // Convert to 32bit integer
12+ }
13+ return hash;
14+};
src/Docs/Search/TypeShape.pursnew file+148
@@ -0,0 +1,148 @@
1+-- | We need `TypeShape`s as a way to "semantically hash" types.
2+-- | This allows us to split type index in parts and load
3+-- | it on demand.
4+module Docs.Search.TypeShape where
5+
6+import Docs.Search.TypeDecoder (QualifiedName(..), Type(..), joinForAlls, joinRows)
7+import Docs.Search.TypeQuery (TypeQuery(..), getFreeVariables)
8+import Docs.Search.Types (Identifier(..))
9+
10+import Prelude
11+import Prim hiding (Type)
12+import Data.Generic.Rep (class Generic)
13+import Data.Show.Generic (genericShow)
14+import Data.List (List(..), (:))
15+import Data.List as List
16+import Data.List.NonEmpty as NonEmptyList
17+import Data.Set as Set
18+import Data.Tuple (Tuple(..), snd)
19+import Data.Ord (abs)
20+
21+
22+type TypeShape = List ShapeChunk
23+
24+
25+data ShapeChunk
26+ = PVar
27+ | PFun
28+ | PApp
29+ | PForAll Int
30+ | PRow Int
31+
32+derive instance eqShapeChunk :: Eq ShapeChunk
33+derive instance ordShapeChunk :: Ord ShapeChunk
34+derive instance genericShapeChunk :: Generic ShapeChunk _
35+
36+instance showShapeChunk :: Show ShapeChunk where
37+ show x = genericShow x
38+
39+
40+stringifyShape :: TypeShape -> String
41+stringifyShape shape =
42+ show $ abs $ hash
43+ if res == "" then "0" else res
44+ where
45+ res = List.foldMap stringifyChunk shape
46+ stringifyChunk =
47+ case _ of
48+ PVar -> "v"
49+ PFun -> "f"
50+ PApp -> "a"
51+ PForAll n -> "b" <> show n
52+ PRow n -> "r" <> show n
53+
54+
55+shapeOfTypeQuery :: TypeQuery -> TypeShape
56+shapeOfTypeQuery query =
57+ prependForAll $ List.reverse $ go (pure query) Nil
58+ where
59+
60+ prependForAll (PForAll n : rest) =
61+ PForAll (count + n) : rest
62+ prependForAll shape =
63+ if count == 0
64+ then shape
65+ else PForAll count : shape
66+
67+ count = Set.size $ getFreeVariables query
68+
69+ go Nil acc = acc
70+ go (this:rest) acc =
71+ case this of
72+ QVar _ ->
73+ go rest (PVar : acc)
74+ QConst v ->
75+ go rest (PVar : acc)
76+ QFun q1 q2 ->
77+ go (q1 : q2 : rest) (PFun : acc)
78+ QApp q1 q2 ->
79+ go (q1 : q2 : rest) (PApp : acc)
80+ QForAll lst q ->
81+ go (q : rest) (PForAll (NonEmptyList.length lst) : acc)
82+ QConstraint str lst q ->
83+ go (q : rest) acc
84+ QRow lst ->
85+ let lst' = List.sortBy (\(Tuple x _) (Tuple y _) -> compare x y) lst in
86+ go (map snd lst' <> rest) (PRow (List.length lst) : acc)
87+
88+
89+shapeOfType :: Type -> TypeShape
90+shapeOfType ty = List.reverse $ go (pure ty) Nil
91+ where
92+ go Nil acc = acc
93+ go (this:rest) acc =
94+ case this of
95+
96+ TypeVar _ ->
97+ go rest (PVar : acc)
98+
99+ TypeLevelString _ ->
100+ go rest (PVar : acc)
101+
102+ TypeWildcard ->
103+ go rest (PVar : acc)
104+
105+ TypeApp (TypeApp (TypeConstructor (QualifiedName { moduleNameParts: ["Prim"]
106+ , name: Identifier "Function" })) t1) t2 ->
107+ go (t1 : t2 : rest) (PFun : acc)
108+
109+ TypeConstructor (QualifiedName { name }) ->
110+ go rest (PVar : acc)
111+
112+ TypeOp _ ->
113+ go rest (PVar : acc)
114+
115+ TypeApp child1 child2 ->
116+ go (child1 : child2 : rest) (PApp : acc)
117+
118+ KindApp child1 child2 ->
119+ go (child1 : child2 : rest) (PApp : acc)
120+
121+ forallType@(ForAll _ _ _) ->
122+ go (foralls.ty : rest) (PForAll (List.length foralls.binders) : acc)
123+ where foralls = joinForAlls forallType
124+
125+ ParensInType child ->
126+ go (child : rest) acc
127+
128+ ConstrainedType _ child ->
129+ go (child : rest) acc
130+
131+ REmpty ->
132+ -- TODO: reconsider
133+ go rest (PVar : acc)
134+
135+ row@(RCons _ _ _) ->
136+ go (typesInRow <> rest) (PRow (List.length joined.rows) : acc)
137+ where
138+ joined = joinRows row
139+ sorted = List.sortBy (\x y -> compare x.row y.row) joined.rows
140+ typesInRow = sorted <#> (_.ty)
141+
142+ Kinded t1 _t2 -> go (t1 : rest) acc
143+
144+ BinaryNoParensType op l r ->
145+ go (TypeApp (TypeApp op l) r : rest) acc
146+
147+
148+foreign import hash :: String -> Int
src/Docs/Search/Types.pursnew file+104
@@ -0,0 +1,104 @@
1+module Docs.Search.Types where
2+
3+import Prelude
4+
5+import Data.Argonaut.Decode (class DecodeJson)
6+import Data.Argonaut.Decode.Generic (genericDecodeJson)
7+import Data.Argonaut.Encode (class EncodeJson)
8+import Data.Argonaut.Encode.Generic (genericEncodeJson)
9+import Data.Generic.Rep (class Generic)
10+import Data.Show.Generic (genericShow)
11+import Data.Newtype (class Newtype)
12+
13+
14+newtype Identifier = Identifier String
15+
16+derive instance newtypeIdentifier :: Newtype Identifier _
17+derive instance genericIdentifier :: Generic Identifier _
18+derive newtype instance eqIdentifier :: Eq Identifier
19+derive newtype instance ordIdentifier :: Ord Identifier
20+derive newtype instance showIdentifier :: Show Identifier
21+derive newtype instance decodeJsonIdentifier :: DecodeJson Identifier
22+derive newtype instance encodeJsonIdentifier :: EncodeJson Identifier
23+
24+
25+newtype ModuleName = ModuleName String
26+
27+derive instance newtypeModuleName :: Newtype ModuleName _
28+derive instance genericModuleName :: Generic ModuleName _
29+derive newtype instance eqModuleName :: Eq ModuleName
30+derive newtype instance ordModuleName :: Ord ModuleName
31+derive newtype instance decodeJsonModuleName :: DecodeJson ModuleName
32+derive newtype instance encodeJsonModuleName :: EncodeJson ModuleName
33+
34+instance Show ModuleName where
35+ show = genericShow
36+
37+
38+-- | Non-normalized package name, e.g. `purescript-prelude` or just `prelude`.
39+newtype RawPackageName = RawPackageName String
40+
41+derive instance newtypeRawPackageName :: Newtype RawPackageName _
42+
43+
44+-- | Normalized package name without "purescript-" prefix.
45+newtype PackageName = PackageName String
46+
47+derive instance newtypePackageName :: Newtype PackageName _
48+derive newtype instance eqPackageName :: Eq PackageName
49+derive newtype instance ordPackageName :: Ord PackageName
50+derive newtype instance showPackageName :: Show PackageName
51+derive newtype instance decodeJsonPackageName :: DecodeJson PackageName
52+derive newtype instance encodeJsonPackageName :: EncodeJson PackageName
53+derive instance genericPackageName :: Generic PackageName _
54+
55+
56+data PackageInfo = LocalPackage | Builtin | Package PackageName | UnknownPackage
57+
58+derive instance eqPackageInfo :: Eq PackageInfo
59+derive instance ordPackageInfo :: Ord PackageInfo
60+derive instance genericPackageInfo :: Generic PackageInfo _
61+instance decodeJsonPackageInfo :: DecodeJson PackageInfo where
62+ decodeJson = genericDecodeJson
63+instance encodeJsonPackageInfo :: EncodeJson PackageInfo where
64+ encodeJson = genericEncodeJson
65+
66+instance showPackageInfo :: Show PackageInfo where
67+ show = genericShow
68+
69+
70+newtype PackageScore = PackageScore Int
71+
72+derive instance newtypePackageScore :: Newtype PackageScore _
73+derive instance genericPackageScore :: Generic PackageScore _
74+derive newtype instance eqPackageScore :: Eq PackageScore
75+derive newtype instance ordPackageScore :: Ord PackageScore
76+derive newtype instance semiringPackageScore :: Semiring PackageScore
77+derive newtype instance ringPackageScore :: Ring PackageScore
78+derive newtype instance showPackageScore :: Show PackageScore
79+derive newtype instance decodeJsonPackageScore :: DecodeJson PackageScore
80+derive newtype instance encodeJsonPackageScore :: EncodeJson PackageScore
81+
82+
83+newtype URL = URL String
84+
85+derive instance newtypeURL :: Newtype URL _
86+derive newtype instance showURL :: Show URL
87+
88+newtype FilePath = FilePath String
89+
90+derive instance newtypeFilePath :: Newtype FilePath _
91+derive newtype instance showFilePath :: Show FilePath
92+
93+newtype GlobalIdentifier = GlobalIdentifier String
94+
95+derive instance newtypeGlobalIdentifier :: Newtype GlobalIdentifier _
96+derive newtype instance showGlobalIdentifier :: Show GlobalIdentifier
97+
98+
99+newtype PartId = PartId Int
100+
101+derive instance newtypePartId :: Newtype PartId _
102+derive newtype instance eqPartId :: Eq PartId
103+derive newtype instance ordPartId :: Ord PartId
104+derive newtype instance showPartId :: Show PartId
src/Docs/Search/URIHash.jsnew file+17
@@ -0,0 +1,17 @@
1+/* global exports history */
2+
3+// https://stackoverflow.com/questions/1397329
4+export function removeHash () {
5+ var scrollV, scrollH, loc = window.location;
6+ if ("pushState" in history)
7+ history.pushState("", document.title, loc.pathname + loc.search);
8+ else {
9+ scrollV = document.body.scrollTop;
10+ scrollH = document.body.scrollLeft;
11+
12+ loc.hash = "";
13+
14+ document.body.scrollTop = scrollV;
15+ document.body.scrollLeft = scrollH;
16+ }
17+}
src/Docs/Search/URIHash.pursnew file+38
@@ -0,0 +1,38 @@
1+module Docs.Search.URIHash
2+ ( getInput
3+ , setInput
4+ , removeHash
5+ )
6+where
7+
8+import Prelude
9+
10+import Data.Maybe (fromMaybe)
11+import Data.String.CodeUnits as String
12+import Effect (Effect)
13+import JSURI (decodeURIComponent, encodeURIComponent)
14+import Web.HTML as HTML
15+import Web.HTML.Location as Location
16+import Web.HTML.Window as Window
17+
18+foreign import removeHash :: Effect Unit
19+
20+setInput :: String -> Effect Unit
21+setInput "" = removeHash
22+setInput input = do
23+ window <- HTML.window
24+ location <- Window.location window
25+ let hash = "search:" <> fromMaybe "" (encodeURIComponent input)
26+ Location.setHash hash location
27+
28+getInput :: Effect String
29+getInput = do
30+ window <- HTML.window
31+ location <- Window.location window
32+ hash <- Location.hash location
33+ pure $
34+ if String.slice 0 8 hash == "#search:"
35+ then fromMaybe "" $
36+ decodeURIComponent $
37+ String.drop 8 hash
38+ else ""
src/Spago/Search/App.pursdeleted−94
@@ -1,94 +0,0 @@
1−module Spago.Search.App where
2−
3−import Prelude
4−
5−import Spago.Search.App.SearchField as SearchField
6−import Spago.Search.App.SearchResults as SearchResults
7−import Spago.Search.Extra (whenJust)
8−
9−import Control.Coroutine as Coroutine
10−import Data.Maybe (Maybe(..))
11−import Data.Newtype (wrap)
12−import Effect (Effect)
13−import Halogen as H
14−import Halogen.Aff as HA
15−import Halogen.VDom.Driver (runUI)
16−import Web.DOM.Document as Document
17−import Web.DOM.Element as Element
18−import Web.DOM.Node as Node
19−import Web.DOM.ParentNode as ParentNode
20−import Web.DOM.Text as Text
21−import Web.HTML as HTML
22−import Web.HTML.HTMLDocument as HTMLDocument
23−import Web.HTML.HTMLElement (fromElement)
24−import Web.HTML.Window as Window
25−
26−main :: Effect Unit
27−main = do
28− win <- HTML.window
29− doc <- HTMLDocument.toDocument <$> Window.document win
30−
31− insertStyle doc
32− mbContainers <- getContainers doc
33−
34− whenJust mbContainers \ { searchField, searchResults, pageContents } -> do
35− HA.runHalogenAff do
36− sfio <- runUI SearchField.component unit searchField
37− srio <- runUI (SearchResults.mkComponent pageContents) unit searchResults
38− sfio.subscribe $
39− Coroutine.consumer \message ->
40− srio.query $ H.tell $ SearchResults.SearchFieldMessage message
41−
42−insertStyle :: Document.Document -> Effect Unit
43−insertStyle doc = do
44− let styleContents = """
45− .top-banner__actions {
46− width: 10%;
47− }
48− .load_more {
49− margin-top:2em;
50− }
51− .load_more p {
52− font-style:italic
53− }
54− .load_more a {
55− background:#eee;
56− padding:0.4em
57− }
58− #load-more-link {
59− cursor: pointer;
60− }
61− .result {
62− font-size: 1.25em;
63− }
64− """
65− mbHead <-
66− ParentNode.querySelector (wrap "head") (Document.toParentNode doc)
67−
68− whenJust mbHead \head -> do
69−
70− contents <- Document.createTextNode styleContents doc
71− style <- Document.createElement "style" doc
72− void $ Node.appendChild (Text.toNode contents) (Element.toNode style)
73− void $ Node.appendChild (Element.toNode style) (Element.toNode head)
74−
75−getContainers :: Document.Document -> Effect (Maybe { searchField :: HTML.HTMLElement
76− , searchResults :: HTML.HTMLElement
77− , pageContents :: Element.Element })
78−getContainers doc = do
79− let docPN = Document.toParentNode doc
80− mbBanner <-
81− ParentNode.querySelector (wrap ".top-banner > .container") docPN
82− mbEverything <-
83− ParentNode.querySelector (wrap ".everything-except-footer") docPN
84− mbContainer <-
85− ParentNode.querySelector (wrap ".everything-except-footer > .container") docPN
86− case mbBanner, mbEverything, mbContainer of
87− Just banner, Just everything, Just pageContents -> do
88− search <- Document.createElement "div" doc
89− void $ Node.appendChild (Element.toNode search) (Element.toNode banner)
90− pure $ fromElement search >>= \searchField ->
91− fromElement everything >>= \searchResults ->
92− pure { searchField, searchResults, pageContents }
93− _, _, _ ->
94− pure Nothing
src/Spago/Search/App/SearchField.pursdeleted−82
@@ -1,82 +0,0 @@
1−module Spago.Search.App.SearchField where
2−
3−import Prelude
4−
5−import CSS hiding (render, map)
6−import Data.Maybe (Maybe(..))
7−import Effect.Aff (Aff)
8−import Halogen as H
9−import Halogen.HTML as HH
10−import Halogen.HTML.CSS as HS
11−import Halogen.HTML.Events as HE
12−import Halogen.HTML.Properties as HP
13−
14−type State = { input :: String }
15−
16−data Action
17− = InputAction String
18− | FocusChanged Boolean
19−
20−data Message
21− = InputUpdated String
22− | Focused
23− | LostFocus
24−
25−component :: forall q i. H.Component HH.HTML q i Message Aff
26−component =
27− H.mkComponent
28− { initialState
29− , render
30− , eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
31− }
32−
33−initialState :: forall i. i -> State
34−initialState _ = { input: "" }
35−
36−handleAction :: forall m. Action -> H.HalogenM State Action () Message m Unit
37−handleAction = case _ of
38− InputAction input -> do
39− H.modify_ $ const { input }
40− H.raise $ InputUpdated input
41− FocusChanged status -> do
42− H.raise
43− if status
44− then Focused
45− else LostFocus
46−
47−render :: forall m. State -> H.ComponentHTML Action () m
48−render state =
49− HH.div
50−
51− [ HS.style do
52− float floatLeft
53− lineHeight (px 90.0)
54− marginBottom (px 0.0)
55− marginLeft (em 2.0)
56− width (pct 30.0)
57− ]
58−
59− [ HH.input
60− [ HP.value state.input
61− , HP.placeholder "Search for definitions"
62− , HP.type_ HP.InputText
63− , HE.onValueInput (Just <<< InputAction)
64− , HE.onFocusIn $ const $ Just $ FocusChanged true
65− , HE.onFocusOut $ const $ Just $ FocusChanged false
66− , HS.style do
67−
68− let pursuitColor = rgb 0x1d 0x22 0x2d
69− rds = px 3.0
70−
71− border solid (px 1.0) pursuitColor
72− borderRadius rds rds rds rds
73− color pursuitColor
74− fontWeight $ weight 300.0
75− lineHeight $ em 2.0
76− paddingLeft $ em 0.8
77− paddingRight $ em 0.21
78− paddingTop $ em 0.512
79− paddingBottom $ em 0.512
80− width $ pct 100.0
81− ]
82− ]
src/Spago/Search/App/SearchResults.pursdeleted−357
@@ -1,357 +0,0 @@
1−module Spago.Search.App.SearchResults where
2−
3−import Prelude
4−import Spago.Search.Index
5−import Spago.Search.TypeDecoder
6−
7−import CSS (textWhitespace, whitespacePreWrap)
8−import Data.Array as Array
9−import Data.Either (Either(..))
10−import Data.List (List(..), (:))
11−import Data.List as List
12−import Data.Maybe (Maybe(..), isNothing, isJust)
13−import Data.Newtype (unwrap, wrap)
14−import Data.Search.Trie as Trie
15−import Data.String (length) as String
16−import Data.String.CodeUnits (toCharArray, stripSuffix) as String
17−import Data.String.Common (toLower) as String
18−import Data.String.Pattern (Pattern(..)) as String
19−import Effect.Aff (Aff)
20−import Effect.Console (error)
21−import Halogen as H
22−import Halogen.HTML as HH
23−import Halogen.HTML.CSS as HS
24−import Halogen.HTML.Events as HE
25−import Halogen.HTML.Properties as HP
26−import Spago.Search.App.SearchField (Message(..))
27−import Spago.Search.DocsJson (loadDeclarations)
28−import Spago.Search.Extra (whenJust)
29−import Web.DOM.Element (Element)
30−import Web.DOM.Element as Element
31−import Web.HTML as HTML
32−import Web.HTML.Location as Location
33−import Web.HTML.Window as Window
34−
35−type State = { mbIndex :: Maybe SearchIndex
36− , results :: Array SearchResult
37− , input :: String
38− , contents :: Element
39− , resultsCount :: Int
40− }
41−
42−data Query a
43− = SearchFieldMessage Message a
44−
45−data Action
46− = SearchResultClicked String
47− | ShowMore
48−
49−mkComponent :: forall o i. Element -> H.Component HH.HTML Query i o Aff
50−mkComponent contents =
51− H.mkComponent
52− { initialState: const { mbIndex: Nothing
53− , results: []
54− , input: ""
55− , contents
56− , resultsCount: 25
57− }
58− , render
59− , eval: H.mkEval $ H.defaultEval { handleQuery = handleQuery
60− , handleAction = handleAction }
61− }
62−
63−handleQuery :: forall o a. Query a -> H.HalogenM State Action () o Aff (Maybe a)
64−handleQuery (SearchFieldMessage Focused next) = do
65− state <- H.get
66− when (isNothing state.mbIndex) do
67− eiDeclarations <- H.liftAff $ loadDeclarations "../spago-search-index.js"
68− case eiDeclarations of
69− Left err -> do
70− H.liftEffect do
71− error $ "spago-search: couldn't load search index: " <> err
72− Right declarations -> do
73− H.modify_ (_ { mbIndex = Just $ mkSearchIndex declarations })
74− pure Nothing
75−handleQuery (SearchFieldMessage LostFocus next) = do
76− pure Nothing
77−handleQuery (SearchFieldMessage (InputUpdated input) next) = do
78− H.modify_ (_ { input = input })
79− if String.length input < 2
80− then do
81− showPageContents
82− H.modify_ (_ { results = [] })
83− else do
84− hidePageContents
85− state <- H.get
86− whenJust (unwrap <$> state.mbIndex) \index -> do
87− let path = List.fromFoldable $
88− String.toCharArray $
89− String.toLower input
90− H.modify_ (_ { results = Array.concat $
91− List.toUnfoldable $
92− map List.toUnfoldable $
93− Trie.queryValues path $
94− index
95− , resultsCount = 25 })
96− pure Nothing
97−
98−handleAction :: forall o. Action -> H.HalogenM State Action () o Aff Unit
99−handleAction = case _ of
100− ShowMore -> do
101− H.modify_ (\state -> state { resultsCount = state.resultsCount + 25 })
102− SearchResultClicked moduleName -> do
103− -- Decide if we are going to load a new page or to jump to a hash on the
104− -- current page. In the latter case, hide search results and show the main
105− -- page contents.
106− onThisPage <- H.liftEffect do
107− window <- HTML.window
108− location <- Window.location window
109− pathname <- Location.pathname location
110− pure $ isJust $
111− String.stripSuffix (String.Pattern $ moduleName <> ".html") pathname
112−
113− when onThisPage do
114− showPageContents
115− H.modify_ (_ { input = "" })
116−
117−showPageContents :: forall o. H.HalogenM State Action () o Aff Unit
118−showPageContents = do
119− state <- H.get
120− H.liftEffect do
121− Element.removeAttribute "style" state.contents
122−
123−hidePageContents :: forall o. H.HalogenM State Action () o Aff Unit
124−hidePageContents = do
125− state <- H.get
126− H.liftEffect do
127− Element.setAttribute "style" "display: none" state.contents
128−
129−render :: forall m. State -> H.ComponentHTML Action () m
130−render { mbIndex: Nothing } =
131− HH.div_ []
132−render { input: "" } =
133− HH.div_ []
134−render state =
135− HH.div [ HP.classes [ wrap "container", wrap "clearfix" ] ] $
136− pure $
137−
138− HH.div [ HP.classes [ wrap "col", wrap "col--main" ] ] $
139−
140− [ HH.h1_ [ HH.text "Search results" ] ] <>
141−
142− if Array.null state.results
143− then
144− [ HH.div [ HP.classes [ wrap "result", wrap "result--empty" ] ]
145− [ HH.text "Your search for "
146− , HH.strong_ [ HH.text state.input ]
147− , HH.text " did not yield any results."
148− ]
149− ]
150− else
151− let selectedResults = Array.take state.resultsCount state.results in
152− [ HH.div [ HP.classes [ wrap "result" ] ]
153− [ HH.text "Found "
154− , HH.strong_ [ HH.text $ show $ Array.length state.results ]
155− , HH.text " definitions"
156− ]
157−
158− , HH.div [ HP.id_ "spage-search-results-container" ] $
159− Array.concat $ selectedResults <#> renderResult
160−
161− , HH.div [ HP.class_ (wrap "load_more"), HP.id_ "load-more" ]
162− [ if Array.length selectedResults < Array.length state.results
163− then HH.a [ HP.id_ "load-more-link"
164− , HE.onClick $ const $ Just ShowMore ]
165− [ HH.text "Show more results" ]
166− else HH.p_
167− [ HH.text "No further results." ]
168− ]
169− ]
170−
171−
172−renderSummary :: forall a b. String -> HH.HTML b a
173−renderSummary text =
174− HH.div [ HP.id_ "spago-search-summary" ]
175− [ HH.text text
176− ]
177−
178−renderResult :: forall a. SearchResult -> Array (HH.HTML a Action)
179−renderResult = unwrap >>> \result ->
180− [ HH.div [ HP.class_ (wrap "result") ]
181− [ HH.h3 [ HP.class_ (wrap "result__title") ]
182− [ HH.a [ HP.class_ (wrap "result__link")
183− , HE.onClick $ const $ Just $ SearchResultClicked result.moduleName
184− , HP.href $
185− result.moduleName <> ".html#" <>
186− result.hashAnchor <> ":" <> result.name
187− ]
188− [ HH.text result.name ]
189− ]
190− ]
191−
192− , HH.div [ HP.class_ (wrap "result__body") ] $
193− renderResultType result <>
194−
195− case result.comments of
196− Just comments -> [ HH.pre [ HS.style do
197− textWhitespace whitespacePreWrap ]
198− [ HH.text comments ]
199− ]
200− Nothing -> [ ]
201−
202− , HH.div [ HP.class_ (wrap "result__actions") ]
203− [ HH.span [ HP.class_ (wrap "result__actions__item") ]
204− [ HH.span [ HP.classes [ wrap "badge"
205− , wrap "badge--package"
206− ]
207− , HP.title "Package"
208− ]
209− [ HH.text "P"
210− ]
211− , HH.text result.packageName
212− ]
213−
214− , HH.span [ HP.class_ (wrap "result__actions__item") ]
215− [ HH.span [ HP.classes [ wrap "badge"
216− , wrap "badge--module"
217− ]
218− , HP.title "Module"
219− ]
220− [ HH.text "M"
221− ]
222− , HH.text result.moduleName
223− ]
224− ]
225− ]
226−
227−renderResultType
228− :: forall a rest
229− . { info :: ResultInfo
230− , name :: String
231− | rest
232− }
233− -> Array (HH.HTML a Action)
234−renderResultType = \result ->
235− case result.info of
236− ValueResult {type:ty} ->
237− [ HH.pre [ HP.class_ (wrap "result__signature") ]
238− [ HH.code_ [ HH.text (result.name <> " :: ")
239− , renderType ty ]
240− ]
241− ]
242− _ -> []
243−
244−renderType :: forall a. Type -> HH.HTML a Action
245−renderType = case _ of
246− TypeVar str -> HH.text str
247− TypeLevelString str -> HH.text str
248− TypeWildcard -> HH.text "_"
249− TypeConstructor qname -> renderQualifiedName qname
250− TypeOp qname -> renderQualifiedName qname
251−
252− TypeApp (TypeApp (TypeConstructor
253− (QualifiedName { moduleName: ["Prim"]
254− , name: "Function" })) t1) t2 ->
255− HH.span_ [ renderType t1
256− , HH.text " -> "
257− , renderType t2
258− ]
259−
260− TypeApp (TypeConstructor (QualifiedName { moduleName: ["Prim"]
261− , name: "Record" }))
262− record -> renderRow record
263−
264− TypeApp t1 t2 ->
265− HH.span_ [ renderType t1
266− , HH.text " "
267− , renderType t2
268− ]
269−
270− ty@(ForAll _ _ _) ->
271− let foralls = joinForalls ty in
272− HH.span_ $
273− [ HH.text "forall" ] <>
274− ( Array.fromFoldable foralls.vars <#>
275− \ { var, mbKind } ->
276− case mbKind of
277− Nothing -> HH.text (" " <> var)
278− Just kind ->
279− HH.span_ [ HH.text ("(" <> var <> " :: ")
280− , renderKind kind
281− , HH.text ")"
282− ]
283− ) <>
284− [ HH.text ". ", renderType foralls.ty ]
285−
286− ConstrainedType cnstr ty ->
287− HH.span_
288− [ renderConstraint cnstr
289− , HH.text " => "
290− , renderType ty
291− ]
292−
293− REmpty -> HH.text "{}"
294− ty@(RCons _ _ _) -> renderRow ty
295−
296− BinaryNoParensType t1 t2 t3 ->
297− HH.span_
298− [ renderType t1
299− , renderType t2
300− , renderType t3
301− ]
302− ParensInType ty ->
303− HH.span_
304− [ HH.text "("
305− , renderType ty
306− , HH.text ")"
307− ]
308−
309−joinForalls :: Type -> { vars :: List.List { var :: String, mbKind :: Maybe Kind }
310− , ty :: Type }
311−joinForalls = go Nil
312− where
313− go acc (ForAll var ty mbKind) =
314− go ({ var, mbKind } : acc) ty
315− go acc ty = { vars: acc, ty }
316−
317−renderRow :: forall a. Type -> HH.HTML a Action
318−renderRow ty =
319− let rows = joinRows ty in
320− HH.span_ $
321− [ HH.text "{ " ] <>
322− ( Array.intercalate [ HH.text ", " ] $ Array.fromFoldable rows <#>
323− \entry ->
324− [ HH.span_ [ HH.text $ entry.row <> " :: "
325− , renderType entry.ty ] ]
326− ) <>
327− [ HH.text " }" ]
328−
329−joinRows :: Type -> List { row :: String
330− , ty :: Type
331− }
332−joinRows = go Nil
333− where
334− go acc (RCons row ty rest) =
335− go ({ row, ty } : acc) rest
336− go acc _ = List.reverse acc
337−
338−
339−htmlSingleton :: forall t406 t407. HH.HTML t407 t406 -> HH.HTML t407 t406
340−htmlSingleton x = HH.span_ [ x ]
341−
342−renderConstraint :: forall a. Constraint -> HH.HTML a Action
343−renderConstraint (Constraint { constraintClass, constraintArgs }) =
344− HH.span_ $
345− [ renderQualifiedName constraintClass, HH.text " " ] <>
346− Array.intercalate [ HH.text " " ] (constraintArgs <#> \ty -> [ renderType ty ])
347−
348−
349−renderQualifiedName :: forall a. QualifiedName -> HH.HTML a Action
350−renderQualifiedName (QualifiedName { moduleName, name })
351− = HH.text name
352−
353−renderKind :: forall a. Kind -> HH.HTML a Action
354−renderKind = case _ of
355− Row k1 -> HH.span_ [ HH.text "#", renderKind k1 ]
356− FunKind k1 k2 -> HH.span_ [ renderKind k1, renderKind k2 ]
357− NamedKind qname -> renderQualifiedName qname
src/Spago/Search/DocsJson.jsdeleted−24
@@ -1,24 +0,0 @@
1−/* global exports */
2−
3−exports.loadDeclarations_ = function (url) {
4− return function () {
5− if (typeof window.spagoSearchIndex == 'undefined') {
6− return new Promise(function(resolve, reject) {
7− var script = document.createElement('script');
8− script.type = 'text/javascript';
9− script.src = url;
10− script.addEventListener('load', function () {
11− if (typeof window.spagoSearchIndex == 'undefined') {
12− reject();
13− } else {
14− resolve(window.spagoSearchIndex);
15− }
16− });
17− script.addEventListener('error', reject);
18− document.body.appendChild(script);
19− });
20− } else {
21− return Promise.resolve(window.spagoSearchIndex);
22− }
23− };
24−};
src/Spago/Search/DocsJson.pursdeleted−190
@@ -1,190 +0,0 @@
1−module Spago.Search.DocsJson where
2−
3−import Prelude
4−
5−import Spago.Search.TypeDecoder
6−
7−import Control.Promise (Promise, toAffE)
8−import Data.Argonaut.Core (Json, fromString, stringify, toString)
9−import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:), (.:?))
10−import Data.Argonaut.Encode (class EncodeJson, encodeJson)
11−import Data.Either (Either(..))
12−import Data.Generic.Rep (class Generic)
13−import Data.Generic.Rep.Show (genericShow)
14−import Data.Maybe (Maybe(..))
15−import Data.Newtype (class Newtype, unwrap)
16−import Effect (Effect)
17−import Effect.Aff (Aff)
18−
19−-- See `src/Language/Purescript/Docs/Types.hs`
20−data DeclType
21− = DeclValue
22− | DeclData
23− | DeclExternData
24− | DeclTypeSynonym
25− | DeclTypeClass
26− | DeclAlias
27− | DeclExternKind
28−
29−derive instance eqDeclType :: Eq DeclType
30−derive instance genericDeclType :: Generic DeclType _
31−
32−instance showDeclType :: Show DeclType where
33− show = genericShow
34−
35−instance encodeJsonDeclType :: EncodeJson DeclType where
36− encodeJson = fromString <<< case _ of
37− DeclValue -> "value"
38− DeclData -> "data"
39− DeclExternData -> "externData"
40− DeclTypeSynonym -> "typeSynonym"
41− DeclTypeClass -> "typeClass"
42− DeclAlias -> "alias"
43− DeclExternKind -> "kind"
44−
45−instance decodeJsonDeclType :: DecodeJson DeclType where
46− decodeJson json =
47− case toString json of
48− Just string ->
49− case string of
50− "value" -> Right DeclValue
51− "data" -> Right DeclData
52− "externData" -> Right DeclExternData
53− "typeSynonym" -> Right DeclTypeSynonym
54− "typeClass" -> Right DeclTypeClass
55− "alias" -> Right DeclAlias
56− "kind" -> Right DeclExternKind
57− _ -> Left $ "Couldn't decode DeclType: " <> string
58− Nothing -> Left $ "Couldn't decode DeclType: " <> stringify json
59−
60−data ChildDeclType
61− = ChildDeclInstance
62− | ChildDeclDataConstructor
63− | ChildDeclTypeClassMember
64−
65−derive instance eqChildDeclType :: Eq ChildDeclType
66−derive instance genericChildDeclType :: Generic ChildDeclType _
67−
68−instance showChildDeclType :: Show ChildDeclType where
69− show = genericShow
70−
71−instance encodeJsonChildDeclType :: EncodeJson ChildDeclType where
72− encodeJson = fromString <<< case _ of
73− ChildDeclInstance -> "instance"
74− ChildDeclDataConstructor -> "dataConstructor"
75− ChildDeclTypeClassMember -> "typeClassMember"
76−
77−instance decodeJsonChildDeclType :: DecodeJson ChildDeclType where
78− decodeJson json =
79− case toString json of
80− Just string ->
81− case string of
82− "instance" -> Right ChildDeclInstance
83− "dataConstructor" -> Right ChildDeclDataConstructor
84− "typeClassMember" -> Right ChildDeclTypeClassMember
85− _ -> Left $ "Couldn't decode ChildDeclType: " <> string
86− Nothing -> Left $ "Couldn't decode ChildDeclType: " <> stringify json
87−
88−newtype IndexEntry
89− = IndexEntry { title :: String
90− , comments :: Maybe String
91− , info :: { declType :: DeclType
92− , kind :: Maybe Kind
93− , typeArguments :: Maybe (Array TypeArgument)
94− , type :: Maybe Type
95− , superclasses :: Maybe (Array Constraint)
96− , arguments :: Maybe (Array TypeArgument)
97− , fundeps :: Maybe FunDeps
98− }
99− , sourceSpan :: { start :: Array Int
100− , end :: Array Int
101− , name :: String
102− }
103− , children :: Array ChildIndexEntry
104− }
105−
106−derive instance eqIndexEntry :: Eq IndexEntry
107−derive instance genericIndexEntry :: Generic IndexEntry _
108−derive instance newtypeIndexEntry :: Newtype IndexEntry _
109−
110−instance showIndexEntry :: Show IndexEntry where
111− show = genericShow
112−
113−instance decodeJsonIndexEntry :: DecodeJson IndexEntry where
114− decodeJson json = IndexEntry <$> do
115− handle <- decodeJson json
116− title <- handle .: "title"
117− comments <- handle .:? "comments"
118− children <- handle .: "children"
119− info <- handle .: "info" >>= \info -> do
120− ty <- info .:? "type"
121− kind <- info .:? "kind"
122− typeArguments <- info .:? "typeArguments"
123− arguments <- info .:? "arguments"
124− superclasses <- info .:? "superclasses"
125− fundeps <- info .:? "fundeps"
126− declType <- info .: "declType"
127− pure { type: ty, kind, declType, typeArguments, superclasses, fundeps, arguments }
128− sourceSpan <- handle .: "sourceSpan"
129− pure { title, comments, info, sourceSpan, children }
130−
131−instance encodeJsonIndexEntry :: EncodeJson IndexEntry where
132− encodeJson = encodeJson <<< unwrap
133−
134−newtype ChildIndexEntry
135− = ChildIndexEntry { title :: String
136− , comments :: Maybe String
137− , info :: { declType :: ChildDeclType
138− , arguments :: Maybe (Array Type)
139− , type :: Maybe Type
140− }
141− , mbSourceSpan :: Maybe { start :: Array Int
142− , end :: Array Int
143− , name :: String
144− }
145− }
146−
147−derive instance eqChildIndexEntry :: Eq ChildIndexEntry
148−derive instance genericChildIndexEntry :: Generic ChildIndexEntry _
149−derive instance newtypeChildIndexEntry :: Newtype ChildIndexEntry _
150−instance showChildIndexEntry :: Show ChildIndexEntry where
151− show = genericShow
152−
153−instance decodeJsonChildIndexEntry :: DecodeJson ChildIndexEntry where
154− decodeJson json = ChildIndexEntry <$> do
155− handle <- decodeJson json
156− title <- handle .: "title"
157− comments <- handle .:? "comments"
158− info <- handle .: "info" >>= \info -> do
159− arguments <- info .:? "arguments"
160− ty <- info .:? "type"
161− declType <- info .: "declType"
162− pure { arguments, declType, type: ty }
163− mbSourceSpan <- handle .:? "sourceSpan"
164− pure { title, comments, info, mbSourceSpan }
165−
166−instance encodeJsonChildIndexEntry :: EncodeJson ChildIndexEntry where
167− encodeJson = encodeJson <<< unwrap
168−
169−newtype Declarations
170− = Declarations { name :: String
171− , declarations :: Array IndexEntry
172− }
173−
174−derive instance eqDeclarations :: Eq Declarations
175−derive instance genericDeclarations :: Generic Declarations _
176−derive instance newtypeDeclarations :: Newtype Declarations _
177−
178−instance showDeclarations :: Show Declarations where
179− show = genericShow
180−
181−instance decodeJsonDeclarations :: DecodeJson Declarations where
182− decodeJson json = Declarations <$> decodeJson json
183−
184−instance encodeJsonDeclarations :: EncodeJson Declarations where
185− encodeJson = encodeJson <<< unwrap
186−
187−loadDeclarations :: String -> Aff (Either String (Array Declarations))
188−loadDeclarations string = decodeJson <$> toAffE (loadDeclarations_ string)
189−
190−foreign import loadDeclarations_ :: String -> Effect (Promise Json)
src/Spago/Search/Extra.pursdeleted−9
@@ -1,9 +0,0 @@
1−module Spago.Search.Extra where
2−
3−import Prelude
4−
5−import Data.Maybe (Maybe(..))
6−
7−whenJust :: forall a m. Monad m => Maybe a -> (a -> m Unit) -> m Unit
8−whenJust (Just a) f = f a
9−whenJust _ _ = pure unit
src/Spago/Search/Index.pursdeleted−231
@@ -1,231 +0,0 @@
1−module Spago.Search.Index where
2−
3−import Prelude
4−
5−import Spago.Search.TypeDecoder (Constraint, FunDeps, Kind, Type, TypeArgument)
6−import Spago.Search.DocsJson (ChildDeclType(..), ChildIndexEntry(..), DeclType(..), Declarations(..), IndexEntry(..))
7−
8−import Control.Alt ((<|>))
9−import Data.Array ((!!))
10−import Data.Foldable (foldr)
11−import Data.List (List, (:))
12−import Data.List as List
13−import Data.Maybe (Maybe(..), fromMaybe)
14−import Data.Newtype (class Newtype)
15−import Data.Search.Trie (Trie, alter)
16−import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray)
17−import Data.String.Common (toLower)
18−import Data.String.Common as String
19−import Data.String.Pattern (Pattern(..))
20−
21−newtype SearchIndex = SearchIndex (Trie Char (List SearchResult))
22−
23−derive instance newtypeSearchIndex :: Newtype SearchIndex _
24−
25−data ResultInfo
26− = DataResult { typeArguments :: Array TypeArgument }
27− | ExternDataResult { kind :: Kind }
28− | TypeSynonymResult { type :: Type }
29− | DataConstructorResult { arguments :: Array Type }
30− | TypeClassMemberResult { type :: Type }
31− | TypeClassResult { fundeps :: FunDeps
32− , arguments :: Array TypeArgument
33− , superclasses :: Array Constraint }
34− | ValueResult { type :: Type }
35− | ValueAliasResult
36− | TypeAliasResult
37− | ExternKindResult
38−
39−newtype SearchResult
40− = SearchResult { name :: String
41− , comments :: Maybe String
42− , hashAnchor :: String
43− , moduleName :: String
44− , packageName :: String
45− , sourceSpan :: Maybe { start :: Array Int
46− , end :: Array Int
47− , name :: String
48− }
49− , info :: ResultInfo
50− }
51−
52−derive instance newtypeSearchResult :: Newtype SearchResult _
53−
54−mkSearchIndex :: Array Declarations -> SearchIndex
55−mkSearchIndex = SearchIndex <<< foldr insertDeclarations mempty
56−
57−insertDeclarations
58− :: Declarations
59− -> Trie Char (List SearchResult)
60− -> Trie Char (List SearchResult)
61−insertDeclarations (Declarations { name, declarations }) trie
62− = foldr (insertIndexEntry name) trie declarations
63−
64−insertIndexEntry
65− :: String
66− -> IndexEntry
67− -> Trie Char (List SearchResult)
68− -> Trie Char (List SearchResult)
69−insertIndexEntry moduleName entry@(IndexEntry { title }) trie
70− = foldr insertSearchResult trie (resultsForEntry moduleName entry)
71−
72−insertSearchResult
73− :: { path :: String
74− , result :: SearchResult
75− }
76− -> Trie Char (List SearchResult)
77− -> Trie Char (List SearchResult)
78−insertSearchResult { path, result } trie =
79− let path' = List.fromFoldable $ toCharArray $ toLower path in
80− alter path' updateResults trie
81− where
82− updateResults mbOldResults =
83− case mbOldResults of
84− Just oldResults ->
85− Just $ result : oldResults
86− Nothing ->
87− Just $ List.singleton result
88−
89−resultsForEntry
90− :: String
91− -> IndexEntry
92− -> List { path :: String
93− , result :: SearchResult
94− }
95−resultsForEntry moduleName ie@(IndexEntry entry@{info, title, sourceSpan, comments, children}) =
96− let { name, declLevel } = getLevelAndName info.declType title
97− packageName = extractPackageName sourceSpan.name
98− in case mkInfo declLevel ie of
99− Nothing -> mempty
100− Just info' -> (
101− List.singleton $
102− { path: name
103− , result: SearchResult { name: title
104− , comments
105− , hashAnchor: declLevelToHashAnchor declLevel
106− , moduleName
107− , sourceSpan: Just sourceSpan
108− , packageName
109− , info: info'
110− }
111− }
112− ) <>
113− ( List.fromFoldable children >>=
114− resultsForChildIndexEntry packageName moduleName
115− )
116−
117−mkInfo :: DeclLevel -> IndexEntry -> Maybe ResultInfo
118−mkInfo declLevel (IndexEntry { info, title }) =
119− case info.declType of
120−
121− DeclValue ->
122− info.type <#>
123− \ty -> ValueResult { type: ty }
124−
125− DeclData ->
126− info.typeArguments <#>
127− \typeArguments -> DataResult { typeArguments }
128−
129− DeclExternData ->
130− info.kind <#>
131− \kind -> ExternDataResult { kind }
132−
133− DeclTypeSynonym ->
134− info.type <#>
135− \ty -> TypeSynonymResult { type: ty }
136−
137− DeclTypeClass ->
138− case info.fundeps, info.arguments, info.superclasses of
139− Just fundeps, Just arguments, Just superclasses ->
140− Just $ TypeClassResult { fundeps, arguments, superclasses }
141− _, _, _ -> Nothing
142−
143− DeclAlias ->
144− case declLevel of
145− TypeLevel -> Just TypeAliasResult
146− ValueLevel -> Just ValueAliasResult
147− _ -> Nothing
148−
149− DeclExternKind ->
150− Just ExternKindResult
151−
152−-- | Level of a declaration, used to determine which URI hash anchor to use in
153−-- | links ("v", "t" or "k").
154−data DeclLevel = ValueLevel | TypeLevel | KindLevel
155−
156−declLevelToHashAnchor :: DeclLevel -> String
157−declLevelToHashAnchor = case _ of
158− ValueLevel -> "v"
159− TypeLevel -> "t"
160− KindLevel -> "k"
161−
162−getLevelAndName
163− :: DeclType
164− -> String
165− -> { declLevel :: DeclLevel
166− , name :: String
167− }
168−getLevelAndName DeclValue name = { name, declLevel: ValueLevel }
169−getLevelAndName DeclData name = { name, declLevel: TypeLevel }
170−getLevelAndName DeclTypeSynonym name = { name, declLevel: TypeLevel }
171−getLevelAndName DeclTypeClass name = { name, declLevel: ValueLevel }
172−-- "declType": "alias" does not specify the level of the declaration.
173−-- But for type aliases, name of the declaration is always wrapped into
174−-- "type (" and ")".
175−getLevelAndName DeclAlias title =
176− fromMaybe (withAnchor ValueLevel title) $
177− (withAnchor ValueLevel <$>
178− (stripPrefix (Pattern "(") >=>
179− stripSuffix (Pattern ")")) title) <|>
180− (withAnchor TypeLevel <$>
181− (stripPrefix (Pattern "type (") >=>
182− stripSuffix (Pattern ")")) title)
183− where
184− withAnchor declLevel name = { declLevel, name }
185−getLevelAndName DeclExternData name = { name, declLevel: TypeLevel }
186−getLevelAndName DeclExternKind name = { name, declLevel: KindLevel }
187−
188−-- | Extract package name from `sourceSpan.name`, which contains path to
189−-- | the source file.
190−extractPackageName :: String -> String
191−extractPackageName name =
192− let chunks = String.split (Pattern "/") name in
193− fromMaybe "<unknown>" $
194− chunks !! 0 >>= \dir ->
195− -- TODO: is it safe to assume that directory name is ".spago"?
196− if dir == ".spago" then
197− chunks !! 1
198− else
199− Just "<local package>"
200−
201−resultsForChildIndexEntry
202− :: String
203− -> String
204− -> ChildIndexEntry
205− -> List { path :: String, result :: SearchResult }
206−resultsForChildIndexEntry packageName moduleName
207− cie@(ChildIndexEntry { title, info, comments, mbSourceSpan }) =
208− case mkChildInfo cie of
209− Nothing -> mempty
210− Just resultInfo ->
211− { path: title
212− , result: SearchResult { name: title
213− , comments
214− , hashAnchor: "v"
215− , moduleName
216− , sourceSpan: mbSourceSpan
217− , packageName
218− , info: resultInfo
219− }
220− } # List.singleton
221−
222−mkChildInfo :: ChildIndexEntry -> Maybe ResultInfo
223−mkChildInfo (ChildIndexEntry { info } ) =
224− case info.declType of
225− ChildDeclDataConstructor ->
226− info.arguments <#>
227− \arguments -> DataConstructorResult { arguments }
228− ChildDeclTypeClassMember ->
229− info.type <#>
230− \ty -> TypeClassMemberResult { type: ty }
231− ChildDeclInstance -> Nothing
src/Spago/Search/IndexBuilder.pursdeleted−99
@@ -1,99 +0,0 @@
1−module Spago.Seach.IndexBuilder where
2−
3−import Prelude
4−
5−import Spago.Search.DocsJson (Declarations)
6−import Spago.Search.Index (mkSearchIndex)
7−
8−import Data.Argonaut.Core (stringify)
9−import Data.Argonaut.Decode (decodeJson)
10−import Data.Argonaut.Encode (encodeJson)
11−import Data.Argonaut.Parser (jsonParser)
12−import Data.Array as Array
13−import Data.Either (hush, isLeft)
14−import Data.Maybe (Maybe(..))
15−import Data.Newtype (unwrap)
16−import Data.Search.Trie as Trie
17−import Data.String.CodePoints (contains) as String
18−import Data.String.Common (replace) as String
19−import Data.String.Pattern (Pattern(..), Replacement(..))
20−import Data.String.Pattern (Pattern(..)) as String
21−import Data.Traversable (for, for_)
22−import Effect (Effect)
23−import Effect.Aff (Aff, launchAff_)
24−import Effect.Class (liftEffect)
25−import Effect.Console (log)
26−import Node.Encoding (Encoding(UTF8))
27−import Node.FS.Aff (exists, readTextFile, readdir, writeTextFile)
28−import Node.Process as Process
29−
30−collectDeclarations :: Array String -> Aff (Array Declarations)
31−collectDeclarations paths = do
32− mbs <- for paths \moduleName -> do
33− let jsonFile = "output/" <> moduleName <> "/docs.json"
34− doesExist <- exists jsonFile
35−
36− if doesExist then do
37− contents <- readTextFile UTF8 jsonFile
38− let mbResult = jsonParser contents >>= decodeJson
39−
40− when (isLeft mbResult) do
41− liftEffect $ log $
42− "Index build failed for module " <>
43− moduleName <>
44− ": " <> show mbResult
45−
46− pure $ hush mbResult
47− else do
48− liftEffect $ do
49− log $
50− "Couldn't find docs.json for " <> moduleName
51− pure Nothing
52− pure $ Array.catMaybes mbs
53−
54−writeDeclarations :: Array Declarations -> Aff Unit
55−writeDeclarations decls = do
56− let filename = "generated-docs/spago-search-index.js"
57− dirname = "generated-docs/"
58−
59− whenM (not <$> exists dirname) $
60− liftEffect $ do
61− log "Run `spago docs` first!"
62− Process.exit 1
63−
64− let header =
65− "// This file was generated by spago.\n" <>
66− "window.spagoSearchIndex = "
67−
68− writeTextFile UTF8 filename (header <> (stringify $ encodeJson decls))
69−
70−patchDocs :: Aff Unit
71−patchDocs = do
72− let dirname = "generated-docs/"
73− let patch = "<!-- Spago search index. -->" <>
74− "<script type=\"text/javascript\" src=\"../spago-search-app.js\"></script>" <>
75− "</body>"
76−
77− whenM (not <$> exists dirname) $
78− liftEffect $ do
79− log "Run `spago docs` first!"
80− Process.exit 1
81−
82− paths <- readdir (dirname <> "html")
83− for_ paths \file -> do
84− let path = dirname <> "html/" <> file
85− contents <- readTextFile UTF8 path
86− when (not $ String.contains (String.Pattern patch) contents) do
87− let patched = String.replace (Pattern "</body>") (Replacement patch) contents
88− writeTextFile UTF8 path patched
89−
90−main :: Effect Unit
91−main = do
92− launchAff_ do
93− paths <- readdir "output"
94− declarations <- collectDeclarations paths
95− liftEffect $ log $ "Found " <> show (Array.length declarations) <> " modules"
96− writeDeclarations declarations
97− patchDocs
98− let index = mkSearchIndex declarations
99− liftEffect $ log $ "Loaded " <> show (Trie.size $ unwrap index) <> " definitions"
src/Spago/Search/TypeQuery.pursdeleted−119
@@ -1,119 +0,0 @@
1−module Spago.Search.TypeQuery where
2−
3−import Prelude
4−
5−import Control.Alt ((<|>))
6−import Data.Array as Array
7−import Data.Either (Either)
8−import Data.Foldable (foldl)
9−import Data.Generic.Rep (class Generic)
10−import Data.Generic.Rep.Show (genericShow)
11−import Data.List (List, some, (:))
12−import Data.List as List
13−import Data.List.NonEmpty (NonEmptyList, cons', uncons)
14−import Data.Maybe (Maybe(..))
15−import Data.String.CodeUnits (fromCharArray)
16−import Data.Tuple (Tuple(..))
17−import Text.Parsing.StringParser (ParseError, Parser, runParser, try)
18−import Text.Parsing.StringParser.CodePoints (alphaNum, anyLetter, char, eof, lowerCaseChar, skipSpaces, string, upperCaseChar)
19−import Text.Parsing.StringParser.Combinators (fix, sepBy, sepBy1, sepEndBy, sepEndBy1)
20−
21−data TypeQuery
22− = QAny String
23− | QConcrete String
24− | QFun TypeQuery TypeQuery
25− | QApp TypeQuery TypeQuery
26− | QForAll (NonEmptyList String) TypeQuery
27− | QConstraint String (List String) TypeQuery
28− | QRow (List (Tuple String TypeQuery))
29− | QEmpty
30−
31−derive instance eqTypeQuery :: Eq TypeQuery
32−derive instance genericTypeQuery :: Generic TypeQuery _
33−
34−instance showTypeQuery :: Show TypeQuery where
35− show x = genericShow x
36−
37−parseTypeQuery :: String -> Either ParseError TypeQuery
38−parseTypeQuery = runParser (typeQueryParser <* eof)
39−
40−typeQueryParser :: Parser TypeQuery
41−typeQueryParser = fix \typeQuery ->
42− let rowFields =
43− QRow <$> sepBy (Tuple <$> (skipSpaces *> ident <* skipSpaces <* string "::") <*>
44− (skipSpaces *> typeQuery <* skipSpaces))
45− (string "," *> skipSpaces)
46−
47− row = string "{" *> rowFields <* string "}"
48−
49− binders =
50− string "forall" *> some space *> sepEndBy1 ident (some space) <* string "." <* skipSpaces
51−
52− for_all = QForAll <$> binders <*> typeQuery
53−
54− parens =
55− string "(" *> skipSpaces *> typeQuery <* skipSpaces <* string ")"
56−
57− atom = skipSpaces *> (
58− for_all <|>
59− parens <|>
60− row <|>
61− concrete <|>
62− any
63− )
64−
65− apps =
66− foldl1 QApp <$> sepEndBy1 atom (some space)
67−
68− funs =
69− foldr1 QFun <$> sepBy1 apps (string "->" *> skipSpaces)
70−
71− constrained =
72− QConstraint <$> (upperCaseIdent <* some space) <*>
73− (sepEndBy ident (some space) <* string "=>") <*>
74− funs
75− in
76− try constrained <|> funs
77−
78−foldl1 :: forall a. (a -> a -> a) -> NonEmptyList a -> a
79−foldl1 f as =
80− case uncons as of
81− { head, tail } -> foldl f head tail
82−
83−foldr1 :: forall a. (a -> a -> a) -> NonEmptyList a -> a
84−foldr1 f = go List.Nil
85− where
86− go acc x = case uncons x of
87− { head, tail } -> case List.uncons tail of
88− Nothing -> List.foldl (flip f) head acc
89− Just { head: head1, tail: tail1 } ->
90− go (head : acc) (cons' head1 tail1)
91−
92−any :: Parser TypeQuery
93−any = do
94− QAny <$> lowerCaseIdent
95−
96−concrete :: Parser TypeQuery
97−concrete =
98− QConcrete <$> upperCaseIdent
99−
100−ident :: Parser String
101−ident = do
102− head <- anyLetter
103− rest <- Array.many alphaNum
104− pure $ fromCharArray $ pure head <> rest
105−
106−upperCaseIdent :: Parser String
107−upperCaseIdent = do
108− head <- upperCaseChar
109− rest <- Array.many alphaNum
110− pure $ fromCharArray $ pure head <> rest
111−
112−lowerCaseIdent :: Parser String
113−lowerCaseIdent = do
114− head <- lowerCaseChar
115− rest <- Array.many alphaNum
116− pure $ fromCharArray $ pure head <> rest
117−
118−space :: Parser Char
119−space = char ' '
test/Main.purs+22−414
@@ -1,421 +1,29 @@
11module Test.Main where
22
3−import Data.Argonaut.Decode
4−import Data.Argonaut.Encode
5−import Data.Argonaut.Parser
6−import Data.Either
7−import Data.Maybe
8−import Effect (Effect)
9−import Effect.Aff
10−import Effect.Console (log)
11−import Partial.Unsafe
123import Prelude
13−import Spago.Search.DocsJson
14−import Spago.Search.TypeDecoder
15−import Test.Unit (suite, test, timeout)
16−import Test.Unit.Assert as Assert
17−import Test.Unit.Main (runTest)
4+
5+import Effect (Effect)
6+import Effect.Aff (launchAff_)
7+import Test.Declarations as Declarations
8+import Test.IndexBuilder as IndexBuilder
9+import Test.ModuleIndex as ModuleIndex
10+import Test.ModuleParser as ModuleParser
11+import Test.Spec (Spec)
12+import Test.Spec.Reporter.Console (consoleReporter)
13+import Test.Spec.Runner (runSpec)
14+import Test.TypeJson as TypeJson
1815import Test.TypeQuery as TypeQuery
1916
2017main :: Effect Unit
21−main = runTest do
22− TypeQuery.tests
23−
24− let mkJson x = unsafePartial $ fromRight $ jsonParser x
25− suite "FunDeps decoder" do
26− test "FunDeps" do
27− let funDeps = mkJson """
28− [
29− [
30− [
31− "lhs",
32− "rhs"
33− ],
34− [
35− "output"
36− ]
37− ]
38− ]
39− """
40− assertRight (decodeJson funDeps)
41− (FunDeps [ FunDep { lhs: [ "lhs", "rhs" ]
42− , rhs: [ "output"]
43− }
44− ])
45−
46− suite "Kind decoder" do
47−
48− test "QualifiedName" do
49−
50− let qualifiedName = mkJson """
51− [
52− [
53− "Prim"
54− ],
55− "Type"
56− ]
57− """
58−
59− assertRight (decodeJson qualifiedName)
60− (QualifiedName { moduleName: ["Prim"]
61− , name: "Type"
62− }
63− )
64−
65− test "NamedKind" do
66− let namedKind = mkJson """
67− {
68− "annotation": [],
69− "tag": "NamedKind",
70− "contents": [
71− [
72− "Prim"
73− ],
74− "Type"
75− ]
76− }
77− """
78−
79− assertRight (decodeJson namedKind)
80− (NamedKind $ QualifiedName { moduleName: ["Prim"]
81− , name: "Type"
82− }
83− )
84−
85− test "Row" do
86− let row = mkJson """
87− {
88− "annotation": [],
89− "tag": "Row",
90− "contents": {
91− "annotation": [],
92− "tag": "NamedKind",
93− "contents": [
94− [
95− "Prim"
96− ],
97− "Type"
98− ]
99− }
100− }
101− """
102−
103− assertRight (decodeJson row)
104− (Row $ NamedKind $ QualifiedName { moduleName: ["Prim"]
105− , name: "Type"
106− }
107− )
108−
109− test "FunKind" do
110− let funKind = mkJson """
111− {
112− "annotation": [],
113− "tag": "FunKind",
114− "contents": [
115− {
116− "annotation": [],
117− "tag": "Row",
118− "contents": {
119− "annotation": [],
120− "tag": "NamedKind",
121− "contents": [
122− [
123− "Prim"
124− ],
125− "Type"
126− ]
127− }
128− },
129− {
130− "annotation": [],
131− "tag": "Row",
132− "contents": {
133− "annotation": [],
134− "tag": "NamedKind",
135− "contents": [
136− [
137− "Prim"
138− ],
139− "Type"
140− ]
141− }
142− }
143− ]
144− }
145− """
146− assertRight (decodeJson funKind)
147− (FunKind (Row $ NamedKind $ QualifiedName { moduleName: ["Prim"]
148− , name: "Type"
149− }
150− )
151− (Row $ NamedKind $ QualifiedName { moduleName: ["Prim"]
152− , name: "Type"
153− }
154− )
155− )
156−
157−
158− suite "Constraint decoder" do
159− test "Constraint" do
160− let constraint = mkJson """
161− {
162− "constraintAnn": [],
163− "constraintClass": [
164− [
165− "Prim"
166− ],
167− "Partial"
168− ],
169− "constraintArgs": [],
170− "constraintData": null
171− }
172− """
173− assertRight (decodeJson constraint)
174− (Constraint { constraintClass: QualifiedName { moduleName: ["Prim"]
175− , name: "Partial"
176− }
177− , constraintArgs: []
178− })
179−
180− suite "Type decoder" do
181− test "TypeVar" do
182− let typeVar = mkJson """
183− {
184− "annotation": [],
185− "tag": "TypeVar",
186− "contents": "m"
187− }
188− """
18+main = do
19+ launchAff_ $ runSpec [consoleReporter] mainTest
20+ -- UI.main
18921
190− assertRight (decodeJson typeVar)
191− (TypeVar "m")
192−
193− test "TypeApp" do
194− let typeApp1 = mkJson """
195− {
196− "annotation": [],
197− "tag": "TypeApp",
198− "contents": [
199− {
200− "annotation": [],
201− "tag": "TypeConstructor",
202− "contents": [
203− [
204− "Control",
205− "Monad",
206− "ST",
207− "Internal"
208− ],
209− "ST"
210− ]
211− },
212− {
213− "annotation": [],
214− "tag": "TypeVar",
215− "contents": "h"
216− }
217− ]
218− }
219− """
220−
221− assertRight (decodeJson typeApp1) $
222− TypeApp
223− (TypeConstructor (QualifiedName { moduleName:
224− [ "Control"
225− , "Monad"
226− , "ST"
227− , "Internal"
228− ],
229− name: "ST"
230− }
231− ))
232− (TypeVar "h")
233−
234− test "TypeOp" do
235− let typeOp = mkJson """
236− {
237− "annotation": [],
238− "tag": "TypeOp",
239− "contents": [
240− [
241− "Data",
242− "NaturalTransformation"
243− ],
244− "~>"
245− ]
246− }
247− """
248− assertRight (decodeJson typeOp) $
249− TypeOp $ QualifiedName { moduleName: [ "Data", "NaturalTransformation" ]
250− , name: "~>"
251− }
252−
253− test "BinaryNoParens" do
254− let binaryNoParens = mkJson """
255− {
256− "annotation": [],
257− "tag": "BinaryNoParensType",
258− "contents": [
259− {
260− "annotation": [],
261− "tag": "TypeOp",
262− "contents": [
263− [
264− "Data",
265− "NaturalTransformation"
266− ],
267− "~>"
268− ]
269− },
270− {
271− "annotation": [],
272− "tag": "TypeVar",
273− "contents": "m"
274− },
275− {
276− "annotation": [],
277− "tag": "TypeVar",
278− "contents": "n"
279− }
280− ]
281− }
282− """
283−
284− assertRight (decodeJson binaryNoParens) $
285− BinaryNoParensType
286− (TypeOp $ QualifiedName { moduleName: ["Data", "NaturalTransformation"], name: "~>" })
287− (TypeVar "m")
288− (TypeVar "n")
289−
290− test "ParensInType" do
291− let parensInType = mkJson """
292− {
293− "annotation": [],
294− "tag": "ParensInType",
295− "contents": {
296− "annotation": [],
297− "tag": "TypeApp",
298− "contents": [
299− {
300− "annotation": [],
301− "tag": "TypeConstructor",
302− "contents": [
303− [
304− "Data",
305− "Maybe"
306− ],
307− "Maybe"
308− ]
309− },
310− {
311− "annotation": [],
312− "tag": "TypeConstructor",
313− "contents": [
314− [
315− "Prim"
316− ],
317− "String"
318− ]
319− }
320− ]
321− }
322− }
323− """
324−
325− assertRight (decodeJson parensInType) $
326− ParensInType $
327− TypeApp
328− (TypeConstructor (QualifiedName { moduleName:
329− [ "Data"
330− , "Maybe"
331− ],
332− name: "Maybe"
333− }
334− ))
335− (TypeConstructor (QualifiedName { moduleName:
336− [ "Prim"
337− ],
338− name: "String"
339− }
340− ))
341− test "RCons" do
342−
343− let rcons = mkJson """
344− {
345− "annotation": [],
346− "tag": "RCons",
347− "contents": [
348− "tail",
349− {
350− "annotation": [],
351− "tag": "TypeApp",
352− "contents": [
353− {
354− "annotation": [],
355− "tag": "TypeConstructor",
356− "contents": [
357− [
358− "Data",
359− "Symbol"
360− ],
361− "SProxy"
362− ]
363− },
364− {
365− "annotation": [],
366− "tag": "TypeVar",
367− "contents": "t"
368− }
369− ]
370− },
371− {
372− "annotation": [],
373− "tag": "REmpty"
374− }
375− ]
376− }
377− """
378−
379− assertRight (decodeJson rcons) $
380− RCons
381− "tail"
382− (TypeApp (TypeConstructor $ QualifiedName { moduleName: [ "Data", "Symbol" ], name: "SProxy" })
383− (TypeVar "t"))
384− REmpty
385−
386− test "ForAll" do
387− let forallJson = mkJson """
388− {"annotation":[],"tag":"ForAll","contents":["a",{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Function"]},{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"String"]}]},{"annotation":[],"tag":"TypeVar","contents":"a"}]},null]}
389− """
390− assertRight (decodeJson forallJson) $
391− ForAll "a" (TypeApp (TypeApp (TypeConstructor $ qualified ["Prim"] "Function")
392− (TypeConstructor $ qualified ["Prim"] "String"))
393− (TypeVar "a")) Nothing
394− suite "jsons" do
395−
396− test "jsons #1" do
397− let json = mkJson """
398−{"annotation":[],"tag":"ForAll","contents":["o",{"annotation":[],"tag":"ForAll","contents":["r",{"annotation":[],"tag":"ForAll","contents":["l",{"annotation":[],"tag":"ConstrainedType","contents":[{"constraintAnn":[],"constraintClass":[["Type","Data","Boolean"],"And"],"constraintArgs":[{"annotation":[],"tag":"TypeVar","contents":"l"},{"annotation":[],"tag":"TypeVar","contents":"r"},{"annotation":[],"tag":"TypeVar","contents":"o"}],"constraintData":null},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Function"]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Type","Data","Boolean"],"BProxy"]},{"annotation":[],"tag":"TypeVar","contents":"l"}]}]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Function"]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Type","Data","Boolean"],"BProxy"]},{"annotation":[],"tag":"TypeVar","contents":"r"}]}]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Type","Data","Boolean"],"BProxy"]},{"annotation":[],"tag":"TypeVar","contents":"o"}]}]}]}]},null]},null]},null]}
399− """
400−
401− assertRight (decodeJson json) $ (ForAll "o" (ForAll "r" (ForAll "l" (ConstrainedType (Constraint { constraintArgs: [(TypeVar "l"),(TypeVar "r"),(TypeVar "o")], constraintClass: (QualifiedName { moduleName: ["Type","Data","Boolean"], name: "And" }) }) (TypeApp (TypeApp (TypeConstructor (QualifiedName { moduleName: ["Prim"], name: "Function" })) (TypeApp (TypeConstructor (QualifiedName { moduleName: ["Type","Data","Boolean"], name: "BProxy" })) (TypeVar "l"))) (TypeApp (TypeApp (TypeConstructor (QualifiedName { moduleName: ["Prim"], name: "Function" })) (TypeApp (TypeConstructor (QualifiedName { moduleName: ["Type","Data","Boolean"], name: "BProxy" })) (TypeVar "r"))) (TypeApp (TypeConstructor (QualifiedName { moduleName: ["Type","Data","Boolean"], name: "BProxy" })) (TypeVar "o"))))) Nothing) Nothing) Nothing)
402−
403− suite "Kind encoder" do
404− test "FunKind" do
405− let k1 =
406− FunKind (Row (NamedKind $ qualified [] "a"))
407− (FunKind (NamedKind $ qualified [] "b")
408− (NamedKind $ qualified [] "b"))
409− assertRight (decodeJson $ encodeJson $ k1) k1
410−
411−
412−qualified :: Array String -> String -> QualifiedName
413−qualified moduleName name = QualifiedName { moduleName, name }
414−
415−assertRight :: forall a. Show a => Eq a => Either String a -> a -> Aff Unit
416−assertRight eiActual expected =
417− case eiActual of
418− Left string -> do
419− Assert.equal (Right expected) eiActual
420− Right actual -> do
421− Assert.equal (Right expected) eiActual
22+mainTest :: Spec Unit
23+mainTest = do
24+ ModuleParser.tests
25+ TypeQuery.tests
26+ TypeJson.tests
27+ IndexBuilder.tests
28+ Declarations.tests
29+ ModuleIndex.tests
test/Test/Declarations.pursnew file+41
@@ -0,0 +1,41 @@
1+module Test.Declarations where
2+
3+import Prelude
4+
5+import Docs.Search.Declarations (extractPackageName)
6+import Docs.Search.Types (PackageName(..), PackageInfo(..))
7+
8+import Data.Maybe (Maybe(..))
9+import Data.Newtype (wrap)
10+import Test.Spec (Spec, describe, it)
11+import Test.Spec.Assertions (shouldEqual)
12+
13+tests :: Spec Unit
14+tests = do
15+ describe "Declarations" do
16+ it "extractPackageName works correctly" do
17+ Builtin `shouldEqual` (extractPackageName (wrap "Prim") Nothing)
18+ Builtin `shouldEqual` (extractPackageName (wrap "Prim.Foo") Nothing)
19+ Builtin `shouldEqual` (extractPackageName (wrap "Prim.Foo.Bar") Nothing)
20+ UnknownPackage `shouldEqual` (extractPackageName (wrap "Primitive") Nothing)
21+ Package (PackageName "foo") `shouldEqual`
22+ (extractPackageName (wrap "Foo") $
23+ Just { start: []
24+ , end: []
25+ , name: ".spago/foo/src/Foo.purs"
26+ }
27+ )
28+ Package (PackageName "bar") `shouldEqual`
29+ (extractPackageName (wrap "Bar") $
30+ Just { start: []
31+ , end: []
32+ , name: "/path/to/somewhere/bower_components/bar/src/Bar.purs"
33+ }
34+ )
35+ LocalPackage `shouldEqual`
36+ (extractPackageName (wrap "Bar") $
37+ Just { start: []
38+ , end: []
39+ , name: "/path/to/somewhere/src/Bar.purs"
40+ }
41+ )
test/Test/Extra.pursnew file+24
@@ -0,0 +1,24 @@
1+module Test.Extra where
2+
3+import Prelude
4+
5+import Data.Either (Either(..))
6+import Effect.Aff (Aff)
7+import Test.Spec.Assertions (shouldEqual)
8+
9+
10+assertRight
11+ :: forall a b
12+ . Show a
13+ => Show b
14+ => Eq a
15+ => Eq b
16+ => Either b a
17+ -> a
18+ -> Aff Unit
19+assertRight eiActual expected =
20+ case eiActual of
21+ Left _ -> do
22+ Right expected `shouldEqual` eiActual
23+ Right _ -> do
24+ Right expected `shouldEqual` eiActual
test/Test/IndexBuilder.pursnew file+20
@@ -0,0 +1,20 @@
1+module Test.IndexBuilder where
2+
3+import Prelude
4+
5+import Docs.Search.IndexBuilder (patchHTML)
6+
7+import Data.Tuple (snd)
8+import Test.Spec (Spec, describe, it)
9+import Test.Spec.Assertions (shouldEqual, shouldNotSatisfy)
10+
11+tests :: Spec Unit
12+tests = do
13+ describe "IndexBuilder" do
14+ describe "patchHTML" do
15+ it "works" do
16+ let input = "</body>"
17+ shouldNotSatisfy (snd (patchHTML input)) (eq input)
18+ it "is idempotent" do
19+ let input = "</body>"
20+ shouldEqual (snd $ patchHTML $ snd $ patchHTML input) (snd $ patchHTML input)
test/Test/ModuleIndex.pursnew file+23
@@ -0,0 +1,23 @@
1+module Test.ModuleIndex where
2+
3+import Docs.Search.ModuleIndex (extractModuleNameParts)
4+
5+import Prelude
6+
7+import Data.List as List
8+import Data.Newtype (wrap)
9+import Test.Spec (Spec, describe, it)
10+import Test.Spec.Assertions (shouldEqual)
11+
12+
13+tests :: Spec Unit
14+tests = do
15+ describe "ModuleIndex" do
16+
17+ it "test #0" do
18+ extractModuleNameParts (wrap "Data.Array.ST") `shouldEqual`
19+ List.fromFoldable [ "st", "array.st", "data.array.st" ]
20+
21+ it "test #1" do
22+ extractModuleNameParts (wrap "Foo") `shouldEqual`
23+ List.fromFoldable [ "foo" ]
test/Test/ModuleParser.pursnew file+61
@@ -0,0 +1,61 @@
1+module Test.ModuleParser where
2+
3+import Docs.Search.IndexBuilder (getPathsByGlobs)
4+import Docs.Search.ModuleParser (multiLineComment, parseModuleName, singleLineComment)
5+import Docs.Search.Types (ModuleName(..))
6+
7+import Prelude
8+
9+import Data.Array as Array
10+import Data.Either (Either(..))
11+import Data.Foldable (for_)
12+import Data.Maybe (Maybe(..))
13+import Effect.Class (liftEffect)
14+import Effect.Console as Console
15+import Effect.Exception (throw)
16+import Node.Encoding (Encoding(..))
17+import Node.FS.Aff (readTextFile)
18+import StringParser (runParser)
19+import Test.Spec (Spec, describe, it)
20+import Test.Spec.Assertions (shouldEqual)
21+
22+tests :: Spec Unit
23+tests = do
24+ describe "ModuleParser" do
25+ it "test #0" do
26+ parseModuleName "module Foo" `shouldEqual` Just (ModuleName "Foo")
27+ it "test #1" do
28+ parseModuleName "module Foo.Bar.B" `shouldEqual` Just (ModuleName "Foo.Bar.B")
29+ it "test #2" do
30+ parseModuleName " module Foo" `shouldEqual` Just (ModuleName "Foo")
31+ it "test #3" do
32+ parseModuleName " {- asdas -} module Foo" `shouldEqual` Just (ModuleName "Foo")
33+ it "test #4" do
34+ parseModuleName "{--}module Foo" `shouldEqual` Just (ModuleName "Foo")
35+ it "test #5" do
36+ parseModuleName "--\nmodule Foo" `shouldEqual` Just (ModuleName "Foo")
37+ it "test #6" do
38+ parseModuleName "--\n-- foo\nmodule Foo" `shouldEqual` Just (ModuleName "Foo")
39+ it "test #7" do
40+ parseModuleName "-- \n -- foo\n {- bar -} --baz\n module Foo" `shouldEqual` Just (ModuleName "Foo")
41+ it "multiline comment #1" do
42+ runParser multiLineComment "{--}" `shouldEqual` Right unit
43+ it "multiline comment #1" do
44+ runParser multiLineComment "{- foo -}" `shouldEqual` Right unit
45+ it "multiline comment #1" do
46+ runParser multiLineComment "{- foo\nbar\nbar\n -}" `shouldEqual` Right unit
47+ it "single line comment #1" do
48+ runParser singleLineComment "-- asd\n" `shouldEqual` Right unit
49+ it "single line comment #1" do
50+ runParser singleLineComment "--\n" `shouldEqual` Right unit
51+ it "Parses every module in .spago/" do
52+ files <- getPathsByGlobs ["./.spago/**/*.purs"]
53+ liftEffect $ Console.log $ "Modules in .spago: " <> show (Array.length files)
54+ for_ files \filePath -> do
55+ fileContents <- readTextFile UTF8 filePath
56+ case parseModuleName fileContents of
57+ Nothing -> do
58+ liftEffect $ throw $
59+ "Module header decoding failed for " <> filePath <>
60+ ", unable to extract module name"
61+ Just _ -> pure unit
test/Test/TypeJson.pursnew file+300
@@ -0,0 +1,300 @@
1+module Test.TypeJson
2+ ( tests )
3+where
4+
5+import Prelude
6+
7+import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), QualifiedName(..), Type(..))
8+import Docs.Search.Types (Identifier(..))
9+import Test.Extra (assertRight)
10+import Test.TypeQuery as TypeQuery
11+
12+import Data.Argonaut.Core (Json)
13+import Data.Argonaut.Decode (decodeJson)
14+import Data.Argonaut.Parser (jsonParser)
15+import Data.Either (Either(..))
16+import Data.Maybe (Maybe(..))
17+import Partial.Unsafe (unsafePartial)
18+import Test.Spec (Spec, describe, it)
19+
20+
21+tests :: Spec Unit
22+tests = do
23+ describe "FunDeps decoder" do
24+ it "FunDeps" do
25+ let
26+ funDeps = mkJson """
27+ [
28+ [
29+ [
30+ "lhs",
31+ "rhs"
32+ ],
33+ [
34+ "output"
35+ ]
36+ ]
37+ ]
38+ """
39+ assertRight (decodeJson funDeps)
40+ (FunDeps [ FunDep { lhs: [ "lhs", "rhs" ]
41+ , rhs: [ "output"]
42+ }
43+ ])
44+
45+ describe "Constraint decoder" do
46+ it "Constraint" do
47+ let constraint = mkJson """
48+ {
49+ "constraintAnn": [],
50+ "constraintClass": [
51+ [
52+ "Prim"
53+ ],
54+ "Partial"
55+ ],
56+ "constraintArgs": [],
57+ "constraintData": null
58+ }
59+ """
60+ assertRight (decodeJson constraint)
61+ (Constraint { constraintClass: qualified ["Prim"] "Partial"
62+ , constraintArgs: []
63+ })
64+
65+ describe "Type decoder" do
66+ it "TypeVar" do
67+ let typeVar = mkJson """
68+ {
69+ "annotation": [],
70+ "tag": "TypeVar",
71+ "contents": "m"
72+ }
73+ """
74+
75+ assertRight (decodeJson typeVar)
76+ (TypeVar "m")
77+
78+ it "TypeApp" do
79+ let typeApp1 = mkJson """
80+ {
81+ "annotation": [],
82+ "tag": "TypeApp",
83+ "contents": [
84+ {
85+ "annotation": [],
86+ "tag": "TypeConstructor",
87+ "contents": [
88+ [
89+ "Control",
90+ "Monad",
91+ "ST",
92+ "Internal"
93+ ],
94+ "ST"
95+ ]
96+ },
97+ {
98+ "annotation": [],
99+ "tag": "TypeVar",
100+ "contents": "h"
101+ }
102+ ]
103+ }
104+ """
105+
106+ assertRight (decodeJson typeApp1) $
107+ TypeApp
108+ (TypeConstructor (qualified [ "Control"
109+ , "Monad"
110+ , "ST"
111+ , "Internal"
112+ ]
113+ "ST"
114+ ))
115+ (TypeVar "h")
116+
117+ it "TypeOp" do
118+ let typeOp = mkJson """
119+ {
120+ "annotation": [],
121+ "tag": "TypeOp",
122+ "contents": [
123+ [
124+ "Data",
125+ "NaturalTransformation"
126+ ],
127+ "~>"
128+ ]
129+ }
130+ """
131+ assertRight (decodeJson typeOp) $
132+ TypeOp $ qualified [ "Data", "NaturalTransformation" ] "~>"
133+
134+ it "BinaryNoParens" do
135+ let binaryNoParens = mkJson """
136+ {
137+ "annotation": [],
138+ "tag": "BinaryNoParensType",
139+ "contents": [
140+ {
141+ "annotation": [],
142+ "tag": "TypeOp",
143+ "contents": [
144+ [
145+ "Data",
146+ "NaturalTransformation"
147+ ],
148+ "~>"
149+ ]
150+ },
151+ {
152+ "annotation": [],
153+ "tag": "TypeVar",
154+ "contents": "m"
155+ },
156+ {
157+ "annotation": [],
158+ "tag": "TypeVar",
159+ "contents": "n"
160+ }
161+ ]
162+ }
163+ """
164+
165+ assertRight (decodeJson binaryNoParens) $
166+ BinaryNoParensType
167+ (TypeOp $ qualified ["Data", "NaturalTransformation"] "~>")
168+ (TypeVar "m")
169+ (TypeVar "n")
170+
171+ it "ParensInType" do
172+ let parensInType = mkJson """
173+ {
174+ "annotation": [],
175+ "tag": "ParensInType",
176+ "contents": {
177+ "annotation": [],
178+ "tag": "TypeApp",
179+ "contents": [
180+ {
181+ "annotation": [],
182+ "tag": "TypeConstructor",
183+ "contents": [
184+ [
185+ "Data",
186+ "Maybe"
187+ ],
188+ "Maybe"
189+ ]
190+ },
191+ {
192+ "annotation": [],
193+ "tag": "TypeConstructor",
194+ "contents": [
195+ [
196+ "Prim"
197+ ],
198+ "String"
199+ ]
200+ }
201+ ]
202+ }
203+ }
204+ """
205+
206+ assertRight (decodeJson parensInType) $
207+ ParensInType $
208+ TypeApp
209+ (TypeConstructor (qualified [ "Data", "Maybe" ] "Maybe"))
210+ (TypeConstructor (qualified [ "Prim" ] "String"))
211+ it "RCons" do
212+
213+ let rcons = mkJson """
214+ {
215+ "annotation": [],
216+ "tag": "RCons",
217+ "contents": [
218+ "tail",
219+ {
220+ "annotation": [],
221+ "tag": "TypeApp",
222+ "contents": [
223+ {
224+ "annotation": [],
225+ "tag": "TypeConstructor",
226+ "contents": [
227+ [
228+ "Data",
229+ "Symbol"
230+ ],
231+ "SProxy"
232+ ]
233+ },
234+ {
235+ "annotation": [],
236+ "tag": "TypeVar",
237+ "contents": "t"
238+ }
239+ ]
240+ },
241+ {
242+ "annotation": [],
243+ "tag": "REmpty"
244+ }
245+ ]
246+ }
247+ """
248+
249+ assertRight (decodeJson rcons) $
250+ RCons
251+ (Identifier "tail")
252+ (TypeApp (TypeConstructor $ qualified [ "Data", "Symbol" ] "SProxy")
253+ (TypeVar "t"))
254+ REmpty
255+
256+ it "ForAll #1" do
257+ let forallJson = mkJson """
258+ {"annotation":[],"tag":"ForAll","contents":["a",{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Function"]},{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"String"]}]},{"annotation":[],"tag":"TypeVar","contents":"a"}]},null]}
259+ """
260+ assertRight (decodeJson forallJson) $
261+ ForAll "a" Nothing (TypeApp (TypeApp (TypeConstructor $ qualified ["Prim"] "Function")
262+ (TypeConstructor $ qualified ["Prim"] "String"))
263+ (TypeVar "a"))
264+
265+ it "KindApp" do
266+ let kindAppJson = mkJson """
267+ {"annotation":[],"tag":"KindApp","contents":[{"annotation":[],"tag":"REmpty"},{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Type"]}]}
268+ """
269+ assertRight (decodeJson kindAppJson) $
270+ KindApp REmpty (TypeConstructor (qualified ["Prim"] "Type"))
271+ it "KindedType" do
272+ let kindedTypeJson = mkJson """
273+ {"annotation":[],"tag":"KindedType","contents":[{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Data","Const"],"Const"]},{"annotation":[],"tag":"TypeConstructor","contents":[["Data","Void"],"Void"]}]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Function"]},{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Type"]}]},{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Type"]}]}]}
274+ """
275+ assertRight (decodeJson kindedTypeJson) $
276+ Kinded
277+ (TypeApp
278+ (TypeConstructor (qualified ["Data","Const"] "Const"))
279+ (TypeConstructor (qualified ["Data","Void"] "Void")))
280+ (TypeApp (TypeApp
281+ (TypeConstructor (qualified ["Prim"] "Function"))
282+ (TypeConstructor (qualified ["Prim"] "Type" )))
283+ (TypeConstructor (qualified ["Prim"] "Type")))
284+ describe "jsons" do
285+
286+ it "jsons #1" do
287+ let json = mkJson """
288+{"annotation":[],"tag":"ForAll","contents":["o",{"annotation":[],"tag":"ForAll","contents":["r",{"annotation":[],"tag":"ForAll","contents":["l",{"annotation":[],"tag":"ConstrainedType","contents":[{"constraintAnn":[],"constraintClass":[["Type","Data","Boolean"],"And"],"constraintArgs":[{"annotation":[],"tag":"TypeVar","contents":"l"},{"annotation":[],"tag":"TypeVar","contents":"r"},{"annotation":[],"tag":"TypeVar","contents":"o"}],"constraintData":null},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Function"]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Type","Data","Boolean"],"BProxy"]},{"annotation":[],"tag":"TypeVar","contents":"l"}]}]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Prim"],"Function"]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Type","Data","Boolean"],"BProxy"]},{"annotation":[],"tag":"TypeVar","contents":"r"}]}]},{"annotation":[],"tag":"TypeApp","contents":[{"annotation":[],"tag":"TypeConstructor","contents":[["Type","Data","Boolean"],"BProxy"]},{"annotation":[],"tag":"TypeVar","contents":"o"}]}]}]}]},null]},null]},null]}
289+ """
290+
291+ assertRight (decodeJson json) $ (ForAll "o" Nothing (ForAll "r" Nothing (ForAll "l" Nothing (ConstrainedType (Constraint { constraintArgs: [(TypeVar "l"),(TypeVar "r"),(TypeVar "o")], constraintClass: (QualifiedName { moduleNameParts: ["Type","Data","Boolean"], name: Identifier "And" }) }) (TypeApp (TypeApp (TypeConstructor (QualifiedName { moduleNameParts: ["Prim"], name: Identifier "Function" })) (TypeApp (TypeConstructor (QualifiedName { moduleNameParts: ["Type","Data","Boolean"], name: Identifier "BProxy" })) (TypeVar "l"))) (TypeApp (TypeApp (TypeConstructor (QualifiedName { moduleNameParts: ["Prim"], name: Identifier "Function" })) (TypeApp (TypeConstructor (QualifiedName { moduleNameParts: ["Type","Data","Boolean"], name: Identifier "BProxy" })) (TypeVar "r"))) (TypeApp (TypeConstructor (QualifiedName { moduleNameParts: ["Type","Data","Boolean"], name: Identifier "BProxy" })) (TypeVar "o"))))))))
292+
293+
294+qualified :: Array String -> String -> QualifiedName
295+qualified = TypeQuery.qname
296+
297+mkJson :: String -> Json
298+mkJson str =
299+ unsafePartial $ case jsonParser str of
300+ Right r -> r
test/Test/TypeQuery.purs+518−93
@@ -1,155 +1,580 @@
11module Test.TypeQuery where
22
3+import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..))
4+import Docs.Search.TypeQuery (Substitution(..), TypeQuery(..), getFreeVariables, parseTypeQuery, penalty, typeVarPenalty)
5+import Docs.Search.TypeShape (ShapeChunk(..), shapeOfType, shapeOfTypeQuery)
6+import Docs.Search.Types (Identifier(..))
37
4−import Data.Argonaut.Decode
5−import Data.Argonaut.Encode
6−import Data.Argonaut.Parser
7−import Data.Either
8−import Data.Maybe
9−import Effect (Effect)
10−import Effect.Aff
11−import Effect.Console (log)
12−import Partial.Unsafe
138import Prelude
14−import Spago.Search.DocsJson
15−import Spago.Search.TypeDecoder
16−import Spago.Search.TypeQuery
17−import Test.Unit (suite, test, timeout)
18−import Test.Unit.Assert as Assert
19−import Test.Unit.Main (runTest)
20−import Data.List as List
9+import Prim hiding (Constraint, Type)
10+import Data.Foldable (class Foldable)
2111import Data.List (List(..), (:))
22−import Data.Tuple
23−import Data.Foldable
12+import Data.List as List
2413import Data.List.NonEmpty (NonEmptyList)
2514import Data.List.NonEmpty as NonEmptyList
15+import Data.Set as Set
16+import Data.Tuple (Tuple(..))
17+import Test.Extra (assertRight)
18+import Test.Spec (Spec, describe, it)
19+import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
20+import Data.Maybe (Maybe(..))
2621
27−tests =
28− suite "TypeQuery parser" do
22+tests :: Spec Unit
23+tests = do
24+ describe "TypeQuery parser" do
2925
30− test "test #0" do
26+ it "test #0" do
3127 let input = "a"
32− assertRight (parseTypeQuery input) (QAny "a")
28+ assertRight (parseTypeQuery input) (qVar "a")
3329
34− test "test #0.1" do
30+ it "test #1" do
3531 let input = "ab"
36− assertRight (parseTypeQuery input) (QAny "ab")
32+ assertRight (parseTypeQuery input) (qVar "ab")
3733
38− test "test #1.0" do
34+ it "test #2" do
3935 let input = "a b"
40− assertRight (parseTypeQuery input) (QApp (QAny "a") (QAny "b"))
36+ assertRight (parseTypeQuery input) (QApp (qVar "a") (qVar "b"))
4137
42− test "test #1.1" do
38+ it "test #3" do
4339 let input = "a b c"
44− assertRight (parseTypeQuery input) (QApp (QApp (QAny "a") (QAny "b")) (QAny "c"))
40+ assertRight (parseTypeQuery input) (QApp (QApp (qVar "a") (qVar "b")) (qVar "c"))
4541
46− test "test #2" do
42+ it "test #4" do
4743 let input = "a -> b"
48− assertRight (parseTypeQuery input) (QFun (QAny "a") (QAny "b"))
44+ assertRight (parseTypeQuery input) (QFun (qVar "a") (qVar "b"))
4945
50− test "test #3" do
46+ it "test #5" do
5147 let input = "a -> b c"
52− assertRight (parseTypeQuery input) (QFun (QAny "a") (QApp (QAny "b") (QAny "c")))
48+ assertRight (parseTypeQuery input) (QFun (qVar "a") (QApp (qVar "b") (qVar "c")))
5349
54− test "test #4" do
50+ it "test #6" do
5551 let input = "a b -> c"
56− assertRight (parseTypeQuery input) (QFun (QApp (QAny "a") (QAny "b")) (QAny "c"))
52+ assertRight (parseTypeQuery input) (QFun (QApp (qVar "a") (qVar "b")) (qVar "c"))
5753
58− test "test #5" do
54+ it "test #7" do
5955 let input = "a b"
60− assertRight (parseTypeQuery input) (QApp (QAny "a") (QAny "b"))
56+ assertRight (parseTypeQuery input) (QApp (qVar "a") (qVar "b"))
6157
62− test "test #6" do
58+ it "test #8" do
6359 let input = "a (b c)"
64− assertRight (parseTypeQuery input) (QApp (QAny "a") (QApp (QAny "b") (QAny "c")))
60+ assertRight (parseTypeQuery input) (QApp (qVar "a") (QApp (qVar "b") (qVar "c")))
6561
66− test "test #6.1" do
62+ it "test #9" do
6763 let input = "(a b) (c d)"
6864 assertRight (parseTypeQuery input)
69− (QApp (QApp (QAny "a") (QAny "b"))
70− (QApp (QAny "c") (QAny "d")))
65+ ( QApp (QApp (qVar "a") (qVar "b"))
66+ (QApp (qVar "c") (qVar "d"))
67+ )
7168
72− test "test #7" do
69+ it "test #10" do
7370 let input = "a ( b c )"
74− assertRight (parseTypeQuery input) (QApp (QAny "a") (QApp (QAny "b") (QAny "c")))
71+ assertRight (parseTypeQuery input) (QApp (qVar "a") (QApp (qVar "b") (qVar "c")))
7572
76− test "test #8" do
73+ it "test #11" do
7774 let input = "aaa"
78− assertRight (parseTypeQuery input) (QAny "aaa")
75+ assertRight (parseTypeQuery input) (qVar "aaa")
7976
80− test "test #9" do
77+ it "test #12" do
8178 let input = "aaa ( bbb ccc )"
82− assertRight (parseTypeQuery input) (QApp (QAny "aaa") (QApp (QAny "bbb") (QAny "ccc")))
79+ assertRight (parseTypeQuery input) (QApp (qVar "aaa") (QApp (qVar "bbb") (qVar "ccc")))
8380
84− test "test #10" do
81+ it "test #13" do
8582 let input = "(a -> b) -> (c -> d)"
86− assertRight (parseTypeQuery input) (QFun (QFun (QAny "a") (QAny "b"))
87− (QFun (QAny "c") (QAny "d")))
83+ assertRight (parseTypeQuery input)
84+ ( QFun (QFun (qVar "a") (qVar "b"))
85+ (QFun (qVar "c") (qVar "d"))
86+ )
8887
89− test "test #11" do
88+ it "test #14" do
9089 let input = "a -> b -> c -> d"
91− assertRight (parseTypeQuery input) (QFun (QAny "a")
92− (QFun (QAny "b")
93− (QFun (QAny "c") (QAny "d"))))
90+ assertRight (parseTypeQuery input)
91+ ( QFun (qVar "a")
92+ ( QFun (qVar "b")
93+ (QFun (qVar "c") (qVar "d"))
94+ )
95+ )
9496
95− test "test #11.1" do
97+ it "test #15" do
9698 let input = "a -> b -> c"
97− assertRight (parseTypeQuery input) (QFun (QAny "a")
98− (QFun (QAny "b")
99− (QAny "c")))
99+ assertRight (parseTypeQuery input)
100+ ( QFun (qVar "a")
101+ ( QFun (qVar "b")
102+ (qVar "c")
103+ )
104+ )
100105
101− test "test #12" do
106+ it "test #16" do
102107 let input = "forall a b c. c"
103− assertRight (parseTypeQuery input) (QForAll (nl "a" ["b", "c"]) (QAny "c"))
108+ assertRight (parseTypeQuery input) (QForAll (nl "a" [ "b", "c" ]) (qVar "c"))
104109
105− test "test #13" do
110+ it "test #17" do
106111 let input = "forall a. Maybe a"
107− assertRight (parseTypeQuery input) (QForAll (nl "a" $ []) (QApp (QConcrete "Maybe") (QAny "a")))
112+ assertRight (parseTypeQuery input) (QForAll (nl "a" $ []) (QApp (qConst "Maybe") (qVar "a")))
108113
109− test "test #14" do
114+ it "test #18" do
110115 let input = "forall m a. Monad m => a -> m a"
111116 assertRight (parseTypeQuery input)
112− (QForAll (nl "m" ["a"])
113− (QConstraint "Monad" (l ["m"])
114− (QFun (QAny "a")
115− (QApp (QAny "m") (QAny "a")))))
117+ ( QForAll (nl "m" [ "a" ])
118+ ( qConstraint "Monad" (l [ qVar "m" ])
119+ ( QFun (qVar "a")
120+ (QApp (qVar "m") (qVar "a"))
121+ )
122+ )
123+ )
116124
117− test "test #15" do
125+ it "test #19" do
118126 let input = "{ a :: Int }"
119127 assertRight (parseTypeQuery input)
120− (QRow (pure (Tuple "a" (QConcrete "Int"))))
128+ (QApp (qConst "Record") (QRow (pure (Tuple (Identifier "a") (qConst "Int")))))
121129
122− test "test #16" do
130+ it "test #20" do
123131 let input = "{a::Int}"
124132 assertRight (parseTypeQuery input)
125− (QRow (pure (Tuple "a" (QConcrete "Int"))))
133+ (QApp (qConst "Record") (QRow (pure (Tuple (Identifier "a") (qConst "Int")))))
126134
127− test "test #17" do
135+ it "test #21" do
128136 let input = "Int"
129− assertRight (parseTypeQuery input) (QConcrete "Int")
137+ assertRight (parseTypeQuery input) (qConst "Int")
130138
131− test "test #18" do
139+ it "test #22" do
132140 let input = "a->b"
133− assertRight (parseTypeQuery input) (QFun (QAny "a") (QAny "b"))
141+ assertRight (parseTypeQuery input) (QFun (qVar "a") (qVar "b"))
142+
143+ it "test #23" do
144+ let input = "forall m a. MonadRec m => Process m a -> m a"
145+ assertRight (parseTypeQuery input)
146+ ( QForAll (nl "m" ("a" : Nil))
147+ ( qConstraint "MonadRec" (l [ qVar "m" ])
148+ ( QFun
149+ ( QApp
150+ ( QApp (qConst "Process")
151+ (qVar "m")
152+ )
153+ (qVar "a")
154+ )
155+ (QApp (qVar "m") (qVar "a"))
156+ )
157+ )
158+ )
159+
160+ it "test #24" do
161+ let input = "forall t f a. Foldable1 t => Apply f => f"
162+ assertRight (parseTypeQuery input)
163+ ( QForAll (nl "t" [ "f", "a" ])
164+ ( qConstraint "Foldable1" (l [ qVar "t" ])
165+ (qConstraint "Apply" (l [ qVar "f" ]) (qVar "f"))
166+ )
167+ )
168+
169+ it "test #25" do
170+ let input = "forall m a.MonadRec m=>Process m a->m a"
171+ assertRight (parseTypeQuery input)
172+ ( ( QForAll (nl "m" ("a" : Nil))
173+ ( qConstraint "MonadRec" (l [ qVar "m" ])
174+ ( QFun
175+ ( QApp
176+ ( QApp (qConst "Process")
177+ (qVar "m")
178+ )
179+ (qVar "a")
180+ )
181+ (QApp (qVar "m") (qVar "a"))
182+ )
183+ )
184+ )
185+ )
186+
187+ it "test #26" do
188+ let input = "m a -> (a -> m b) -> m b"
189+ assertRight (parseTypeQuery input) (QFun (QApp (qVar "m") (qVar "a")) (QFun (QFun (qVar "a") (QApp (qVar "m") (qVar "b"))) (QApp (qVar "m") (qVar "b"))))
190+
191+ it "test #27" do
192+ let input = "forall f a. Alternative f => Lazy (f (List a)) => f a -> f (List a)"
193+ assertRight (parseTypeQuery input)
194+ ( (QForAll (nl "f" [ "a" ]))
195+ ( qConstraint "Alternative" (l [ qVar "f" ])
196+ ( qConstraint "Lazy"
197+ ( l
198+ [ QApp (qVar "f")
199+ (QApp (qConst "List") (qVar "a"))
200+ ]
201+ )
202+ ( QFun (QApp (qVar "f") (qVar "a"))
203+ ( QApp (qVar "f")
204+ (QApp (qConst "List") (qVar "a"))
205+ )
206+ )
207+ )
208+ )
209+ )
210+
211+ it "test #28" do
212+ let input = "forall f a. Alternative f => Lazy(f (List a))=>f a -> f (List a)"
213+ assertRight (parseTypeQuery input)
214+ ( (QForAll (nl "f" [ "a" ]))
215+ ( qConstraint "Alternative" (l [ qVar "f" ])
216+ ( qConstraint "Lazy"
217+ ( l
218+ [ QApp (qVar "f")
219+ (QApp (qConst "List") (qVar "a"))
220+ ]
221+ )
222+ ( QFun (QApp (qVar "f") (qVar "a"))
223+ ( QApp (qVar "f")
224+ (QApp (qConst "List") (qVar "a"))
225+ )
226+ )
227+ )
228+ )
229+ )
230+
231+ it "test #29" do
232+ let input = "{a::Int,b::Int}"
233+ assertRight (parseTypeQuery input)
234+ ( QApp (qConst "Record")
235+ ( QRow
236+ ( List.fromFoldable
237+ [ Tuple (Identifier "a") (qConst "Int")
238+ , Tuple (Identifier "b") (qConst "Int")
239+ ]
240+ )
241+ )
242+ )
243+
244+ it "test #30" do
245+ let input = "{record''' :: Int}"
246+ assertRight (parseTypeQuery input)
247+ (QApp (qConst "Record") (QRow (List.fromFoldable [ Tuple (Identifier "record'''") (qConst "Int") ])))
248+
249+ it "test #31" do
250+ let input = "(row''' :: Int)"
251+ assertRight (parseTypeQuery input)
252+ (QRow (List.fromFoldable [ Tuple (Identifier "row'''") (qConst "Int") ]))
253+
254+ it "test #32" do
255+ let input = "(row1 :: Int, row2 :: (),row3::(row4::{}))"
256+ assertRight (parseTypeQuery input)
257+ ( QRow
258+ ( l
259+ [ Tuple (Identifier "row1") (qConst "Int")
260+ , Tuple (Identifier "row2") (QRow Nil)
261+ , Tuple (Identifier "row3") (QRow (l [ Tuple (Identifier "row4") (QApp (qConst "Record") (QRow Nil)) ]))
262+ ]
263+ )
264+ )
265+
266+ it "test #33" do
267+ let input = "Foldable1 t => Apply f => t (f a) -> f Unit"
268+ assertRight (parseTypeQuery input)
269+ (qConstraint "Foldable1" ((qVar "t") : Nil) (qConstraint "Apply" ((qVar "f") : Nil) (QFun (QApp (qVar "t") (QApp (qVar "f") (qVar "a"))) (QApp (qVar "f") (qConst "Unit")))))
270+
271+ it "test #34" do
272+ let input = "Foldable1 t => Apply f => t (f a) -> f a"
273+ assertRight (parseTypeQuery input)
274+ (qConstraint "Foldable1" ((qVar "t") : Nil) (qConstraint "Apply" ((qVar "f") : Nil) (QFun (QApp (qVar "t") (QApp (qVar "f") (qVar "a"))) (QApp (qVar "f") (qVar "a")))))
275+
276+ it "test #35" do
277+ let input = "Generic a rep => GenericEq rep => a -> a -> Boolean"
278+ assertRight (parseTypeQuery input)
279+ ( qConstraint "Generic" ((qVar "a") : (qVar "rep") : Nil)
280+ ( qConstraint "GenericEq" ((qVar "rep") : Nil)
281+ (QFun (qVar "a") (QFun (qVar "a") (qConst "Boolean")))
282+ )
283+ )
284+
285+ describe "polish notation" do
286+
287+ it "test #1" do
288+ let input = "(a -> b) -> (b -> ((a -> b) -> c))"
289+ assertRight (shapeOfTypeQuery <$> parseTypeQuery input)
290+ ( l
291+ [ PForAll 3
292+ , PFun
293+ , PFun
294+ , PVar
295+ , PVar
296+ , PFun
297+ , PVar
298+ , PFun
299+ , PFun
300+ , PVar
301+ , PVar
302+ , PVar
303+ ]
304+ )
305+
306+ it "test #2" do
307+ let input = "forall a. (a -> b) -> (b -> ((a -> b) -> c))"
308+ assertRight (shapeOfTypeQuery <$> parseTypeQuery input)
309+ ( l
310+ [ PForAll 3
311+ , PFun
312+ , PFun
313+ , PVar
314+ , PVar
315+ , PFun
316+ , PVar
317+ , PFun
318+ , PFun
319+ , PVar
320+ , PVar
321+ , PVar
322+ ]
323+ )
324+
325+ it "test #3" do
326+ let input = "forall a. (a -> b) -> (b -> ((a -> b) -> c))"
327+ assertRight (shapeOfTypeQuery <$> parseTypeQuery input)
328+ ( l
329+ [ PForAll 3
330+ , PFun
331+ , PFun
332+ , PVar
333+ , PVar
334+ , PFun
335+ , PVar
336+ , PFun
337+ , PFun
338+ , PVar
339+ , PVar
340+ , PVar
341+ ]
342+ )
343+
344+ it "test #4" do
345+ let input = "forall a. (forall h. ST h (STArray h a)) -> Array a"
346+ assertRight (shapeOfTypeQuery <$> parseTypeQuery input)
347+ (l [ PForAll 1, PFun, PForAll 1, PApp, PApp, PVar, PVar, PApp, PApp, PVar, PVar, PVar, PApp, PVar, PVar ])
348+
349+ describe "type shapes" do
350+ it "test #1" do
351+ let
352+ query = "Generic a rep => GenericEq rep => a -> a -> Boolean"
353+ c1 = constr (qname [ "" ] "Generic") [ TypeVar "a", TypeVar "rep" ]
354+ c2 = constr (qname [ "" ] "GenericEq") [ TypeVar "rep" ]
355+
356+ fun t1 t2 =
357+ TypeApp
358+ ( TypeApp
359+ ( TypeConstructor
360+ ( QualifiedName
361+ { moduleNameParts: [ "Prim" ]
362+ , name: Identifier "Function"
363+ }
364+ )
365+ )
366+ t1
367+ )
368+ t2
369+ type_ =
370+ ForAll "a" Nothing
371+ $ ForAll "rep" Nothing
372+ $
373+ ConstrainedType c1
374+ ( ConstrainedType c2
375+ ( fun (TypeVar "a")
376+ ( fun (TypeVar "b")
377+ (TypeConstructor $ qname [ "Prim", "Boolean" ] "Boolean")
378+ )
379+ )
380+ )
381+ shape = shapeOfTypeQuery <$> parseTypeQuery query
382+ pure (shapeOfType type_) `shouldEqual` shape
383+
384+ describe "free variable counting" do
385+
386+ it "test #1" do
387+ let input = "forall a. (a -> b) -> (b -> ((a -> b) -> c))"
388+ assertRight (countFreeVars <$> parseTypeQuery input) 2
389+
390+ it "test #2" do
391+ -- `b` is not bound on the left, `a` is not bound on the right
392+ let input = "(forall a. (a -> b)) -> forall b. (b -> a)"
393+ assertRight (countFreeVars <$> parseTypeQuery input) 2
394+
395+ it "test #3" do
396+ let input = "a -> forall a. a"
397+ assertRight (countFreeVars <$> parseTypeQuery input) 1
398+
399+ it "test #4" do
400+ let input = "(forall a. a) -> a"
401+ assertRight (countFreeVars <$> parseTypeQuery input) 1
402+
403+ it "test #5" do
404+ let input = "forall a. a -> a"
405+ assertRight (countFreeVars <$> parseTypeQuery input) 0
406+
407+ it "test #6" do
408+ let input = "a -> b -> c"
409+ assertRight (countFreeVars <$> parseTypeQuery input) 3
410+
411+ it "test #7" do
412+ let input = "forall m a. Monad m => a -> m a"
413+ assertRight (countFreeVars <$> parseTypeQuery input) 0
414+
415+ it "test #8" do
416+ let input = "Monad m => a -> m a"
417+ assertRight (countFreeVars <$> parseTypeQuery input) 2
418+
419+ it "test #9" do
420+ let input = "Monad m => a -> a"
421+ assertRight (countFreeVars <$> parseTypeQuery input) 2
422+
423+ it "test #10" do
424+ let input = "forall a. (forall a. a) a"
425+ assertRight (countFreeVars <$> parseTypeQuery input) 0
426+
427+ it "test #11" do
428+ let input = "forall a. (forall b. a) a"
429+ assertRight (countFreeVars <$> parseTypeQuery input) 0
430+
431+ it "test #12" do
432+ let input = "forall a. (forall b. a) a b"
433+ assertRight (countFreeVars <$> parseTypeQuery input) 1
434+
435+ describe "typeVarPenalty" do
436+
437+ it "#0" do
438+ 0 `shouldEqual` (typeVarPenalty mempty)
439+
440+ it "#1" do
441+ 0 `shouldEqual`
442+ ( typeVarPenalty $ l
443+ [ substitute "a" "b"
444+ , substitute "b" "a"
445+ ]
446+ )
447+
448+ it "#2" do
449+ 0 `shouldEqual`
450+ ( typeVarPenalty $ l
451+ [ substitute "a" "b"
452+ , substitute "a" "b"
453+ , substitute "a" "b"
454+ ]
455+ )
456+
457+ it "#3" do
458+ 1 `shouldEqual`
459+ ( typeVarPenalty $ l
460+ [ substitute "a" "b"
461+ , substitute "a" "c"
462+ ]
463+ )
464+
465+ it "#4" do
466+ 1 `shouldEqual`
467+ ( typeVarPenalty $ l
468+ [ substitute "a" "b"
469+ , substitute "b" "a"
470+ , substitute "b" "c"
471+ ]
472+ )
473+
474+ it "#5" do
475+ 0 `shouldEqual`
476+ ( typeVarPenalty $ l
477+ [ substitute "a" "b"
478+ , substitute "b" "c"
479+ , substitute "c" "a"
480+ ]
481+ )
482+
483+ it "#6" do
484+ 2 `shouldEqual`
485+ ( typeVarPenalty $ l
486+ [ substitute "a" "b"
487+ , substitute "a" "c"
488+ , substitute "a" "a"
489+ ]
490+ )
491+
492+ it "#7" do
493+ 2 `shouldEqual`
494+ ( typeVarPenalty $ l
495+ [ substitute "a" "a"
496+ , substitute "b" "a"
497+ , substitute "c" "a"
498+ ]
499+ )
500+
501+ it "#8" do
502+ 4 `shouldEqual`
503+ ( typeVarPenalty $ l
504+ [ substitute "a" "a"
505+ , substitute "b" "a"
506+ , substitute "c" "a"
507+ , substitute "a" "b"
508+ , substitute "a" "c"
509+ , substitute "a" "a"
510+ ]
511+ )
512+
513+ it "#9" do
514+ 0 `shouldEqual`
515+ ( typeVarPenalty $ l
516+ [ substitute "a" "e"
517+ , substitute "b" "d"
518+ , substitute "c" "f"
519+ ]
520+ )
521+
522+ describe "unification" do
523+ it "instantiation #0" do
524+ let
525+ mVarQuery = qVar "m"
526+ unitConstQuery = qConst "Unit"
527+
528+ shouldSatisfy
529+ (penalty unitConstQuery unitType)
530+ (_ < penalty mVarQuery unitType)
531+
532+ it "generalization #0" do
533+ let
534+ query = qVar "m"
535+ t1 = TypeVar "m"
536+
537+ shouldSatisfy
538+ (penalty query unitType)
539+ (_ > penalty query t1)
134540
135541l :: forall f. Foldable f => (forall a. f a -> List a)
136542l = List.fromFoldable
137543
138−nl :: forall t5 t6. Foldable t6 => t5 -> t6 t5 -> NonEmptyList t5
139−nl x rst = NonEmptyList.cons' x $ List.fromFoldable rst
140−
141−assertRight
142− :: forall a b
143− . Show a
144− => Show b
145− => Eq a
146− => Eq b
147− => Either b a
148− -> a
149− -> Aff Unit
150−assertRight eiActual expected =
151− case eiActual of
152− Left string -> do
153− Assert.equal (Right expected) eiActual
154− Right actual -> do
155− Assert.equal (Right expected) eiActual
544+nl
545+ :: forall t
546+ . Foldable t
547+ => Functor t
548+ => String
549+ -> t String
550+ -> NonEmptyList Identifier
551+nl x rst = NonEmptyList.cons' (Identifier x) $ List.fromFoldable (rst <#> Identifier)
552+
553+unitType :: Type
554+unitType = TypeConstructor
555+ ( QualifiedName
556+ { moduleNameParts: []
557+ , name: Identifier "Unit"
558+ }
559+ )
560+
561+countFreeVars :: TypeQuery -> Int
562+countFreeVars = getFreeVariables >>> Set.size
563+
564+qname :: Array String -> String -> QualifiedName
565+qname m n = QualifiedName { moduleNameParts: m, name: Identifier n }
566+
567+constr :: QualifiedName -> Array Type -> Constraint
568+constr c a = Constraint { constraintClass: c, constraintArgs: a }
569+
570+qVar :: String -> TypeQuery
571+qVar = QVar <<< Identifier
572+
573+qConst :: String -> TypeQuery
574+qConst = QConst <<< Identifier
575+
576+qConstraint :: String -> List TypeQuery -> TypeQuery -> TypeQuery
577+qConstraint = QConstraint <<< Identifier
578+
579+substitute :: String -> String -> Substitution
580+substitute a b = Substitute (Identifier a) (Identifier b)
test/Test/UI.pursnew file+68
@@ -0,0 +1,68 @@
1+module Test.UI where
2+
3+-- import Prelude
4+
5+-- import Data.Array as Array
6+-- import Data.Foldable (for_)
7+-- import Data.Newtype (unwrap)
8+-- import Effect (Effect)
9+-- import Effect.Aff (Aff, launchAff_)
10+-- import Effect.Class (liftEffect)
11+-- import Effect.Console as Console
12+-- import Foreign (Foreign)
13+-- import Foreign as Foreign
14+-- import Node.FS.Sync (realpath)
15+-- import Test.Spec.Assertions (shouldSatisfy)
16+-- -- import Toppokki as T
17+-- -- import Playwright as P
18+
19+-- main :: Effect Unit
20+-- main = launchAff_ do
21+-- pure unit
22+-- path <- liftEffect $ realpath "."
23+
24+-- let indexHTML = T.URL $ "file://" <> path <> "/generated-docs/html/index.html"
25+-- prim = T.URL $ "file://" <> path <> "/generated-docs/html/Prim.html"
26+-- docsSearch = T.URL $ "file://" <> path <> "/generated-docs/html/Docs.Search.App.html"
27+
28+-- for_ [ indexHTML, prim, docsSearch ] \url -> do
29+-- withPage url \page -> do
30+-- void $ T.pageWaitForSelector (T.Selector "#group-modules__label") { timeout: 10000 } page
31+-- log $ "has module grouping: " <> unwrap url
32+
33+-- withPage url \page -> do
34+-- T.keyboardPress (T.KeyboardKey "s") {} page
35+-- void $ T.keyboardType "slice" {} page
36+-- T.keyboardPress (T.KeyboardKey "Enter") {} page
37+-- void $ T.pageWaitForSelector (T.Selector ".result__actions__item") { timeout: 10000 } page
38+-- log $ "has working search field: " <> unwrap url
39+
40+-- withPage url \page -> do
41+-- T.keyboardPress (T.KeyboardKey "s") {} page
42+-- void $ T.keyboardType "a -> b" {} page
43+-- T.keyboardPress (T.KeyboardKey "Enter") {} page
44+-- void $ T.pageWaitForSelector (T.Selector ".result__actions__item") { timeout: 10000 } page
45+-- arr <- readStringArray <$> T.unsafeEvaluateStringFunction getResultTitlesCode page
46+-- arr `shouldSatisfy` (Array.elem "unsafeCoerce")
47+-- log $ "is able to find unsafeCoerce by type: " <> unwrap url
48+
49+-- withPage (T.URL $ unwrap url <> "#search:unsafeCoerce") \page -> do
50+-- void $ T.pageWaitForSelector (T.Selector ".result__actions__item") { timeout: 10000 } page
51+-- arr <- readStringArray <$> T.unsafeEvaluateStringFunction getResultTitlesCode page
52+-- arr `shouldSatisfy` (Array.elem "unsafeCoerce")
53+-- log $ "can read URI hash: " <> unwrap url
54+
55+-- where
56+-- log = liftEffect <<< Console.log
57+-- getResultTitlesCode = "[].map.call(document.querySelectorAll('.result__title'), el => el.textContent)"
58+-- readStringArray :: Foreign -> Array String
59+-- readStringArray = Foreign.unsafeFromForeign
60+
61+
62+-- withPage :: forall a. T.URL -> (T.Page -> Aff a) -> Aff Unit
63+-- withPage url f = do
64+-- browser <- T.launch {}
65+-- page <- T.newPage browser
66+-- T.goto url page
67+-- void $ f page
68+-- T.close browser