git.iko.soy/rowtype-yoga/purescript-docs-search
on master help sign in
58 files changed+7203−1550
since splitsnapshots
unifiedsplit
.github/workflows/build.yml+44 .github/workflows/release.yml+55 .gitignore+2 .travis.yml−34 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−32 src/Docs/Search/App.purs+185−25 src/Docs/Search/App/SearchField.purs+109−21 src/Docs/Search/App/SearchResults.purs+241−158 src/Docs/Search/App/Sidebar.purs+218 src/Docs/Search/Index.js → src/Docs/Search/BrowserEngine.js+1−1 src/Docs/Search/Index.purs → src/Docs/Search/BrowserEngine.purs+49−23 src/Docs/Search/Config.purs+80−30 src/Docs/Search/Declarations.purs+186−128 src/Docs/Search/DocsJson.purs+28−18 src/Docs/Search/Engine.purs+167 src/Docs/Search/Extra.purs+34−2 src/Docs/Search/IndexBuilder.js+13 src/Docs/Search/IndexBuilder.purs+333−98 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+35−19 src/Docs/Search/Terminal.purs+23 src/Docs/Search/TypeDecoder.purs+115−93 src/Docs/Search/TypeIndex.js+1−1 src/Docs/Search/TypeIndex.purs+57−46 src/Docs/Search/TypePrinter.purs+169 src/Docs/Search/TypeQuery.purs+179−103 src/Docs/Search/TypeShape.js+1−1 src/Docs/Search/TypeShape.purs+20−5 src/Docs/Search/Types.purs+104 src/Docs/Search/URIHash.js+17 src/Docs/Search/URIHash.purs+38 test/Main.purs+18−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+438−246 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/
.travis.ymldeleted−34
@@ -1,34 +0,0 @@
1−language: node_js
2−dist: trusty
3−sudo: required
4−node_js: stable
5−
6−env:
7− matrix:
8− - PATH=$HOME/purescript:$PATH
9−
10−install:
11−- export PURS_VERSION=v0.13.2
12−- wget -O $HOME/purescript.tar.gz https://github.com/purescript/purescript/releases/download/$PURS_VERSION/linux64.tar.gz
13−- tar -xvf $HOME/purescript.tar.gz -C $HOME/
14−- chmod a+x $HOME/purescript
15−- npm install -g spago
16−- spago install
17−
18−script:
19−- spago build
20−- spago test
21−- spago docs
22−- spago bundle-app -m Docs.Search.App --to generated-docs/docs-search-app.js
23−- spago run -m Docs.Search.IndexBuilder
24−
25−deploy:
26− - provider: releases
27− api_key: $API_KEY
28− file:
29− - generated-docs/docs-search-app.js
30− skip_cleanup: true
31− on:
32− tags: true
33− script:
34− - echo 'done'
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 @@
11# 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−To see it in action, run the following:
9+## Installing
10+
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 Docs.Search.App --to generated-docs/docs-search-app.js
15−spago run -m Docs.Search.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−32
@@ -1,34 +1,55 @@
1−{ name =
2− "docs-search"
1+{ sources = [ "src/**/*.purs", "test/**/*.purs" ]
2+, name = "docs-search"
33, dependencies =
4− [ "aff-promise"
5− , "argonaut-codecs"
6− , "argonaut-core"
7− , "argonaut-generic"
8− , "arrays"
9− , "console"
10− , "control"
11− , "coroutines"
12− , "effect"
13− , "foldable-traversable"
14− , "generics-rep"
15− , "halogen"
16− , "halogen-css"
17− , "lists"
18− , "maybe"
19− , "newtype"
20− , "node-buffer"
21− , "node-fs"
22− , "node-fs-aff"
23− , "node-process"
24− , "profunctor"
25− , "search-trie"
26− , "string-parsers"
27− , "strings"
28− , "test-unit"
29− , "web-dom"
30− , "web-html"
31− ]
32−, packages =
33− ./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
3455}
src/Docs/Search/App.purs+185−25
@@ -1,45 +1,127 @@
1+-- | This is the main module of the client-side Halogen app.
12module Docs.Search.App where
23
3−import Prelude
4−
54import Docs.Search.App.SearchField as SearchField
65import Docs.Search.App.SearchResults as SearchResults
6+import Docs.Search.App.Sidebar as Sidebar
7+import Docs.Search.Config as Config
78import 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
812
9−import Control.Coroutine as Coroutine
13+import Prelude
14+
15+import Control.Alt (alt)
1016import Data.Maybe (Maybe(..))
1117import Data.Newtype (wrap)
18+import Data.Map as Map
19+import Data.Tuple (Tuple(..))
20+import Data.Tuple.Nested ((/\))
1221import Effect (Effect)
22+import Effect.Aff (launchAff_)
1323import Halogen as H
1424import Halogen.Aff as HA
25+import Halogen.Subscription (subscribe)
1526import Halogen.VDom.Driver (runUI)
27+import MarkdownIt as MD
28+import Web.DOM.ChildNode as ChildNode
1629import Web.DOM.Document as Document
30+import Web.DOM.Document (Document)
1731import Web.DOM.Element as Element
1832import Web.DOM.Node as Node
1933import Web.DOM.ParentNode as ParentNode
2034import Web.DOM.Text as Text
35+import Web.Event.EventTarget (addEventListener, eventListener)
2136import Web.HTML as HTML
37+import Web.HTML.Event.EventTypes (focus)
38+import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange)
2239import Web.HTML.HTMLDocument as HTMLDocument
2340import Web.HTML.HTMLElement (fromElement)
2441import Web.HTML.Window as Window
2542
43+
2644main :: Effect Unit
2745main = do
28− win <- HTML.window
29− doc <- HTMLDocument.toDocument <$> Window.document win
46+ window <- HTML.window
3047
31− insertStyle doc
32− mbContainers <- getContainers doc
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
3365
34− whenJust mbContainers \ { searchField, searchResults, pageContents } -> do
3566 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+
3685 sfio <- runUI SearchField.component unit searchField
37− srio <- runUI (SearchResults.mkComponent pageContents) unit searchResults
38− sfio.subscribe $
39− Coroutine.consumer (srio.query <<< H.tell <<< SearchResults.MessageFromSearchField)
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
40124
41−insertStyle :: Document.Document -> Effect Unit
42−insertStyle doc = do
43125 let styleContents = """
44126 .top-banner__actions {
45127 width: 10%;
@@ -67,21 +149,76 @@ insertStyle doc = do
67149 /* Add a margin between badge icons and package/module names. */
68150 margin-right: 0.25em;
69151 }
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+ }
70177 """
178+
71179 mbHead <-
72180 ParentNode.querySelector (wrap "head") (Document.toParentNode doc)
73181
74182 whenJust mbHead \head -> do
75−
76183 contents <- Document.createTextNode styleContents doc
77184 style <- Document.createElement "style" doc
78185 void $ Node.appendChild (Text.toNode contents) (Element.toNode style)
79186 void $ Node.appendChild (Element.toNode style) (Element.toNode head)
80187
81−getContainers :: Document.Document -> Effect (Maybe { searchField :: HTML.HTMLElement
82− , searchResults :: HTML.HTMLElement
83− , pageContents :: Element.Element })
84−getContainers doc = do
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
85222 let docPN = Document.toParentNode doc
86223 mbBanner <-
87224 ParentNode.querySelector (wrap ".top-banner > .container") docPN
@@ -89,12 +226,35 @@ getContainers doc = do
89226 ParentNode.querySelector (wrap ".everything-except-footer") docPN
90227 mbContainer <-
91228 ParentNode.querySelector (wrap ".everything-except-footer > .container") docPN
92− case mbBanner, mbEverything, mbContainer of
93− Just banner, Just everything, Just pageContents -> do
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
94243 search <- Document.createElement "div" doc
95244 void $ Node.appendChild (Element.toNode search) (Element.toNode banner)
96− pure $ fromElement search >>= \searchField ->
97− fromElement everything >>= \searchResults ->
98− pure { searchField, searchResults, pageContents }
99− _, _, _ ->
100− pure Nothing
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.purs+109−21
@@ -1,24 +1,45 @@
1+-- | This module contains a Halogen component for search field, that emits
2+-- | `SearchFieldMessage`s for various events.
13module Docs.Search.App.SearchField where
24
35import Prelude
46
5−import CSS hiding (render, map)
6−import Data.Maybe (Maybe(..))
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)
712import Effect.Aff (Aff)
813import Halogen as H
914import Halogen.HTML as HH
1015import Halogen.HTML.CSS as HS
1116import Halogen.HTML.Events as HE
1217import 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
1328import Web.UIEvent.KeyboardEvent as KeyboardEvent
29+import Web.UIEvent.KeyboardEvent.EventTypes as KET
1430
15−type State = { input :: String }
31+type State = { input :: String, focused :: Boolean }
1632
1733data Action
1834 = InputAction String
1935 | EnterPressed
20− | EscapePressed
2136 | FocusChanged Boolean
37+ | InitKeyboardListener
38+ | HandleKey H.SubscriptionId KeyboardEvent
39+ | NoOp
40+
41+data Query a
42+ = ReadURIHash a
2243
2344data SearchFieldMessage
2445 = InputUpdated String
@@ -26,32 +47,99 @@ data SearchFieldMessage
2647 | Focused
2748 | LostFocus
2849
29−component :: forall q i. H.Component HH.HTML q i SearchFieldMessage Aff
50+component :: forall i. H.Component Query i SearchFieldMessage Aff
3051component =
3152 H.mkComponent
3253 { initialState
3354 , render
34− , eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
55+ , eval: H.mkEval $ H.defaultEval { handleAction = handleAction
56+ , handleQuery = handleQuery
57+ , initialize = Just InitKeyboardListener }
3558 }
3659
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+
3772initialState :: forall i. i -> State
38−initialState _ = { input: "" }
73+initialState _ = { input: "", focused: false }
3974
40−handleAction :: forall m. Action -> H.HalogenM State Action () SearchFieldMessage m Unit
75+handleAction :: Action -> H.HalogenM State Action () SearchFieldMessage Aff Unit
4176handleAction = 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+
42104 InputAction input -> do
43− H.modify_ $ const { input }
105+ H.modify_ $ (_ { input = input })
106+
44107 EnterPressed -> do
45108 state <- H.get
109+ H.liftEffect do
110+ withSearchField (HTMLInputElement.toHTMLElement >>> HTMLElement.blur)
111+ H.liftEffect (URIHash.setInput state.input)
46112 H.raise $ InputUpdated state.input
47− EscapePressed -> do
48− H.modify_ (_ { input = "" })
49− H.raise $ InputCleared
50− FocusChanged status -> do
113+
114+ FocusChanged isFocused -> do
115+ H.modify_ (_ { focused = isFocused })
51116 H.raise
52− if status
117+ if isFocused
53118 then Focused
54119 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)
55143
56144render :: forall m. State -> H.ComponentHTML Action () m
57145render state =
@@ -67,16 +155,16 @@ render state =
67155
68156 [ HH.input
69157 [ HP.value state.input
70− , HP.placeholder "Search for definitions"
158+ , HP.placeholder "Search for definitions... (S to focus)"
159+ , HP.id "docs-search-query-field"
71160 , HP.type_ HP.InputText
72161 , HE.onKeyUp (\event ->
73162 case KeyboardEvent.code event of
74− "Enter" -> Just EnterPressed
75− "Escape" -> Just EscapePressed
76− _ -> Nothing)
77− , HE.onValueInput (Just <<< InputAction)
78− , HE.onFocusIn $ const $ Just $ FocusChanged true
79− , HE.onFocusOut $ const $ Just $ FocusChanged false
163+ "Enter" -> EnterPressed
164+ _ -> NoOp)
165+ , HE.onValueInput InputAction
166+ , HE.onFocusIn $ const $ FocusChanged true
167+ , HE.onFocusOut $ const $ FocusChanged false
80168 , HS.style do
81169
82170 let pursuitColor = rgb 0x1d 0x22 0x2d
src/Docs/Search/App/SearchResults.purs+241−158
@@ -1,87 +1,99 @@
1+-- | This module contains a Halogen component for search results.
12module Docs.Search.App.SearchResults where
23
3−import Prelude
4−
54import Docs.Search.App.SearchField (SearchFieldMessage(..))
6−import Docs.Search.Config (config)
5+import Docs.Search.BrowserEngine (PartialIndex, browserSearchEngine)
6+import Docs.Search.Config as Config
77import Docs.Search.Declarations (DeclLevel(..), declLevelToHashAnchor)
88import Docs.Search.DocsJson (DataDeclType(..))
9−import Docs.Search.Extra ((>#>))
10−import Docs.Search.Index (Index)
11−import Docs.Search.Index as Index
12−import Docs.Search.SearchResult (ResultInfo(..), SearchResult, typeOf)
13−import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), Kind(..), QualifiedName(..), Type(..), TypeArgument(..), joinForAlls, joinRows)
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)
1416import Docs.Search.TypeIndex (TypeIndex)
15−import Docs.Search.TypeIndex as TypeIndex
16−import Docs.Search.TypeQuery (TypeQuery(..), parseTypeQuery, penalty)
17+import Docs.Search.Types (Identifier(..), ModuleName(..), PackageName)
18+import Docs.Search.Meta (Meta)
1719
18−import CSS (textWhitespace, whitespacePreWrap)
20+import Prelude
21+import Prim hiding (Type, Constraint)
1922import Data.Array ((!!))
2023import Data.Array as Array
21−import Data.Either (hush)
2224import Data.List as List
23−import Data.Maybe (Maybe(..), isJust, maybe)
24−import Data.Newtype (unwrap, wrap)
25−import Data.String (length) as String
25+import Data.Maybe (Maybe(..), isJust, fromMaybe)
26+import Data.Newtype (wrap, unwrap)
2627import Data.String.CodeUnits (stripSuffix) as String
27−import Data.String.Common (toLower, trim) as String
28+import Data.String.Common (null, trim) as String
2829import Data.String.Pattern (Pattern(..)) as String
2930import Effect.Aff (Aff)
3031import Halogen as H
3132import Halogen.HTML as HH
32−import Halogen.HTML.CSS as HS
3333import Halogen.HTML.Events as HE
3434import Halogen.HTML.Properties as HP
35+import MarkdownIt as MD
36+import MarkdownIt.Renderer.Halogen as MDH
3537import Web.DOM.Element (Element)
3638import Web.DOM.Element as Element
3739import Web.HTML as HTML
3840import Web.HTML.Location as Location
3941import Web.HTML.Window as Window
4042
41−data Mode = Off | Loading | Active | InputTooShort
43+
44+data Mode = Off | Loading | Active
4245
4346derive instance eqMode :: Eq Mode
4447
45−-- | Is it a search by type or by name?
46−data ResultsType = TypeResults TypeQuery | DeclResults
4748
48−type State = { index :: Index
49− , typeIndex :: TypeIndex
50− , results :: Array SearchResult
51− , resultsType :: ResultsType
49+type EngineState =
50+ Engine.EngineState PartialIndex TypeIndex
51+
52+
53+type State = { engineState :: EngineState
54+ , results :: Array Result
5255 , input :: String
5356 , contents :: Element
5457 , resultsCount :: Int
5558 , mode :: Mode
59+ , markdownIt :: MD.MarkdownIt
60+ , localPackageName :: PackageName
5661 }
5762
63+
5864data Query a
5965 = MessageFromSearchField SearchFieldMessage a
6066
67+
6168data Action
62− = SearchResultClicked String
69+ = SearchResultClicked ModuleName
6370 | MoreResultsRequested
6471
72+
6573mkComponent
6674 :: forall o i
67− . Element
68− -> H.Component HH.HTML Query i o Aff
69−mkComponent contents =
75+ . EngineState
76+ -> Element
77+ -> MD.MarkdownIt
78+ -> Meta
79+ -> H.Component Query i o Aff
80+mkComponent initialEngineState contents markdownIt { localPackageName } =
7081 H.mkComponent
71− { initialState: const { index: mempty
72− , typeIndex: mempty
82+ { initialState: const { engineState: initialEngineState
7383 , results: []
74− , resultsType: DeclResults
7584 , input: ""
7685 , contents
77− , resultsCount: config.resultsCount
86+ , resultsCount: Config.resultsCount
7887 , mode: Off
88+ , markdownIt
89+ , localPackageName
7990 }
8091 , render
8192 , eval: H.mkEval $ H.defaultEval { handleQuery = handleQuery
8293 , handleAction = handleAction }
8394 }
8495
96+
8597handleQuery
8698 :: forall o a
8799 . Query a
@@ -99,41 +111,28 @@ handleQuery (MessageFromSearchField (InputUpdated input_) next) = do
99111
100112 state <- H.modify (_ { input = input })
101113
102− if String.length input < 2
114+ if String.null input
103115 then do
104− if input == ""
105− then do
106116 H.modify_ (_ { mode = Off })
107117 showPageContents
108− else do
109− H.modify_ (_ { mode = InputTooShort })
110− hidePageContents
111118 else do
112− H.modify_ (_ { mode = Loading, resultsCount = config.resultsCount })
119+ H.modify_ (_ { mode = Loading, resultsCount = Config.resultsCount })
113120
114121 void $ H.fork do
115− let resultsType =
116− maybe DeclResults TypeResults (hush (parseTypeQuery state.input)
117− >>= isValuableTypeQuery)
118−
119− case resultsType of
120−
121− DeclResults -> do
122− { index, results } <- H.liftAff $ Index.query state.index (String.toLower state.input)
123− H.modify_ (_ { results = results
124− , mode = Active
125− , index = index })
122+ { index, results } <- H.liftAff $
123+ Engine.query browserSearchEngine state.engineState state.input
126124
127− TypeResults query -> do
128− { index, results } <- H.liftAff $ TypeIndex.query state.typeIndex query
129− H.modify_ (_ { results = sortByDistance query results
130− , mode = Active
131− , typeIndex = index })
125+ H.modify_ (_ { results = results
126+ , mode = Active
127+ , engineState = index
128+ }
129+ )
132130
133131 hidePageContents
134132
135133 pure Nothing
136134
135+
137136handleAction
138137 :: forall o
139138 . Action
@@ -151,12 +150,14 @@ handleAction = case _ of
151150 location <- Window.location window
152151 pathname <- Location.pathname location
153152 pure $ isJust $
154− String.stripSuffix (String.Pattern $ moduleName <> ".html") pathname
153+ String.stripSuffix (String.Pattern $ unwrap moduleName <> ".html") pathname
155154
156155 when onThisPage do
157156 showPageContents
158157 H.modify_ (_ { input = "", mode = Off })
159158
159+
160+-- | Inverse of `hidePageContents`
160161showPageContents
161162 :: forall o
162163 . H.HalogenM State Action () o Aff Unit
@@ -165,13 +166,16 @@ showPageContents = do
165166 H.liftEffect do
166167 Element.removeAttribute "style" state.contents
167168
169+
170+-- | When search UI is active, we want to hide the main page contents.
168171hidePageContents
169172 :: forall o
170173 . H.HalogenM State Action () o Aff Unit
171174hidePageContents = do
172175 state <- H.get
173176 H.liftEffect do
174− Element.setAttribute "style" "display: none" state.contents
177+ Element.setAttribute "style" "display: none;" state.contents
178+
175179
176180render
177181 :: forall m
@@ -181,12 +185,6 @@ render { mode: Off } = HH.div_ []
181185render { mode: Loading } =
182186 renderContainer $
183187 [ HH.h1_ [ HH.text "Loading..." ] ]
184−render { mode: InputTooShort } =
185− renderContainer $
186− [ HH.h1_ [ HH.text "Error" ] ] <>
187− [ HH.div [ HP.classes [ wrap "result", wrap "result--empty" ] ]
188− [ HH.text "Search query is too short." ]
189− ]
190188render state@{ mode: Active, results: [] } =
191189 renderContainer $
192190
@@ -201,22 +199,13 @@ render state@{ mode: Active } =
201199 renderContainer $
202200 [ HH.h1_ [ HH.text "Search results" ]
203201
204− , HH.div [ HP.classes [ wrap "result" ] ] $
205− [ HH.text "Found "
206− , HH.strong_ [ HH.text $ show $ Array.length state.results ]
207− , HH.text $
208− case state.resultsType of
209− DeclResults -> " definitions."
210− TypeResults _ -> " definitions with similar types."
211− ]
212−
213202 , HH.div_ $
214− Array.concat $ shownResults <#> renderResult
203+ Array.concat $ shownResults <#> renderResult state
215204
216− , HH.div [ HP.class_ (wrap "load_more"), HP.id_ "load-more" ]
205+ , HH.div [ HP.class_ (wrap "load_more"), HP.id "load-more" ]
217206 [ if Array.length shownResults < Array.length state.results
218− then HH.a [ HP.id_ "load-more-link"
219− , HE.onClick $ const $ Just MoreResultsRequested ]
207+ then HH.a [ HP.id "load-more-link"
208+ , HE.onClick $ const MoreResultsRequested ]
220209 [ HH.text "Show more results" ]
221210 else HH.p_
222211 [ HH.text "No further results." ]
@@ -231,6 +220,7 @@ renderContainer =
231220 pure <<<
232221 HH.div [ HP.classes [ wrap "col", wrap "col--main" ] ]
233222
223+
234224renderSummary
235225 :: forall a b
236226 . String
@@ -238,31 +228,96 @@ renderSummary
238228renderSummary text =
239229 HH.div_ [ HH.text text ]
240230
231+
241232renderResult
242233 :: forall a
243− . SearchResult
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
244278 -> Array (HH.HTML a Action)
245−renderResult = unwrap >>> \result ->
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.
246305 [ HH.div [ HP.class_ (wrap "result") ]
247306 [ HH.h3 [ HP.class_ (wrap "result__title") ]
248307 [ HH.a [ HP.class_ (wrap "result__link")
249− , HE.onClick $ const $ Just $ SearchResultClicked result.moduleName
308+ , HE.onClick $ const $ SearchResultClicked result.moduleName
250309 , HP.href $
251− result.moduleName <> ".html#" <>
252− result.hashAnchor <> ":" <> result.name
310+ unwrap result.moduleName <> ".html#" <>
311+ result.hashAnchor <> ":" <> unwrap result.name
253312 ]
254− [ HH.text result.name ]
313+ [ HH.text $ unwrap result.name ]
255314 ]
256315 ]
257316
258317 , HH.div [ HP.class_ (wrap "result__body") ] $
259318 renderResultType result <>
260319
261− result.comments >#>
262− \comments -> [ HH.pre [ HS.style do
263− textWhitespace whitespacePreWrap ]
264− [ HH.text comments ]
265− ]
320+ result.comments >#> pure <<< MDH.render_ state.markdownIt
266321
267322 , HH.div [ HP.class_ (wrap "result__actions") ]
268323
@@ -273,7 +328,7 @@ renderResult = unwrap >>> \result ->
273328 , HP.title "Package"
274329 ]
275330 [ HH.text "P" ]
276− , HH.text result.packageName
331+ , HH.text $ packageInfoToString state.localPackageName result.packageInfo
277332 ]
278333
279334 , HH.span [ HP.class_ (wrap "result__actions__item") ]
@@ -283,27 +338,25 @@ renderResult = unwrap >>> \result ->
283338 , HP.title "Module"
284339 ]
285340 [ HH.text "M" ]
286− , HH.text result.moduleName
341+ , HH.text $ unwrap result.moduleName
287342 ]
288343 ]
289344 ]
290345
346+
291347renderResultType
292348 :: forall a rest
293349 . { info :: ResultInfo
294− , name :: String
295− , moduleName :: String
350+ , name :: Identifier
351+ , moduleName :: ModuleName
296352 | rest
297353 }
298354 -> Array (HH.HTML a Action)
299355renderResultType result =
356+
300357 case result.info of
301358 ValueResult { type: ty } ->
302− wrapSignature [ HH.a [ makeHref ValueLevel false result.moduleName result.name
303− , HE.onClick $ const $ Just $ SearchResultClicked result.moduleName ]
304− [ HH.text result.name ]
305− , HH.text " :: "
306− , renderType ty ]
359+ wrapSignature $ renderValueSignature result ty
307360
308361 TypeClassResult info ->
309362 wrapSignature $ renderTypeClassSignature info result
@@ -317,17 +370,35 @@ renderResultType result =
317370 TypeSynonymResult info ->
318371 wrapSignature $ renderTypeSynonymSignature info result
319372 _ -> []
373+
320374 where
321375 wrapSignature signature =
322376 [ HH.pre [ HP.class_ (wrap "result__signature") ] [ HH.code_ signature ] ]
323377
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+
324395renderTypeClassSignature
325396 :: forall a rest
326397 . { fundeps :: FunDeps
327398 , arguments :: Array TypeArgument
328399 , superclasses :: Array Constraint
329400 }
330− -> { name :: String, moduleName :: String | rest }
401+ -> { name :: Identifier, moduleName :: ModuleName | rest }
331402 -> Array (HH.HTML a Action)
332403renderTypeClassSignature { fundeps, arguments, superclasses } { name, moduleName } =
333404 [ keyword "class"
@@ -346,10 +417,10 @@ renderTypeClassSignature { fundeps, arguments, superclasses } { name, moduleName
346417 ]
347418 , space
348419 , HH.a [ makeHref TypeLevel false moduleName name
349− , HE.onClick $ const $ Just $
420+ , HE.onClick $ const $
350421 SearchResultClicked moduleName
351422 ]
352− [ HH.text name ]
423+ [ HH.text $ unwrap name ]
353424 , space
354425 ] <> (
355426 Array.intercalate [ space ] $
@@ -358,6 +429,7 @@ renderTypeClassSignature { fundeps, arguments, superclasses } { name, moduleName
358429 renderFunDeps fundeps
359430 )
360431
432+
361433renderFunDeps :: forall a. FunDeps -> Array (HH.HTML a Action)
362434renderFunDeps (FunDeps []) = []
363435renderFunDeps (FunDeps deps) =
@@ -370,25 +442,28 @@ renderFunDeps (FunDeps deps) =
370442 [ syntax " -> " ] <>
371443 Array.intercalate [ space ] (pure <<< HH.text <$> rhs)
372444
445+
373446-- | Insert type class name and arguments
374447renderTypeClassMemberSignature
375448 :: forall a rest
376449 . { type :: Type
377450 , typeClass :: QualifiedName
378− , typeClassArguments :: Array String
451+ , typeClassArguments :: Array TypeArgument
379452 }
380− -> { name :: String | rest }
453+ -> { name :: Identifier | rest }
381454 -> Array (HH.HTML a Action)
382455renderTypeClassMemberSignature { type: ty, typeClass, typeClassArguments } result =
383− [ HH.text result.name
456+ [ HH.text $ unwrap result.name
384457 , HH.text " :: "
385− , renderType ty ]
458+ , renderType ty
459+ ]
460+
386461
387462renderDataSignature
388463 :: forall a rest
389464 . { typeArguments :: Array TypeArgument
390465 , dataDeclType :: DataDeclType }
391− -> { name :: String | rest }
466+ -> { name :: Identifier | rest }
392467 -> Array (HH.HTML a Action)
393468renderDataSignature { typeArguments, dataDeclType } { name } =
394469 [ keyword
@@ -396,24 +471,25 @@ renderDataSignature { typeArguments, dataDeclType } { name } =
396471 NewtypeDataDecl -> "newtype"
397472 DataDataDecl -> "data"
398473 , space
399− , HH.text name
474+ , HH.text $ unwrap name
400475 , space
401476 , HH.span_ $
402477 Array.intercalate [ space ] $
403478 typeArguments <#> renderTypeArgument
404479 ]
405480
481+
406482renderTypeSynonymSignature
407483 :: forall a rest
408484 . { type :: Type
409485 , arguments :: Array TypeArgument
410486 }
411− -> { name :: String | rest }
487+ -> { name :: Identifier | rest }
412488 -> Array (HH.HTML a Action)
413489renderTypeSynonymSignature { type: ty, arguments } { name } =
414490 [ keyword "type"
415491 , space
416− , HH.text name
492+ , HH.text $ unwrap name
417493 , space
418494 , HH.span_ $
419495 Array.intercalate [ space ] $
@@ -424,40 +500,42 @@ renderTypeSynonymSignature { type: ty, arguments } { name } =
424500 , renderType ty
425501 ]
426502
503+
427504renderTypeArgument :: forall a. TypeArgument -> Array (HH.HTML a Action)
428505renderTypeArgument (TypeArgument { name, mbKind }) =
429506 case mbKind of
430507 Nothing ->
431− [ HH.text name ]
508+ [ HH.text $ name ]
432509 Just kind ->
433510 [ HH.text "("
434− , HH.text name
511+ , HH.text $ name
435512 , HH.text " :: "
436− , renderKind kind
513+ , renderType kind
437514 , HH.text ")"
438515 ]
439516
517+
440518renderType
441519 :: forall a
442520 . Type
443521 -> HH.HTML a Action
444522renderType = case _ of
445523 TypeVar str -> HH.text str
446− TypeLevelString str -> HH.text $ "(Text \"" <> str <> "\")" -- TODO: add escaping
524+ TypeLevelString str -> HH.text $ "\"" <> str <> "\"" -- TODO: add escaping
447525 TypeWildcard -> HH.text "_"
448526 TypeConstructor qname -> renderQualifiedName false TypeLevel qname
449527 TypeOp qname -> renderQualifiedName true TypeLevel qname
450528
451529 TypeApp (TypeApp (TypeConstructor
452− (QualifiedName { moduleName: [ "Prim" ]
453− , name: "Function" })) t1) t2 ->
530+ (QualifiedName { moduleNameParts: [ "Prim" ]
531+ , name: Identifier "Function" })) t1) t2 ->
454532 HH.span_ [ renderType t1
455533 , syntax " -> "
456534 , renderType t2
457535 ]
458536
459− TypeApp (TypeConstructor (QualifiedName { moduleName: [ "Prim" ]
460− , name: "Record" }))
537+ TypeApp (TypeConstructor (QualifiedName { moduleNameParts: [ "Prim" ]
538+ , name: Identifier "Record" }))
461539 row ->
462540 renderRow false row
463541
@@ -467,6 +545,9 @@ renderType = case _ of
467545 , renderType t2
468546 ]
469547
548+ KindApp t1 t2 ->
549+ HH.span_ [ renderType t1, space, renderType t2 ]
550+
470551 ty@(ForAll _ _ _) ->
471552 renderForAll ty
472553
@@ -480,6 +561,9 @@ renderType = case _ of
480561 ty@REmpty -> renderRow true ty
481562 ty@(RCons _ _ _) -> renderRow true ty
482563
564+ Kinded t1 t2 ->
565+ HH.span_ [ renderType t1, space, syntax "::", space, renderType t2 ]
566+
483567 BinaryNoParensType op t1 t2 ->
484568 HH.span_
485569 [ renderType t1
@@ -496,6 +580,7 @@ renderType = case _ of
496580 , HH.text ")"
497581 ]
498582
583+
499584renderForAll
500585 :: forall a
501586 . Type
@@ -506,14 +591,14 @@ renderForAll ty =
506591 [ keyword "forall" ] <>
507592
508593 ( Array.fromFoldable foralls.binders <#>
509− \ { var, mbKind } ->
594+ \ { name, mbKind } ->
510595 case mbKind of
511− Nothing -> HH.text (" " <> var)
596+ Nothing -> HH.text (" " <> name)
512597 Just kind ->
513− HH.span_ [ HH.text $ " (" <> var <> " "
598+ HH.span_ [ HH.text $ " (" <> name <> " "
514599 , syntax "::"
515600 , space
516− , renderKind kind
601+ , renderType kind
517602 , HH.text ")" ]
518603 ) <>
519604
@@ -522,6 +607,7 @@ renderForAll ty =
522607 where
523608 foralls = joinForAlls ty
524609
610+
525611renderRow
526612 :: forall a
527613 . Boolean
@@ -534,24 +620,38 @@ renderRow asRow =
534620
535621 if List.null rows
536622 then
537− [ HH.text $ if asRow then "()" else "{}" ]
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+ ]
538634 else
539635 [ HH.text opening ] <>
540636
541637 ( Array.intercalate [ HH.text ", " ] $ Array.fromFoldable $ rows <#>
542638 \entry ->
543− [ HH.span_ [ HH.text $ entry.row <> " :: "
639+ [ HH.span_ [ HH.text $ unwrap entry.row <> " :: "
544640 , renderType entry.ty ] ]
545641 ) <>
546642
547− case ty of
548− Just ty' -> [ HH.text " | ", renderType ty', HH.text closing ]
549− Nothing -> [ HH.text closing ]
643+ (ty >#> \ty' -> [ HH.text " | ", renderType ty' ]) <>
644+
645+ [ HH.text closing ]
550646
551647 where
552648 opening = if asRow then "( " else "{ "
553649 closing = if asRow then " )" else " }"
554650
651+ primRecord :: QualifiedName
652+ primRecord = QualifiedName { moduleNameParts: [ "Prim" ], name: Identifier "Record" }
653+
654+
555655renderConstraint
556656 :: forall a
557657 . Constraint
@@ -561,46 +661,41 @@ renderConstraint (Constraint { constraintClass, constraintArgs }) =
561661 [ renderQualifiedName false TypeLevel constraintClass, space ] <>
562662 Array.intercalate [ space ] (constraintArgs <#> \ty -> [ renderType ty ])
563663
664+
564665renderQualifiedName
565666 :: forall a
566667 . Boolean
567668 -> DeclLevel
568669 -> QualifiedName
569670 -> HH.HTML a Action
570−renderQualifiedName isInfix level (QualifiedName { moduleName, name })
671+renderQualifiedName isInfix level (QualifiedName { moduleNameParts, name })
571672 = if isBuiltIn then
572− HH.text name
673+ HH.text $ unwrap name
573674 else
574− HH.a [ HE.onClick $ const $ Just $
575− SearchResultClicked moduleNameString
576− , makeHref level isInfix moduleNameString name
675+ HH.a [ HE.onClick $ const $
676+ SearchResultClicked $ moduleName
677+ , makeHref level isInfix moduleName name
577678 ]
578− [ HH.text name ]
679+ [ HH.text $ unwrap name ]
579680 where
580− moduleNameString = Array.intercalate "." moduleName
581− isBuiltIn = moduleName !! 0 == Just "Prim"
681+ moduleName = ModuleName $ Array.intercalate "." $ moduleNameParts
682+ isBuiltIn = moduleNameParts !! 0 == Just "Prim"
582683
583−renderKind
584− :: forall a
585− . Kind ->
586− HH.HTML a Action
587−renderKind = case _ of
588− Row k1 -> HH.span_ [ HH.text "# ", renderKind k1 ]
589− FunKind k1 k2 -> HH.span_ [ renderKind k1, syntax " -> ", renderKind k2 ]
590− NamedKind qname -> renderQualifiedName false KindLevel qname
591684
685+-- | Construct a `href` property value w.r.t. `DeclLevel`.
592686makeHref
593687 :: forall t rest
594688 . DeclLevel
595689 -> Boolean
596− -> String
597− -> String
690+ -> ModuleName
691+ -> Identifier
598692 -> HH.IProp ( href :: String | rest ) t
599693makeHref level isInfix moduleName name =
600694 HP.href $
601− moduleName <> ".html#" <>
695+ unwrap moduleName <> ".html#" <>
602696 declLevelToHashAnchor level <> ":" <>
603− if isInfix then "type (" <> name <> ")" else name
697+ if isInfix then "type (" <> unwrap name <> ")" else unwrap name
698+
604699
605700keyword
606701 :: forall a
@@ -608,25 +703,13 @@ keyword
608703 -> HH.HTML a Action
609704keyword str = HH.span [ HP.class_ (wrap "keyword") ] [ HH.text str ]
610705
706+
611707syntax
612708 :: forall a
613709 . String
614710 -> HH.HTML a Action
615711syntax str = HH.span [ HP.class_ (wrap "syntax") ] [ HH.text str ]
616712
713+
617714space :: forall a b. HH.HTML a b
618715space = HH.text " "
619−
620−isValuableTypeQuery :: TypeQuery -> Maybe TypeQuery
621−isValuableTypeQuery (QVar _) = Nothing
622−isValuableTypeQuery (QConst _) = Nothing
623−isValuableTypeQuery query = Just query
624−
625−sortByDistance :: TypeQuery -> Array SearchResult -> Array SearchResult
626−sortByDistance typeQuery results =
627− _.result <$> Array.sortBy comparePenalties resultsWithPenalties
628− where
629− comparePenalties r1 r2 = compare r1.penalty r2.penalty
630− resultsWithPenalties = results <#>
631− \result -> { penalty: typeOf (unwrap result).info <#> penalty typeQuery
632− , result }
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/Index.js → src/Docs/Search/BrowserEngine.jsrenamed+1−1
@@ -1,6 +1,6 @@
11/* global exports */
22
3−exports.loadIndex_ = function (partId) {
3+export function loadIndex_ (partId) {
44 return function (url) {
55 return function () {
66 return new Promise(function(resolve, reject) {
src/Docs/Search/Index.purs → src/Docs/Search/BrowserEngine.pursrenamed+49−23
@@ -1,15 +1,22 @@
1−module Docs.Search.Index where
1+-- | A search engine that is used in the browser.
2+module Docs.Search.BrowserEngine where
23
3−import Prelude
4−
5−import Docs.Search.Config (config)
4+import Docs.Search.Config as Config
5+import Docs.Search.PackageIndex (queryPackageIndex)
6+import Docs.Search.Engine (Engine, EngineState, Index)
67import 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
714
15+import Data.Char as Char
816import Control.Promise (Promise, toAffE)
917import Data.Argonaut.Core (Json)
1018import Data.Argonaut.Decode (decodeJson)
1119import Data.Array as Array
12−import Data.Char as Char
1320import Data.Either (hush)
1421import Data.List (List, (:))
1522import Data.List as List
@@ -24,26 +31,30 @@ import Data.Tuple (Tuple(..))
2431import Effect (Effect)
2532import Effect.Aff (Aff, try)
2633
27−newtype Index
28− = Index (Map Int (Trie Char (List SearchResult)))
2934
30−derive instance newtypeIndex :: Newtype Index _
31−derive newtype instance semigroupIndex :: Semigroup Index
32−derive newtype instance monoidIndex :: Monoid Index
35+newtype PartialIndex
36+ = PartialIndex (Map PartId Index)
37+
38+derive instance newtypePartialIndex :: Newtype PartialIndex _
39+
40+type BrowserEngineState = EngineState PartialIndex TypeIndex
3341
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.
3447query
35− :: Index
48+ :: PartialIndex
3649 -> String
37− -> Aff { index :: Index, results :: Array SearchResult }
38−query index@(Index indexMap) input = do
50+ -> Aff { index :: PartialIndex, results :: Array SearchResult }
51+query index@(PartialIndex indexMap) input = do
3952 let
40− path :: List Char
4153 path =
4254 List.fromFoldable $
4355 String.toCharArray $
4456 input
4557
46− partId :: Int
4758 partId = getPartId path
4859
4960 case Map.lookup partId indexMap of
@@ -53,7 +64,7 @@ query index@(Index indexMap) input = do
5364
5465
5566 eiPartJson <-
56− try $ toAffE $ loadIndex_ partId $ config.mkIndexPartLoadPath partId
67+ try $ toAffE $ loadIndex_ partId $ Config.mkIndexPartLoadPath partId
5768
5869 let
5970 mbNewTrie :: Maybe (Trie Char (List SearchResult))
@@ -64,7 +75,7 @@ query index@(Index indexMap) input = do
6475
6576 case mbNewTrie of
6677 Just newTrie -> do
67− pure { index: Index $ Map.insert partId newTrie indexMap
78+ pure { index: PartialIndex $ Map.insert partId newTrie indexMap
6879 , results: flatten $ Trie.queryValues path newTrie
6980 }
7081 Nothing -> do
@@ -73,6 +84,7 @@ query index@(Index indexMap) input = do
7384 where
7485 flatten = Array.concat <<< Array.fromFoldable <<< map Array.fromFoldable
7586
87+
7688insertResults
7789 :: Tuple String (Array SearchResult)
7890 -> Trie Char (List SearchResult)
@@ -90,15 +102,29 @@ insertResults (Tuple path newResults) =
90102 Nothing -> Just $ List.fromFoldable newResults
91103 Just old -> Just $ List.fromFoldable newResults <> old
92104
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+
93117-- | Find in which part of the index this path can be found.
94−getPartId :: List Char -> Int
118+getPartId :: List Char -> PartId
95119getPartId (a : b : _) =
96− (Char.toCharCode a + Char.toCharCode b) `mod` config.numberOfIndexParts
120+ PartId $ (Char.toCharCode a + Char.toCharCode b) `mod` Config.numberOfIndexParts
97121getPartId (a : _) =
98− Char.toCharCode a `mod` config.numberOfIndexParts
99−getPartId _ = 0
122+ PartId $ Char.toCharCode a `mod` Config.numberOfIndexParts
123+getPartId _ = PartId 0
124+
100125
126+-- | Load a part of the index by injecting a <script> tag into the DOM.
101127foreign import loadIndex_
102− :: Int
103− -> String
128+ :: PartId
129+ -> URL
104130 -> Effect (Promise Json)
src/Docs/Search/Config.purs+80−30
@@ -1,35 +1,85 @@
11module Docs.Search.Config where
22
3+import Docs.Search.Types (GlobalIdentifier, PackageName, PartId, URL, FilePath)
4+
35import Prelude
46
5−config =
6− { outputDirectory: "output"
7− , requiredDirectories:
8− [ "generated-docs"
9− , "output"
10− ]
11− -- ^ Directories required by IndexBuilder
12− , indexDirectory: "generated-docs/index"
13− , declIndexDirectory: "generated-docs/index/declarations"
14− , typeIndexDirectory: "generated-docs/index/types"
15− , mkShapeScriptPath:
16− \shape -> "../index/types/" <> shape <> ".js"
17− , numberOfIndexParts: 50
18− -- ^ In how many parts the index should be splitted?
19− , mkIndexPartPath:
20− \(partId :: Int) -> "generated-docs/index/declarations/" <> show partId <> ".js"
21− , mkIndexPartLoadPath:
22− \(partId :: Int) -> "../index/declarations/" <> show partId <> ".js"
23− , resultsCount: 25
24− -- ^ How many results to show by default?
25− , penalties: { typeVars: 6
26− , match: 2
27− , matchConstraint: 1
28− , instantiate: 1
29− , generalize: 4
30− , rowsMismatch: 6
31− , mismatch: 10
32− , missingConstraint: 1
33− , excessiveConstraint: 1
34− }
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
3572 }
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.purs+186−128
@@ -1,53 +1,57 @@
11module Docs.Search.Declarations where
22
3−import Prelude
4−
3+import Docs.Search.DocsJson (ChildDeclType(..), ChildDeclaration(..), DeclType(..), Declaration(..), DocsJson(..), SourceSpan)
4+import Docs.Search.Score (Scores, getPackageScore, getPackageScoreForPackageName)
55import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
6−import Docs.Search.DocsJson (ChildDeclType(..), ChildDeclaration(..), DeclType(..), Declaration(..), DocsJson(..))
76import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..), joinForAlls)
7+import Docs.Search.Types (ModuleName(..), PackageName(..), PackageInfo(..), Identifier(..))
88
9+import Prelude
10+import Prim hiding (Type)
911import Control.Alt ((<|>))
1012import Data.Array ((!!))
11−import Data.Foldable (foldr)
13+import Data.Array as Array
14+import Data.Foldable (foldl, foldr)
1215import Data.List (List, (:))
1316import Data.List as List
1417import Data.Maybe (Maybe(..), fromMaybe)
1518import Data.Newtype (class Newtype, unwrap, wrap)
1619import Data.Search.Trie (Trie, alter)
1720import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray)
18−import Data.String.Common (toLower)
1921import Data.String.Common (split) as String
22+import Data.String.Common (toLower)
2023import Data.String.Pattern (Pattern(..))
2124
22−type ModuleName = String
23−type PackageName = String
2425
25−newtype Declarations
26− = Declarations (Trie Char (List SearchResult))
26+newtype Declarations = Declarations (Trie Char (List SearchResult))
2727
2828derive instance newtypeDeclarations :: Newtype Declarations _
2929derive newtype instance semigroupDeclarations :: Semigroup Declarations
3030derive newtype instance monoidDeclarations :: Monoid Declarations
3131
32−mkDeclarations :: Array DocsJson -> Declarations
33−mkDeclarations docsJson = Declarations trie
34− where
35− trie = foldr insertDocsJson mempty docsJson
32+
33+mkDeclarations :: Scores -> Array DocsJson -> Declarations
34+mkDeclarations scores = Declarations <<< foldr (insertDocsJson scores) mempty
35+
3636
3737insertDocsJson
38− :: DocsJson
38+ :: Scores
39+ -> DocsJson
3940 -> Trie Char (List SearchResult)
4041 -> Trie Char (List SearchResult)
41−insertDocsJson (DocsJson { name, declarations }) trie
42− = foldr (insertDeclaration name) trie declarations
42+insertDocsJson scores (DocsJson { name, declarations }) trie
43+ = foldr (insertDeclaration scores $ ModuleName name) trie declarations
44+
4345
4446insertDeclaration
45− :: ModuleName
47+ :: Scores
48+ -> ModuleName
4649 -> Declaration
4750 -> Trie Char (List SearchResult)
4851 -> Trie Char (List SearchResult)
49−insertDeclaration moduleName entry@(Declaration { title }) trie
50− = foldr insertSearchResult trie (resultsForEntry moduleName entry)
52+insertDeclaration scores moduleName entry@(Declaration { title }) trie
53+ = foldr insertSearchResult trie (resultsForDeclaration scores moduleName entry)
54+
5155
5256insertSearchResult
5357 :: { path :: String
@@ -57,34 +61,36 @@ insertSearchResult
5761 -> Trie Char (List SearchResult)
5862insertSearchResult { path, result } trie =
5963 let path' = List.fromFoldable $ toCharArray $ toLower path in
60− alter path' updateResults trie
61− where
62− updateResults mbOldResults =
63− case mbOldResults of
64− Just oldResults ->
65− Just $ result : oldResults
66− Nothing ->
67− Just $ List.singleton result
68−
69−resultsForEntry
70− :: ModuleName
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
7178 -> Declaration
7279 -> List { path :: String
7380 , result :: SearchResult
7481 }
75−resultsForEntry moduleName indexEntry@(Declaration entry) =
76− let { info, title, sourceSpan, comments, children } = entry
77− { name, declLevel } = getLevelAndName info.declType title
78− packageName = extractPackageName sourceSpan.name
79− in case mkInfo declLevel indexEntry of
80− Nothing -> mempty
81− Just info' ->
82− let result = SearchResult { name: title
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
8387 , comments
8488 , hashAnchor: declLevelToHashAnchor declLevel
8589 , moduleName
86− , sourceSpan: Just sourceSpan
87− , packageName
90+ , sourceSpan
91+ , packageInfo
92+ , score:
93+ fromMaybe zero $ getPackageScoreForPackageName scores <$> mbPackageName
8894 , info: info'
8995 }
9096 in
@@ -94,37 +100,45 @@ resultsForEntry moduleName indexEntry@(Declaration entry) =
94100 }
95101 ) <>
96102 ( List.fromFoldable children >>=
97− resultsForChildDeclaration packageName moduleName result
103+ resultsForChildDeclaration scores packageInfo moduleName result
98104 )
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+
99114
100115mkInfo :: DeclLevel -> Declaration -> Maybe ResultInfo
101116mkInfo declLevel (Declaration { info, title }) =
102117 case info.declType of
103118
104119 DeclValue ->
105− info.type <#>
106− \ty -> ValueResult { type: ty }
120+ info."type" <#> \ty -> ValueResult { type: ty }
107121
108122 DeclData ->
109− make <$> info.typeArguments <*> info.dataDeclType
110− where
111− make typeArguments dataDeclType =
112− DataResult { typeArguments, dataDeclType }
123+ make <$> info.typeArguments <*> info.dataDeclType
124+ where
125+ make typeArguments dataDeclType =
126+ DataResult { typeArguments, dataDeclType }
113127
114128 DeclExternData ->
115− info.kind <#>
116− \kind -> ExternDataResult { kind }
129+ info.kind <#> \kind -> ExternDataResult { kind }
117130
118131 DeclTypeSynonym ->
119− make <$> info.type <*> info.arguments
132+ make <$> info."type" <*> info.arguments
120133 where
121− make ty args = TypeSynonymResult { type: ty, arguments: args }
134+ make ty args = TypeSynonymResult { "type": ty, arguments: args }
122135
123− DeclTypeClass ->
124− case info.fundeps, info.arguments, info.superclasses of
125− Just fundeps, Just arguments, Just superclasses ->
126− Just $ TypeClassResult { fundeps, arguments, superclasses }
127− _, _, _ -> Nothing
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
128142
129143 DeclAlias ->
130144 case declLevel of
@@ -135,16 +149,19 @@ mkInfo declLevel (Declaration { info, title }) =
135149 DeclExternKind ->
136150 Just ExternKindResult
137151
152+
138153-- | Level of a declaration, used to determine which URI hash anchor to use in
139154-- | links ("v", "t" or "k").
140155data DeclLevel = ValueLevel | TypeLevel | KindLevel
141156
157+
142158declLevelToHashAnchor :: DeclLevel -> String
143159declLevelToHashAnchor = case _ of
144160 ValueLevel -> "v"
145161 TypeLevel -> "t"
146162 KindLevel -> "k"
147163
164+
148165getLevelAndName
149166 :: DeclType
150167 -> String
@@ -171,32 +188,38 @@ getLevelAndName DeclAlias title =
171188getLevelAndName DeclExternData name = { name, declLevel: TypeLevel }
172189getLevelAndName DeclExternKind name = { name, declLevel: KindLevel }
173190
174−-- | Extract package name from `sourceSpan.name`, which contains path to
175−-- | the source file.
176−extractPackageName :: String -> String
177−extractPackageName name =
178− let chunks = String.split (Pattern "/") name in
179− fromMaybe "<unknown>" $
180− chunks !! 0 >>= \dir ->
181− -- TODO: is it safe to assume that directory name is ".spago"?
182− if dir == ".spago" then
183− chunks !! 1
184− else
185− Just "<local package>"
186191
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`.
187211resultsForChildDeclaration
188− :: PackageName
212+ :: Scores
213+ -> PackageInfo
189214 -> ModuleName
190215 -> SearchResult
191216 -> ChildDeclaration
192217 -> List { path :: String, result :: SearchResult }
193−resultsForChildDeclaration packageName moduleName parentResult
194− child@(ChildDeclaration { title, info, comments, mbSourceSpan }) =
195− case mkChildInfo parentResult child of
196− Nothing -> mempty
197− Just resultInfo ->
218+resultsForChildDeclaration scores packageInfo moduleName parentResult
219+ child@(ChildDeclaration { title, info, comments, mbSourceSpan })
220+ | Just resultInfo <- mkChildInfo parentResult child =
198221 { path: title
199− , result: SearchResult { name: title
222+ , result: SearchResult { name: Identifier title
200223 , comments
201224 -- `ChildDeclaration`s are always either data
202225 -- constructors, type class members or instances.
@@ -205,65 +228,100 @@ resultsForChildDeclaration packageName moduleName parentResult
205228 , hashAnchor: "v"
206229 , moduleName
207230 , sourceSpan: mbSourceSpan
208− , packageName
231+ , packageInfo
232+ , score: getPackageScore scores packageInfo
209233 , info: resultInfo
210234 }
211235 } # List.singleton
236+ | otherwise = mempty
237+
212238
213239mkChildInfo
214240 :: SearchResult
215241 -> ChildDeclaration
216242 -> Maybe ResultInfo
217−mkChildInfo parentResult (ChildDeclaration { info } ) =
218− case info.declType of
219− ChildDeclDataConstructor ->
220− info.arguments <#>
221− \arguments -> DataConstructorResult { arguments }
222− ChildDeclTypeClassMember ->
223− case (unwrap parentResult).info of
224− TypeClassResult { arguments } ->
225− -- We need to reconstruct a "real" type of a type class member.
226− -- For example, if `unconstrainedType` is the type of `pure`, i.e. `forall a. a -> m a`,
227− -- `restoredType` should be `forall m a. Control.Applicative.Applicative m => a -> m a`.
228− info.type <#>
229− \(unconstrainedType :: Type) ->
230− let
231− -- First, we get a list of nested `forall` quantifiers for `unconstrainedType`
232− -- and a version of `unconstrainedType` without them (`ty`).
233− { ty, binders } = joinForAlls unconstrainedType
234−
235− -- Then we construct a qualified name of the type class.
236− constraintClass =
237− QualifiedName { moduleName:
238− String.split (wrap ".")
239− (unwrap parentResult).moduleName
240− , name: (unwrap parentResult).name }
241−
242− typeClassArguments = arguments <#> unwrap >>> _.name
243−
244− -- We concatenate two lists:
245− -- * list of type parameters of the type class, and
246− -- * list of quantified variables of the unconstrained type
247− allArguments :: Array String
248− allArguments =
249− typeClassArguments <> (List.toUnfoldable binders <#> (_.var))
250−
251− restoreType :: Type -> Type
252− restoreType =
253− foldr
254− (\arg -> compose (\type'' -> ForAll arg type'' Nothing))
255− identity allArguments
256−
257− restoredType = restoreType $
258− ConstrainedType (Constraint { constraintClass
259− , constraintArgs: typeClassArguments <#> TypeVar
260− }) ty
261−
262− in TypeClassMemberResult
263− { type: restoredType
264− , typeClass: constraintClass
265− , typeClassArguments
266− }
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+ }
267274
268− _ -> Nothing
269− ChildDeclInstance -> Nothing
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.purs+28−18
@@ -1,20 +1,20 @@
1+-- | A module containing everything that is necessary to decode `docs.json` files.
12module Docs.Search.DocsJson where
23
34import Prelude
5+import Prim hiding (Type, Constraint)
6+import Docs.Search.TypeDecoder (Constraint, FunDeps, Type, TypeArgument)
47
5−import Docs.Search.TypeDecoder
6−
7−import Control.Promise (Promise, toAffE)
8−import Data.Argonaut.Core (Json, fromString, stringify, toString)
8+import Data.Argonaut.Core (fromString, stringify, toString)
99import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:), (.:?))
10+import Data.Argonaut.Decode.Error (JsonDecodeError(..))
1011import Data.Argonaut.Encode (class EncodeJson, encodeJson)
1112import Data.Either (Either(..))
1213import Data.Generic.Rep (class Generic)
13−import Data.Generic.Rep.Show (genericShow)
14+import Data.Show.Generic (genericShow)
1415import Data.Maybe (Maybe(..))
1516import Data.Newtype (class Newtype, unwrap)
16−import Effect (Effect)
17−import Effect.Aff (Aff)
17+
1818
1919newtype DocsJson
2020 = DocsJson { name :: String
@@ -34,22 +34,24 @@ instance decodeJsonDocsJson :: DecodeJson DocsJson where
3434instance encodeJsonDocsJson :: EncodeJson DocsJson where
3535 encodeJson = encodeJson <<< unwrap
3636
37+type SourceSpan = { start :: Array Int
38+ , end :: Array Int
39+ , name :: String
40+ }
41+
3742newtype Declaration
3843 = Declaration { title :: String
3944 , comments :: Maybe String
4045 , info :: { declType :: DeclType
4146 , dataDeclType :: Maybe DataDeclType
42− , kind :: Maybe Kind
47+ , kind :: Maybe Type
4348 , typeArguments :: Maybe (Array TypeArgument)
4449 , type :: Maybe Type
4550 , superclasses :: Maybe (Array Constraint)
4651 , arguments :: Maybe (Array TypeArgument)
4752 , fundeps :: Maybe FunDeps
4853 }
49− , sourceSpan :: { start :: Array Int
50− , end :: Array Int
51− , name :: String
52− }
54+ , sourceSpan :: Maybe SourceSpan
5355 , children :: Array ChildDeclaration
5456 }
5557
@@ -83,6 +85,7 @@ instance decodeJsonDeclaration :: DecodeJson Declaration where
8385instance encodeJsonDeclaration :: EncodeJson Declaration where
8486 encodeJson = encodeJson <<< unwrap
8587
88+
8689newtype ChildDeclaration
8790 = ChildDeclaration { title :: String
8891 , comments :: Maybe String
@@ -119,6 +122,7 @@ instance decodeJsonChildDeclaration :: DecodeJson ChildDeclaration where
119122instance encodeJsonChildDeclaration :: EncodeJson ChildDeclaration where
120123 encodeJson = encodeJson <<< unwrap
121124
125+
122126-- See `src/Language/Purescript/Docs/Types.hs`
123127data DeclType
124128 = DeclValue
@@ -148,7 +152,7 @@ instance encodeJsonDeclType :: EncodeJson DeclType where
148152instance decodeJsonDeclType :: DecodeJson DeclType where
149153 decodeJson json =
150154 case toString json of
151− Nothing -> Left $ "Couldn't decode DeclType: " <> stringify json
155+ Nothing -> errorWith $ "Couldn't decode DeclType: " <> stringify json
152156 Just string ->
153157 case string of
154158 "value" -> Right DeclValue
@@ -158,7 +162,8 @@ instance decodeJsonDeclType :: DecodeJson DeclType where
158162 "typeClass" -> Right DeclTypeClass
159163 "alias" -> Right DeclAlias
160164 "kind" -> Right DeclExternKind
161− _ -> Left $ "Couldn't decode DeclType: " <> string
165+ _ -> errorWith $ "Couldn't decode DeclType: " <> string
166+
162167
163168data ChildDeclType
164169 = ChildDeclInstance
@@ -180,13 +185,14 @@ instance encodeJsonChildDeclType :: EncodeJson ChildDeclType where
180185instance decodeJsonChildDeclType :: DecodeJson ChildDeclType where
181186 decodeJson json =
182187 case toString json of
183− Nothing -> Left $ "Couldn't decode ChildDeclType: " <> stringify json
188+ Nothing -> errorWith $ "Couldn't decode ChildDeclType: " <> stringify json
184189 Just tag ->
185190 case tag of
186191 "instance" -> Right ChildDeclInstance
187192 "dataConstructor" -> Right ChildDeclDataConstructor
188193 "typeClassMember" -> Right ChildDeclTypeClassMember
189− _ -> Left $ "Couldn't decode ChildDeclType: " <> tag
194+ _ -> errorWith $ "Couldn't decode ChildDeclType: " <> tag
195+
190196
191197data DataDeclType
192198 = NewtypeDataDecl
@@ -210,5 +216,9 @@ instance decodeJsonDataDeclType :: DecodeJson DataDeclType where
210216 case tag of
211217 "newtype" -> Right NewtypeDataDecl
212218 "data" -> Right DataDataDecl
213− _ -> Left $ "Couldn't decode DataDeclType: " <> tag
214− Nothing -> Left $ "Couldn't decode DataDeclType: " <> stringify json
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.purs+34−2
@@ -2,8 +2,14 @@ module Docs.Search.Extra where
22
33import Prelude
44
5−import Data.Foldable (class Foldable, foldMap)
6−import Data.Maybe (Maybe(..))
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)
713
814whenJust :: forall a m. Monad m => Maybe a -> (a -> m Unit) -> m Unit
915whenJust (Just a) f = f a
@@ -13,3 +19,29 @@ foldMapFlipped :: forall a m f. Foldable f => Monoid m => f a -> (a -> m) -> m
1319foldMapFlipped = flip foldMap
1420
1521infixr 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.purs+333−98
@@ -1,114 +1,248 @@
11module Docs.Search.IndexBuilder where
22
3−import Prelude
4−
5−import Docs.Search.Config (config)
3+import Docs.Search.BrowserEngine (getPartId)
4+import Docs.Search.Config as Config
65import Docs.Search.Declarations (Declarations(..), mkDeclarations)
6+import Docs.Search.DocsJson (DocsJson)
77import Docs.Search.Extra ((>#>))
8−import Docs.Search.Index (getPartId)
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)
913import Docs.Search.SearchResult (SearchResult)
1014import Docs.Search.TypeIndex (TypeIndex, mkTypeIndex)
15+import Docs.Search.Types (ModuleName, PackageName, PartId)
16+
17+import Prelude
1118
1219import Data.Argonaut.Core (stringify)
1320import Data.Argonaut.Decode (decodeJson)
21+import Data.Argonaut.Decode.Error (printJsonDecodeError)
1422import Data.Argonaut.Encode (encodeJson)
1523import Data.Argonaut.Parser (jsonParser)
24+import Data.Array (concat)
1625import Data.Array as Array
17−import Data.Either (Either(..))
26+import Data.Either (Either(..), either)
27+import Data.Foldable (sum)
1828import Data.List (List)
1929import Data.List as List
2030import Data.Map (Map)
2131import Data.Map as Map
22−import Data.Maybe (Maybe(..))
32+import Data.Maybe (Maybe(..), fromMaybe)
2333import Data.Newtype (unwrap)
34+import Data.Profunctor.Choice (left)
2435import Data.Search.Trie as Trie
2536import Data.Set as Set
2637import Data.String.CodePoints (contains) as String
2738import Data.String.CodeUnits (singleton) as String
2839import Data.String.Common (replace) as String
29−import Data.String.Pattern (Pattern(..)) as String
3040import Data.String.Pattern (Pattern(..), Replacement(..))
3141import Data.Traversable (for, for_)
32−import Data.Tuple (Tuple(..), fst, snd)
42+import Data.Tuple (Tuple(..), fst)
43+import Data.Tuple.Nested ((/\))
3344import Effect (Effect)
3445import Effect.Aff (Aff, launchAff_, parallel, sequential)
3546import Effect.Class (liftEffect)
3647import Effect.Console (log)
3748import Node.Encoding (Encoding(UTF8))
38−import Node.FS.Aff (exists, mkdir, readTextFile, readdir, stat, writeTextFile)
49+import Node.FS.Aff (mkdir, readFile, readTextFile, readdir, stat, writeFile, writeTextFile)
3950import Node.FS.Stats (isDirectory, isFile)
51+import Node.FS.Sync (exists)
4052import Node.Process as Process
41−import Docs.Search.DocsJson (DocsJson)
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+ }
4264
43−main :: Effect Unit
44−main = launchAff_ mainAff
4565
46−mainAff :: Aff Unit
47−mainAff = do
48− checkDirectories
66+run :: Config -> Effect Unit
67+run = launchAff_ <<< run'
4968
50− docsJsons <- collectDocsJsons config.outputDirectory
5169
52− liftEffect $ log $
53− "Found " <> show (Array.length docsJsons) <> " modules."
70+run' :: Config -> Aff Unit
71+run' cfg = do
5472
55− let index = mkDeclarations docsJsons
56− typeIndex = mkTypeIndex index
73+ checkDirectories cfg
5774
58− createDirectories
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
59103
60104 void $ sequential do
61− ignore <$> parallel (writeIndex index)
105+ ignore <$> parallel (writeIndex cfg index)
62106 <*> parallel (writeTypeIndex typeIndex)
63− <*> parallel patchDocs
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
64128
65− liftEffect $ log $
66− "Loaded " <>
67− show (Trie.size $ unwrap index) <>
68− " definitions and " <>
69− show (List.length $ join $ map snd $ Trie.entriesUnordered (unwrap index)) <>
70− " type definitions"
71− where ignore _ _ _ = unit
72129
73130-- | Exit early if something is missing.
74−checkDirectories :: Aff Unit
75−checkDirectories = do
76− for_ config.requiredDirectories \dir -> do
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
77139 whenM (not <$> directoryExists dir) $
78− liftEffect $ logAndExit "Generate the documentation first!"
140+ liftEffect do
141+ logAndExit "Build the documentation first!"
142+
79143
80−-- | Read and decode all `docs.json` files in the `outputDir`.
81−collectDocsJsons :: String -> Aff (Array DocsJson)
82−collectDocsJsons outputDir = do
83− paths <- readdir outputDir
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
84150
85− mbs <- for paths \moduleName -> do
86− let jsonFile = "output/" <> moduleName <> "/docs.json"
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
87160 doesExist <- fileExists jsonFile
161+
88162 if doesExist then do
89163
90164 contents <- readTextFile UTF8 jsonFile
91− let eiResult = jsonParser contents >>= decodeJson
165+ let eiResult = jsonParser contents >>= left printJsonDecodeError <<< decodeJson
92166
93167 case eiResult of
94168 Left error -> do
95169 liftEffect $ log $
96− "\"docs.json\" decoding failed failed for module " <> moduleName <> ": " <> show error
170+ "\"docs.json\" decoding failed failed for " <> jsonFile <> ": " <> error
97171 pure Nothing
98172 Right result -> pure result
99173
100174 else do
101175 liftEffect $ do
102176 log $
103− "Couldn't find docs.json for " <> moduleName
177+ "File does not exist: " <> jsonFile
104178 pure Nothing
105179
106− pure $ Array.catMaybes mbs
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)
107229
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.
108242writeTypeIndex :: TypeIndex -> Aff Unit
109243writeTypeIndex typeIndex =
110244 for_ entries \(Tuple typeShape results) -> do
111− writeTextFile UTF8 ("generated-docs/index/types/" <> typeShape <> ".js")
245+ writeTextFile UTF8 (unwrap Config.typeIndexDirectory <> "/" <> typeShape <> ".js")
112246 (mkHeader typeShape <> stringify (encodeJson results))
113247 where
114248 mkHeader typeShape =
@@ -117,53 +251,98 @@ writeTypeIndex typeIndex =
117251 entries :: Array _
118252 entries = Map.toUnfoldableUnordered (unwrap typeIndex)
119253
120−writeIndex :: Declarations -> Aff Unit
121−writeIndex (Declarations trie) = do
122− let
123− prefixes :: Array (List Char)
124− prefixes =
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 =
125308 Set.toUnfoldable $
126− List.foldr (\entry -> Set.insert (List.take 2 $ fst entry)) mempty $
127− Trie.entriesUnordered trie
128−
129− parts
130− :: Array { prefix :: List Char
131− , results :: Array (Tuple String (Array SearchResult))
132− }
133− parts = prefixes <#> \prefix ->
134− let results =
135− Array.fromFoldable $
136− Trie.query prefix trie <#>
137− \(Tuple path value) ->
138− Tuple (path >#> String.singleton) (Array.fromFoldable value)
139− in
140− { prefix, results }
141−
142− resultsMap :: Map Int (Array (Tuple String (Array SearchResult)))
143− resultsMap = Array.foldr insert mempty parts
144−
145− insert part = Map.insertWith append (getPartId part.prefix) part.results
309+ List.foldr (\path -> Set.insert (List.take 2 path)) mempty $
310+ fst <$> Trie.entriesUnordered trie
146311
312+
313+writeIndex :: Config -> Declarations -> Aff Unit
314+writeIndex { generatedDocs } = getIndex >>> \resultsMap -> do
147315 for_ (Map.toUnfoldableUnordered resultsMap :: Array _)
148316 \(Tuple indexPartId results) -> do
149317 let header =
150318 "// This file was generated by purescript-docs-search.\n" <>
151319 "window.DocsSearchIndex[\"" <> show indexPartId <> "\"] = "
152320
153− writeTextFile UTF8 (config.mkIndexPartPath indexPartId) $
321+ writeTextFile UTF8 (generatedDocs <> Config.mkIndexPartPath indexPartId) $
154322 header <> stringify (encodeJson results)
155323
156−patchDocs :: Aff Unit
157−patchDocs = do
158− let dirname = "generated-docs/"
159− pattern = Pattern "</body>"
160− patch = "<!-- Docs search index. -->" <>
161− "<script type=\"text/javascript\" src=\"../docs-search-app.js\"></script>" <>
162− "<script type=\"text/javascript\">" <>
163− "window.DocsSearchTypeIndex = {};" <>
164− "window.DocsSearchIndex = {};" <>
165− "</script>" <>
166− "</body>"
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
167346
168347 files <- readdir (dirname <> "html")
169348
@@ -172,18 +351,25 @@ patchDocs = do
172351
173352 whenM (fileExists path) do
174353 contents <- readTextFile UTF8 path
175− when (not $ String.contains (String.Pattern patch) contents) do
176− writeTextFile UTF8 path $
177− String.replace pattern (Replacement patch) contents
178−
179−createDirectories :: Aff Unit
180−createDirectories = do
181− let generatedDocsDir = "generated-docs/"
182− indexDir = "generated-docs/index"
183− declIndexDir = "generated-docs/index/declarations"
184− typeIndexDir = "generated-docs/index/types"
185−
186− whenM (not <$> directoryExists generatedDocsDir) $ liftEffect do
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
187373 logAndExit "Generate the documentation first!"
188374
189375 whenM (not <$> directoryExists indexDir) do
@@ -195,21 +381,70 @@ createDirectories = do
195381 whenM (not <$> directoryExists typeIndexDir) do
196382 mkdir typeIndexDir
197383
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+
198400directoryExists :: String -> Aff Boolean
199401directoryExists path = do
200− doesExist <- exists path
402+ doesExist <- liftEffect $ exists path
201403 case doesExist of
202404 false -> pure false
203405 true -> isDirectory <$> stat path
204406
407+
205408fileExists :: String -> Aff Boolean
206409fileExists path = do
207− doesExist <- exists path
410+ doesExist <- liftEffect $ exists path
208411 case doesExist of
209412 false -> pure false
210413 true -> isFile <$> stat path
211414
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+
212432logAndExit :: forall a. String -> Effect a
213−logAndExit err = do
214− log err
433+logAndExit message = do
434+ log message
215435 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.purs+35−19
@@ -1,26 +1,32 @@
11module Docs.Search.SearchResult where
22
33import Docs.Search.DocsJson (DataDeclType)
4−import Docs.Search.TypeDecoder (Constraint, FunDeps, Kind, QualifiedName, Type, TypeArgument)
4+import Docs.Search.TypeDecoder (Constraint, FunDeps, QualifiedName, Type, TypeArgument)
5+import Docs.Search.Types (ModuleName, PackageInfo, Identifier, PackageScore)
56
7+import Prelude
8+import Prim hiding (Type, Constraint)
69import Data.Argonaut.Decode (class DecodeJson)
7−import Data.Argonaut.Decode.Generic.Rep (genericDecodeJson)
10+import Data.Argonaut.Decode.Generic (genericDecodeJson)
811import Data.Argonaut.Encode (class EncodeJson)
9−import Data.Argonaut.Encode.Generic.Rep (genericEncodeJson)
12+import Data.Argonaut.Encode.Generic (genericEncodeJson)
1013import Data.Generic.Rep (class Generic)
1114import Data.Maybe (Maybe(..))
12−import Data.Newtype (class Newtype)
15+import Data.Newtype (class Newtype, un)
1316
17+
18+-- | Metadata that makes sense only for certain types of search results.
1419data ResultInfo
1520 = DataResult { typeArguments :: Array TypeArgument
1621 , dataDeclType :: DataDeclType }
17− | ExternDataResult { kind :: Kind }
22+ | ExternDataResult { kind :: Type }
1823 | TypeSynonymResult { arguments :: Array TypeArgument
1924 , type :: Type }
20− | DataConstructorResult { arguments :: Array Type }
25+ | DataConstructorResult { dataDeclType :: DataDeclType
26+ , type :: Type }
2127 | TypeClassMemberResult { type :: Type
2228 , typeClass :: QualifiedName
23− , typeClassArguments :: Array String }
29+ , typeClassArguments :: Array TypeArgument }
2430 | TypeClassResult { fundeps :: FunDeps
2531 , arguments :: Array TypeArgument
2632 , superclasses :: Array Constraint }
@@ -37,6 +43,8 @@ instance encodeJsonResultInfo :: EncodeJson ResultInfo where
3743instance decodeJsonResultInfo :: DecodeJson ResultInfo where
3844 decodeJson = genericDecodeJson
3945
46+
47+-- | Extract the type field.
4048typeOf :: ResultInfo -> Maybe Type
4149typeOf (TypeSynonymResult { type: res }) =
4250 Just res
@@ -46,24 +54,32 @@ typeOf (ValueResult { type: res }) =
4654 Just res
4755typeOf _ = Nothing
4856
57+
58+-- | Common metadata for all types of search results.
4959newtype SearchResult
50− = SearchResult { name :: String
51− , comments :: Maybe String
52− , hashAnchor :: String
53− , moduleName :: String
54− , packageName :: String
55− , sourceSpan :: Maybe { start :: Array Int
56− , end :: Array Int
57− , name :: String
58− }
59− , info :: ResultInfo
60− }
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+ }
6173
62−derive instance newtypeSearchResult :: Newtype SearchResult _
6374derive instance genericSearchResult :: Generic SearchResult _
75+derive instance newtypeSearchResult :: Newtype SearchResult _
6476
6577instance encodeJsonSearchResult :: EncodeJson SearchResult where
6678 encodeJson = genericEncodeJson
6779
6880instance decodeJsonSearchResult :: DecodeJson SearchResult where
6981 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/Docs/Search/TypeDecoder.purs+115−93
@@ -1,17 +1,21 @@
1+-- | A decoder for types located in 'Language.PureScript.Types'.
12module Docs.Search.TypeDecoder where
23
3−import Prelude
4+import Docs.Search.Types (Identifier)
45
6+import Prelude
7+import Prim hiding (Type, Constraint)
8+import Control.Alt ((<|>))
59import Data.Argonaut.Core (Json, caseJsonObject, fromArray, fromObject, jsonEmptyObject, stringify, toArray)
610import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:))
711import Data.Argonaut.Encode (class EncodeJson, encodeJson)
8−import Data.Array ((!!))
12+import Data.Argonaut.Decode.Error (JsonDecodeError(..))
913import Data.Either (Either(..))
1014import Data.Generic.Rep (class Generic)
11−import Data.Generic.Rep.Show (genericShow)
15+import Data.Show.Generic (genericShow)
1216import Data.List (List(..), (:))
1317import Data.List as List
14−import Data.Maybe (Maybe(..), fromMaybe)
18+import Data.Maybe (Maybe(..))
1519import Data.Newtype (class Newtype)
1620import Data.Tuple (Tuple(..))
1721import Foreign.Object as Object
@@ -24,71 +28,28 @@ instance showQualifiedName :: Show QualifiedName where
2428 show = genericShow
2529
2630newtype QualifiedName
27− = QualifiedName { moduleName :: Array String
28− , name :: String
31+ = QualifiedName { moduleNameParts :: Array String
32+ , name :: Identifier
2933 }
3034
3135instance decodeJsonQualifiedName :: DecodeJson QualifiedName where
3236 decodeJson json = do
3337 decodeTuple
34− (\moduleName name -> QualifiedName { moduleName, name })
38+ (\moduleNameParts name -> QualifiedName { moduleNameParts, name })
3539 (mkJsonError "QualifiedName" json)
3640 json
3741
3842instance encodeJsonQualifiedName :: EncodeJson QualifiedName where
39− encodeJson (QualifiedName { moduleName, name }) =
40− encodeTuple moduleName name
43+ encodeJson (QualifiedName { moduleNameParts, name }) =
44+ encodeTuple moduleNameParts name
4145
42−mkJsonError :: String -> Json -> (forall i. i -> String)
46+mkJsonError :: String -> Json -> (forall i. i -> JsonDecodeError)
4347mkJsonError name json _ =
44− "Couldn't parse " <> name <> " from " <> stringify json
48+ TypeMismatch $ "Couldn't parse " <> name <> " from " <> stringify json
4549
46−mkJsonError' :: String -> Json -> String
50+mkJsonError' :: String -> Json -> JsonDecodeError
4751mkJsonError' name json = mkJsonError name json unit
4852
49−-- | The data type of kinds
50−data Kind
51− -- | Kinds for labelled, unordered rows without duplicates
52− = Row Kind
53− -- | Function kinds
54− | FunKind Kind Kind
55− -- | A named kind
56− | NamedKind QualifiedName
57−
58−derive instance eqKind :: Eq Kind
59−derive instance genericKind :: Generic Kind _
60−
61−instance showKind :: Show Kind where
62− show x = genericShow x
63−
64−instance decodeJsonKind :: DecodeJson Kind where
65− decodeJson json = do
66− handle <- decodeJson json
67− tag <- handle .: "tag"
68− case tag of
69− "NamedKind" -> do
70− contents <- handle .: "contents"
71− pure $ NamedKind contents
72− "Row" -> do
73− contents <- handle .: "contents"
74− pure $ Row contents
75− "FunKind" -> do
76− contents <- handle .: "contents"
77− case contents of
78− [k1, k2] ->
79− Right $ FunKind k1 k2
80− _ -> Left $ mkJsonError' "FunKind" json
81− _ -> Left $ mkJsonError' "Kind" json
82−
83−instance encodeJsonKind :: EncodeJson Kind where
84− encodeJson = case _ of
85− Row k ->
86− encodeTaggedContents "Row" (encodeJson k)
87− FunKind k1 k2 ->
88− encodeTaggedContents "FunKind" (encodeTuple k1 k2)
89− NamedKind qname ->
90− encodeTaggedContents "NamedKind" (encodeJson qname)
91−
9253-- | A typeclass constraint
9354newtype Constraint = Constraint
9455 { constraintClass :: QualifiedName
@@ -137,8 +98,10 @@ data Type
13798 | TypeOp QualifiedName
13899 -- | A type application
139100 | TypeApp Type Type
101+ -- | Explicit kind application
102+ | KindApp Type Type
140103 -- | Forall quantifier
141− | ForAll String Type (Maybe Kind)
104+ | ForAll String (Maybe Type) Type
142105 -- | A type withset of type class constraints
143106 | ConstrainedType Constraint Type
144107 {-
@@ -148,11 +111,9 @@ data Type
148111 -- | An empty row
149112 | REmpty
150113 -- | A non-empty row
151− | RCons String Type Type
152− {-
114+ | RCons Identifier Type Type
153115 -- | A type with a kind annotation
154− | Kinded Type Kind
155− -}
116+ | Kinded Type Type
156117 -- | Binary operator application. During the rebracketing phase of desugaring,
157118 -- this data constructor will be removed.
158119 | BinaryNoParensType Type Type Type
@@ -178,8 +139,25 @@ instance decodeJsonType :: DecodeJson Type where
178139 "TypeApp" ->
179140 decodeContents (decodeTuple TypeApp (const err)) (Left err) json
180141 where err = mkJsonError' "TypeApp" json
142+ "KindApp" ->
143+ decodeContents (decodeTuple KindApp (const err)) (Left err) json
144+ where err = mkJsonError' "KindApp" json
181145 "ForAll" ->
182− 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
183161 where err = mkJsonError "ForAll" json
184162 "ConstrainedType" ->
185163 decodeContents (decodeTuple ConstrainedType err) (Left $ err unit) json
@@ -188,6 +166,9 @@ instance decodeJsonType :: DecodeJson Type where
188166 "RCons" ->
189167 decodeContents (decodeTriple RCons (const err)) (Left err) json
190168 where err = mkJsonError' "RCons" json
169+ "KindedType" ->
170+ decodeContents (decodeTuple Kinded (const err)) (Left err) json
171+ where err = mkJsonError' "KindedType" json
191172 "BinaryNoParensType" ->
192173 decodeContents (decodeTriple BinaryNoParensType (const err)) (Left err) json
193174 where err = mkJsonError' "BinaryNoParens" json
@@ -200,19 +181,25 @@ instance decodeJsonType :: DecodeJson Type where
200181
201182instance encodeJsonType :: EncodeJson Type where
202183 encodeJson = case _ of
203− TypeVar val -> encodeTaggedContents "TypeVar" (encodeJson val)
204− TypeLevelString val -> encodeTaggedContents "TypeLevelString" (encodeJson val)
205− TypeConstructor val -> encodeTaggedContents "TypeConstructor" (encodeJson val)
206− TypeOp val -> encodeTaggedContents "TypeOp" (encodeJson val)
207− TypeApp t1 t2 -> encodeTaggedContents "TypeApp" (encodeTuple t1 t2)
208− ForAll str ty mbk -> encodeTaggedContents "ForAll" (encodeTriple str ty mbk)
209− ConstrainedType c t -> encodeTaggedContents "ConstrainedType" (encodeTuple c t)
210− REmpty -> encodeTaggedContents "REmpty" jsonEmptyObject
211− 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
212198 BinaryNoParensType t1 t2 t3 ->
213− encodeTaggedContents "BinaryNoParensType" (encodeTriple t1 t2 t3)
214− ParensInType t -> encodeTaggedContents "ParensInType" (encodeJson t)
215− TypeWildcard -> encodeTaggedContents "TypeWildcard" jsonEmptyObject
199+ tagged "BinaryNoParensType" (encodeTriple t1 t2 t3)
200+
201+emptySkolemScope :: Maybe Int
202+emptySkolemScope = Nothing
216203
217204newtype FunDep
218205 = FunDep
@@ -250,7 +237,7 @@ instance encodeJsonFunDeps :: EncodeJson FunDeps where
250237newtype TypeArgument
251238 = TypeArgument
252239 { name :: String
253− , mbKind :: Maybe Kind
240+ , mbKind :: Maybe Type
254241 }
255242
256243derive newtype instance eqTypeArgument :: Eq TypeArgument
@@ -274,17 +261,16 @@ decodeTuple
274261 . DecodeJson fst
275262 => DecodeJson sec
276263 => (fst -> sec -> res)
277− -> (forall a. a -> String)
264+ -> (forall a. a -> JsonDecodeError)
278265 -> Json
279− -> Either String res
266+ -> Either JsonDecodeError res
280267decodeTuple cont err json =
281− fromMaybe (Left $ err unit) $
282− toArray json >>= \jsons ->
283− jsons !! 0 >>= \json1 ->
284− jsons !! 1 >>= \json2 -> pure $ do
285− fst <- decodeJson json1
286− sec <- decodeJson json2
287− 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
288274
289275-- | Decode a heterogeneous triple.
290276decodeTriple
@@ -293,9 +279,9 @@ decodeTriple
293279 => DecodeJson sec
294280 => DecodeJson trd
295281 => (fst -> sec -> trd -> res)
296− -> (forall a. a -> String)
282+ -> (forall a. a -> JsonDecodeError)
297283 -> Json
298− -> Either String res
284+ -> Either JsonDecodeError res
299285decodeTriple cont err json =
300286 case toArray json of
301287 Just [ json1, json2, json3 ] -> do
@@ -305,6 +291,28 @@ decodeTriple cont err json =
305291 pure $ cont fst sec trd
306292 _ -> Left $ err unit
307293
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+
308316-- | Decode a `.contents` property.
309317decodeContents :: forall r. (Json -> r) -> r -> Json -> r
310318decodeContents go err json =
@@ -338,8 +346,22 @@ encodeTriple
338346encodeTriple fst sec trd =
339347 fromArray [ encodeJson fst, encodeJson sec, encodeJson trd ]
340348
341−encodeTaggedContents :: String -> Json -> Json
342−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 =
343365 fromObject $ Object.fromFoldable
344366 [ Tuple "tag" (encodeJson tag)
345367 , Tuple "contents" contents
@@ -348,17 +370,17 @@ encodeTaggedContents tag contents =
348370
349371joinForAlls
350372 :: Type
351− -> { binders :: List { var :: String
352− , mbKind :: Maybe Kind }
373+ -> { binders :: List { name :: String
374+ , mbKind :: Maybe Type }
353375 , ty :: Type
354376 }
355377joinForAlls ty = go Nil ty
356378 where
357− go acc (ForAll var ty' mbKind) =
358− go ({ var, mbKind } : acc) ty'
379+ go acc (ForAll name mbKind ty') =
380+ go ({ name, mbKind } : acc) ty'
359381 go acc ty' = { binders: acc, ty: ty' }
360382
361−joinRows :: Type -> { rows :: List { row :: String
383+joinRows :: Type -> { rows :: List { row :: Identifier
362384 , ty :: Type
363385 }
364386 , ty :: Maybe Type }
@@ -374,7 +396,7 @@ joinRows = go Nil
374396 }
375397
376398-- | Only returns a list of type class names (lists of arguments are omitted).
377−joinConstraints :: Type -> { constraints :: List String
399+joinConstraints :: Type -> { constraints :: List Identifier
378400 , ty :: Type }
379401joinConstraints = go Nil
380402 where
src/Docs/Search/TypeIndex.js+1−1
@@ -1,6 +1,6 @@
11/* global exports */
22
3−exports.lookup_ = function (shape) {
3+export function lookup_ (shape) {
44 return function (url) {
55 return function () {
66 return new Promise(function (resolve, reject) {
src/Docs/Search/TypeIndex.purs+57−46
@@ -1,70 +1,75 @@
1+-- | Partial type index, can be loaded on demand in the browser.
12module Docs.Search.TypeIndex where
23
3−import Prelude
4−
5−import Docs.Search.Config (config)
6−import Docs.Search.Declarations (Declarations(..))
7−import Docs.Search.SearchResult (ResultInfo(..), SearchResult)
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)
810import Docs.Search.TypeQuery (TypeQuery)
11+import Docs.Search.Types (ModuleName(..))
912import Docs.Search.TypeShape (shapeOfType, shapeOfTypeQuery, stringifyShape)
1013
14+import Prelude
15+import Prim hiding (Type)
1116import Control.Promise (Promise, toAffE)
1217import Data.Argonaut.Core (Json)
1318import Data.Argonaut.Decode (decodeJson)
1419import Data.Array as Array
1520import Data.Either (hush)
16−import Data.List (List)
17−import Data.List as List
21+import Data.Foldable (fold, foldr)
1822import Data.Map (Map)
1923import Data.Map as Map
20−import Data.Maybe (Maybe(..), fromMaybe')
21−import Data.Newtype (class Newtype, unwrap, wrap)
22−import Data.Search.Trie as Trie
23−import Data.Tuple (Tuple(..), snd)
24+import Data.Maybe (Maybe(..), fromMaybe', isJust)
25+import Data.Newtype (class Newtype, over)
2426import Effect (Effect)
2527import Effect.Aff (Aff, try)
2628
29+
2730newtype TypeIndex = TypeIndex (Map String (Maybe (Array SearchResult)))
2831
29−derive newtype instance semigroupTypeIndex :: Semigroup TypeIndex
30−derive newtype instance monoidTypeIndex :: Monoid TypeIndex
3132derive instance newtypeTypeIndex :: Newtype TypeIndex _
3233
33−insert
34− :: String
35− -> Maybe (Array SearchResult)
36− -> TypeIndex
37− -> TypeIndex
38−insert key value = unwrap >>> Map.insert key value >>> wrap
3934
40−mkTypeIndex :: Declarations -> TypeIndex
41−mkTypeIndex (Declarations trie) = TypeIndex $ map (Array.fromFoldable >>> Just) types
35+mkTypeIndex :: Scores -> Array DocsJson -> TypeIndex
36+mkTypeIndex scores docsJsons =
37+ TypeIndex $ map Just $ foldr insert Map.empty docsJsons
4238 where
43− insertTypes
44− :: Tuple String SearchResult
45− -> Map String (List SearchResult)
46− -> Map String (List SearchResult)
47− insertTypes (Tuple shape result) =
48− Map.insertWith append shape (List.singleton result)
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)
4953
50− types = List.foldr insertTypes mempty do
5154
52− results <- Trie.entriesUnordered trie >>= snd
55+resultsWithTypes :: Scores -> DocsJson -> Array SearchResult
56+resultsWithTypes scores = Array.filter (getType >>> isJust) <<< allResults scores
5357
54− case (unwrap results).info of
55− ValueResult dict ->
56− insertTypeResultsFor dict.type results
5758
58− TypeClassMemberResult dict ->
59− insertTypeResultsFor dict.type results
59+getType :: SearchResult -> Maybe Type
60+getType (SearchResult { info }) =
61+ case info of
62+ ValueResult dict ->
63+ Just dict.type
6064
61− TypeSynonymResult dict ->
62− insertTypeResultsFor dict.type results
63− _ -> mempty
65+ TypeClassMemberResult dict ->
66+ Just dict.type
67+
68+ TypeSynonymResult dict ->
69+ Just dict.type
70+
71+ _ -> Nothing
6472
65− insertTypeResultsFor ty results =
66− let path = stringifyShape (shapeOfType ty) in
67− pure $ Tuple path results
6873
6974lookup
7075 :: String
@@ -72,9 +77,9 @@ lookup
7277 -> Aff { index :: TypeIndex, results :: Array SearchResult }
7378lookup key index@(TypeIndex map) =
7479 case Map.lookup key map of
75− Just results -> pure { index, results: Array.fold results }
80+ Just results -> pure { index, results: fold results }
7681 Nothing -> do
77− eiJson <- try (toAffE (lookup_ key $ config.mkShapeScriptPath key))
82+ eiJson <- try (toAffE (lookup_ key $ Config.mkShapeScriptPath key))
7883 pure $ fromMaybe'
7984 (\_ -> { index: insert key Nothing index, results: [] })
8085 do
@@ -82,17 +87,23 @@ lookup key index@(TypeIndex map) =
8287 results <- hush (decodeJson json)
8388 pure { index: insert key (Just results) index, results }
8489
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+
8599query
86100 :: TypeIndex
87101 -> TypeQuery
88102 -> Aff { index :: TypeIndex, results :: Array SearchResult }
89103query typeIndex typeQuery = do
90104 res <- lookup (stringifyShape $ shapeOfTypeQuery typeQuery) typeIndex
91− pure $ res { results = sortByRelevance typeQuery res.results }
105+ pure $ res { results = res.results }
92106
93−-- | TODO
94−sortByRelevance :: TypeQuery -> Array SearchResult -> Array SearchResult
95−sortByRelevance typeQuery = identity
96107
97108foreign import lookup_
98109 :: String
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.purs+179−103
@@ -1,3 +1,4 @@
1+-- | `TypeQuery` is a representation of a user-provided type.
12module Docs.Search.TypeQuery
23 ( TypeQuery(..)
34 , Substitution(..)
@@ -9,44 +10,45 @@ module Docs.Search.TypeQuery
910 )
1011where
1112
12−import Prelude
13−
14−import Docs.Search.Config
15−import Docs.Search.DocsJson
16−import Docs.Search.TypeDecoder
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(..))
1717
18+import Prelude
19+import Prim hiding (Type)
1820import Control.Alt ((<|>))
1921import Data.Array as Array
2022import Data.Either (Either)
21−import Data.Foldable (foldl)
2223import Data.Generic.Rep (class Generic)
23−import Data.Generic.Rep.Show (genericShow)
24+import Data.Show.Generic (genericShow)
2425import Data.List (List(..), many, some, (:))
2526import Data.List as List
26−import Data.List.NonEmpty (NonEmptyList, cons', uncons)
27+import Data.List.NonEmpty (NonEmptyList)
2728import Data.List.NonEmpty as NonEmptyList
2829import Data.Map (Map)
2930import Data.Map as Map
30−import Data.Maybe (Maybe(..))
31−import Data.Newtype (unwrap)
3231import Data.Ord (abs)
3332import Data.Set (Set)
3433import Data.Set as Set
3534import Data.String.CodeUnits (fromCharArray)
3635import Data.String.Common (trim) as String
3736import Data.Tuple (Tuple(..), fst, snd)
38−import Text.Parsing.StringParser (ParseError, Parser, runParser, try)
39−import Text.Parsing.StringParser.CodePoints (alphaNum, anyLetter, char, eof, lowerCaseChar, skipSpaces, string, upperCaseChar)
40−import Text.Parsing.StringParser.Combinators (fix, sepBy, sepBy1, sepEndBy, sepEndBy1)
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+
4141
42+-- | We need type queries because we don't have a full-featured type parser
43+-- | available.
4244data TypeQuery
43− = QVar String
44− | QConst String
45+ = QVar Identifier
46+ | QConst Identifier
4547 | QFun TypeQuery TypeQuery
4648 | QApp TypeQuery TypeQuery
47− | QForAll (NonEmptyList String) TypeQuery
48− | QConstraint String (List TypeQuery) TypeQuery
49− | QRow (List (Tuple String TypeQuery))
49+ | QForAll (NonEmptyList Identifier) TypeQuery
50+ | QConstraint Identifier (List TypeQuery) TypeQuery
51+ | QRow (List (Tuple Identifier TypeQuery))
5052
5153derive instance eqTypeQuery :: Eq TypeQuery
5254derive instance genericTypeQuery :: Generic TypeQuery _
@@ -54,9 +56,11 @@ derive instance genericTypeQuery :: Generic TypeQuery _
5456instance showTypeQuery :: Show TypeQuery where
5557 show x = genericShow x
5658
59+
5760parseTypeQuery :: String -> Either ParseError TypeQuery
5861parseTypeQuery = String.trim >>> runParser (typeQueryParser <* eof)
5962
63+
6064typeQueryParser :: Parser TypeQuery
6165typeQueryParser = fix \typeQuery ->
6266 let rowFields =
@@ -66,7 +70,8 @@ typeQueryParser = fix \typeQuery ->
6670
6771 row = string "(" *> rowFields <* string ")"
6872
69− record = QApp (QConst "Record") <$> (string "{" *> rowFields <* string "}")
73+ record = QApp (QConst $ Identifier "Record") <$>
74+ (string "{" *> rowFields <* string "}")
7075
7176 binders =
7277 string "forall" *> some space *> sepEndBy1 ident skipSpaces <* string "." <* skipSpaces
@@ -93,24 +98,12 @@ typeQueryParser = fix \typeQuery ->
9398
9499 constrained =
95100 QConstraint <$> (upperCaseIdent <* skipSpaces) <*>
96− (sepEndBy ((QVar <$> ident) <|> parens) (many space) <* string "=>" <* skipSpaces) <*>
101+ (sepEndBy ((QVar <$> ident) <|> parens)
102+ (many space) <* string "=>" <* skipSpaces) <*>
97103 typeQuery
98104 in
99105 try constrained <|> funs
100106
101−foldl1 :: forall a. (a -> a -> a) -> NonEmptyList a -> a
102−foldl1 f as =
103− case uncons as of
104− { head, tail } -> foldl f head tail
105−
106−foldr1 :: forall a. (a -> a -> a) -> NonEmptyList a -> a
107−foldr1 f = go List.Nil
108− where
109− go acc x = case uncons x of
110− { head, tail } -> case List.uncons tail of
111− Nothing -> List.foldl (flip f) head acc
112− Just { head: head1, tail: tail1 } ->
113− go (head : acc) (cons' head1 tail1)
114107
115108any :: Parser TypeQuery
116109any = do
@@ -120,32 +113,32 @@ concrete :: Parser TypeQuery
120113concrete =
121114 QConst <$> upperCaseIdent
122115
123−ident :: Parser String
116+ident :: Parser Identifier
124117ident = do
125118 head <- anyLetter
126119 rest <- Array.many (alphaNum <|> char '\'')
127− pure $ fromCharArray $ pure head <> rest
120+ pure $ Identifier <$> fromCharArray $ pure head <> rest
128121
129−upperCaseIdent :: Parser String
122+upperCaseIdent :: Parser Identifier
130123upperCaseIdent = do
131124 head <- upperCaseChar
132125 rest <- Array.many (alphaNum <|> char '\'')
133− pure $ fromCharArray $ pure head <> rest
126+ pure $ Identifier $ fromCharArray $ pure head <> rest
134127
135−lowerCaseIdent :: Parser String
128+lowerCaseIdent :: Parser Identifier
136129lowerCaseIdent = do
137130 head <- lowerCaseChar
138131 rest <- Array.many (alphaNum <|> char '\'')
139− pure $ fromCharArray $ pure head <> rest
132+ pure $ Identifier $ fromCharArray $ pure head <> rest
140133
141134space :: Parser Char
142135space = char ' '
143136
144137
145138-- | Used only in `getFreeVariables`.
146−data FreeVarCounterQueueEntry = Unbind (Set.Set String) | Next TypeQuery
139+data FreeVarCounterQueueEntry = Unbind (Set.Set Identifier) | Next TypeQuery
147140
148−getFreeVariables :: TypeQuery -> Set.Set String
141+getFreeVariables :: TypeQuery -> Set.Set Identifier
149142getFreeVariables query = go Set.empty Set.empty (List.singleton $ Next query)
150143 where
151144 insertIfUnbound bound var free =
@@ -179,22 +172,32 @@ getFreeVariables query = go Set.empty Set.empty (List.singleton $ Next query)
179172 go bound free (Next (QRow lst) : rest) =
180173 go bound free ((lst <#> snd >>> Next) <> rest)
181174
175+
182176data Substitution
183− = Instantiate String Type
184− | Match String String
185− | Generalize TypeQuery String
186− | Substitute String String
187− | MatchConstraints (Set String) (Set String)
177+ = Instantiate Identifier Type
178+ | Match Identifier Identifier
179+ | Generalize TypeQuery Identifier
180+ | Substitute Identifier Identifier
181+ | MatchConstraints (Set Identifier) (Set Identifier)
188182 | MissingConstraint
189183 | ExcessiveConstraint
190184 | RowsMismatch Int Int
191− | Mismatch
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.
192191
193192derive instance genericSubstitution :: Generic Substitution _
194193
195194instance showSubstitution :: Show Substitution where
196195 show x = genericShow x
197196
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.
198201unify :: TypeQuery -> Type -> List Substitution
199202unify query type_ = go Nil (List.singleton { q: query, t: type_ })
200203 where
@@ -204,64 +207,61 @@ unify query type_ = go Nil (List.singleton { q: query, t: type_ })
204207 go acc ({ q, t } : rest)
205208
206209 -- * ForAll
207− go acc ({ q: (QForAll queryBinders q), t:type_1@(ForAll _ _ _) } : rest) =
208− let { binders, ty } = joinForAlls type_1 in
209− go acc ({ q, t: ty } : rest)
210− go acc ({ q, t: ForAll _ t _ } : rest) =
210+ go acc ({ q, t: ForAll _ _ t } : rest) =
211211 go acc ({ q, t } : rest)
212− go acc ({ q: (QForAll queryBinders q), t } : rest) =
212+ go acc ({ q: (QForAll _ q), t } : rest) =
213213 go acc ({ q, t } : rest)
214214
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+
215228 -- * Type variables
216229 go acc ({ q: QVar q, t: TypeVar v } : rest) =
217− go (Substitute q v : acc) rest
230+ go (Substitute q (Identifier v) : acc) rest
218231 go acc ({ q, t: TypeVar v } : rest ) =
219− go (Generalize q v : acc) rest
232+ go (Generalize q (Identifier v) : acc) rest
220233 go acc ({ q: QVar v, t } : rest) =
221234 go (Instantiate v t : acc) rest
222235
223236 -- * Names
224237 go acc ({ q: QConst qname, t: TypeConstructor (QualifiedName { name }) } : rest) =
225238 go (Match qname name : acc) rest
226− go acc ({ q: QConst qname, t } : rest) =
227− go (Mismatch : acc) rest
228− go acc ({ q, t: TypeConstructor (QualifiedName { name }) } : rest) =
229− go (Mismatch : 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
230243
231244 -- type operators can't appear in type queries: this is always a mismatch
232− go acc ({ q, t: TypeOp (QualifiedName { name }) } : rest) =
233− go (Mismatch : acc) rest
234− go acc ({ q, t: BinaryNoParensType _ _ _ } : rest) =
235− go (Mismatch : acc) rest
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
236249
237250 -- * Functions
238251 go acc ({ q: QFun q1 q2
239252 , t: TypeApp (TypeApp (TypeConstructor
240− (QualifiedName { moduleName: [ "Prim" ]
241− , name: "Function" })) t1) t2 } : rest) =
253+ (QualifiedName { moduleNameParts: [ "Prim" ]
254+ , name: Identifier "Function" })) t1) t2 } : rest) =
242255 go acc ({ q: q1, t: t1 } : { q: q2, t: t2 } : rest)
243− go acc ({ q: QFun q1 q2, t } : rest) =
244− go (Mismatch : acc) rest
245−
246− -- * Constraints
247− go acc ({ q: q@(QConstraint _ _ _)
248− , t: t@(ConstrainedType _ _) } : rest) =
249− let qcs = Set.fromFoldable (joinQueryConstraints q).constraints
250− tcs = Set.fromFoldable (joinConstraints t).constraints
251− in
252− -- TODO: use edit distance instead
253− go (MatchConstraints qcs tcs : acc) rest
254− go acc ({ q: QConstraint _ _ q, t } : rest) =
255− go (ExcessiveConstraint : acc) ({ q, t } : rest)
256− go acc ({ q, t: ConstrainedType _ t } : rest) =
257− go (MissingConstraint : acc) ({ q, t } : rest)
256+ go acc ({ q: q@(QFun _ _), t } : rest) =
257+ go (Mismatch q t : acc) rest
258258
259259 -- * Rows
260− go acc ({ q: QApp (QConst "Record") (QRow qRows)
260+ go acc ({ q: QApp (QConst (Identifier "Record")) (QRow qRows)
261261 , t: TypeApp (TypeConstructor
262− (QualifiedName { moduleName: [ "Prim" ]
263− , name: "Record" })) row } : rest) =
264− let { rows, ty } = joinRows row
262+ (QualifiedName { moduleNameParts: [ "Prim" ]
263+ , name: Identifier "Record" })) row } : rest) =
264+ let { rows } = joinRows row
265265 qRowsLength = List.length qRows
266266 rowsLength = List.length rows in
267267 if rowsLength == qRowsLength then
@@ -278,32 +278,41 @@ unify query type_ = go Nil (List.singleton { q: query, t: type_ })
278278 else
279279 go (RowsMismatch qRowsLength rowsLength : acc) rest
280280
281− go acc ({ q: QRow _ } : rest) =
282− go (Mismatch : acc) rest
281+ go acc ({ q: q@(QRow _), t } : rest) =
282+ go (Mismatch q t : acc) rest
283283
284284 -- * Type application
285285 go acc ({ q: QApp q1 q2, t: TypeApp t1 t2 } : rest) =
286286 go acc ({ q: q1, t: t1 } : { q: q2, t: t2 } : rest)
287287
288288 go acc ({ q, t: TypeLevelString _ } : rest) =
289− go (Mismatch : acc) rest
289+ go (QueryMismatch q : acc) rest
290290
291291 go acc ({ q, t: TypeWildcard } : rest) =
292− go (Mismatch : acc) rest
292+ go (QueryMismatch q : acc) rest
293293
294− go acc ({ q, t: RCons _ _ _ } : rest) =
295− go (Mismatch : acc) rest
294+ go acc ({ q, t: t@(RCons _ _ _) } : rest) =
295+ go (Mismatch q t : acc) rest
296296
297297 go acc ({ q, t: REmpty } : rest) =
298− go (Mismatch : acc) rest
298+ go (QueryMismatch q : acc) rest
299+
300+ go acc ({ q, t: Kinded _ _} : rest) =
301+ go (QueryMismatch q : acc) rest
299302
303+ go acc ({ t: t@(KindApp _ _) } : rest) =
304+ go (TypeMismatch t : acc) rest
305+
306+
307+-- | Sum various penalties.
300308penalty :: TypeQuery -> Type -> Int
301309penalty typeQuery ty =
302310 let substs = unify typeQuery ty in
303− typeVarPenalty substs * config.penalties.typeVars +
311+ typeVarPenalty substs * Config.penalties.typeVars +
304312 namesPenalty substs +
305313 mismatchPenalty substs
306314
315+
307316-- | Penalty for type variables mismatch.
308317-- | Congruent types should receive zero penalty points.
309318typeVarPenalty :: List Substitution -> Int
@@ -318,47 +327,114 @@ typeVarPenalty substs =
318327 insertion v1 v2 = Map.insertWith append v1 (Set.singleton v2)
319328
320329 varSubstMapWith
321− :: (String -> String -> Map String (Set String) -> Map String (Set String))
322− -> Map String (Set String)
330+ :: (Identifier ->
331+ Identifier ->
332+ Map Identifier (Set Identifier) ->
333+ Map Identifier (Set Identifier))
334+ -> Map Identifier (Set Identifier)
323335 varSubstMapWith f =
324336 List.foldr (case _ of
325337 Substitute v1 v2 ->
326338 f v1 v2
327339 _ -> identity
328− ) mempty substs
340+ ) Map.empty substs
341+
329342
330343-- | Penalty for name mismatches.
331344namesPenalty :: List Substitution -> Int
332345namesPenalty = go 0
333346 where
334347 go p Nil = p
335− go p (Mismatch : rest) = go (p + config.penalties.mismatch) rest
336348 go p (Match a b : rest)
337349 | a == b = go p rest
338− | otherwise = go (p + config.penalties.match) rest
350+ | otherwise = go (p + Config.penalties.match) rest
339351 go p (MatchConstraints qcs tcs : rest)
340352 = let p' = Set.size (Set.union qcs tcs) -
341353 Set.size (Set.intersection qcs tcs) in
342− go (p + config.penalties.matchConstraint * p') rest
343− go p (RowsMismatch n m : rest) = go (config.penalties.rowsMismatch * abs (n - m)) rest
354+ go (p + Config.penalties.matchConstraint * p') rest
355+ go p (RowsMismatch n m : rest) = go (Config.penalties.rowsMismatch * abs (n - m)) rest
344356 go p (_ : rest) = go p rest
345357
358+
346359-- | Penalty for generalization and instantiation.
347360mismatchPenalty :: List Substitution -> Int
348361mismatchPenalty = go 0
349362 where
350363 go n Nil = n
351− go n (Instantiate _ _ : rest) = go (n + config.penalties.instantiate) rest
352− go n (Generalize _ _ : rest) = go (n + config.penalties.generalize) rest
353− go n (ExcessiveConstraint : rest) = go (n + config.penalties.excessiveConstraint) rest
354− go n (MissingConstraint : rest) = go (n + config.penalties.missingConstraint) rest
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
355373 go n (_ : rest) = go n rest
356374
375+
357376-- | Only returns a list of type class names (lists of arguments are omitted).
358−joinQueryConstraints :: TypeQuery -> { constraints :: List String
377+joinQueryConstraints :: TypeQuery -> { constraints :: List Identifier
359378 , ty :: TypeQuery }
360379joinQueryConstraints = go Nil
361380 where
362381 go acc (QConstraint name _ query) =
363382 go (name : acc) query
364383 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.js+1−1
@@ -1,6 +1,6 @@
11/* global exports */
22
3−exports.hash = function (string) {
3+export function hash (string) {
44 var hash = Math.floor(Number.MAX_SAFE_INTEGER / 2);
55 if (string.length == 0) {
66 return hash;
src/Docs/Search/TypeShape.purs+20−5
@@ -1,12 +1,16 @@
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.
14module Docs.Search.TypeShape where
25
3−import Prelude
4−
56import Docs.Search.TypeDecoder (QualifiedName(..), Type(..), joinForAlls, joinRows)
67import Docs.Search.TypeQuery (TypeQuery(..), getFreeVariables)
8+import Docs.Search.Types (Identifier(..))
79
10+import Prelude
11+import Prim hiding (Type)
812import Data.Generic.Rep (class Generic)
9−import Data.Generic.Rep.Show (genericShow)
13+import Data.Show.Generic (genericShow)
1014import Data.List (List(..), (:))
1115import Data.List as List
1216import Data.List.NonEmpty as NonEmptyList
@@ -14,8 +18,10 @@ import Data.Set as Set
1418import Data.Tuple (Tuple(..), snd)
1519import Data.Ord (abs)
1620
21+
1722type TypeShape = List ShapeChunk
1823
24+
1925data ShapeChunk
2026 = PVar
2127 | PFun
@@ -30,6 +36,7 @@ derive instance genericShapeChunk :: Generic ShapeChunk _
3036instance showShapeChunk :: Show ShapeChunk where
3137 show x = genericShow x
3238
39+
3340stringifyShape :: TypeShape -> String
3441stringifyShape shape =
3542 show $ abs $ hash
@@ -44,6 +51,7 @@ stringifyShape shape =
4451 PForAll n -> "b" <> show n
4552 PRow n -> "r" <> show n
4653
54+
4755shapeOfTypeQuery :: TypeQuery -> TypeShape
4856shapeOfTypeQuery query =
4957 prependForAll $ List.reverse $ go (pure query) Nil
@@ -77,6 +85,7 @@ shapeOfTypeQuery query =
7785 let lst' = List.sortBy (\(Tuple x _) (Tuple y _) -> compare x y) lst in
7886 go (map snd lst' <> rest) (PRow (List.length lst) : acc)
7987
88+
8089shapeOfType :: Type -> TypeShape
8190shapeOfType ty = List.reverse $ go (pure ty) Nil
8291 where
@@ -93,8 +102,8 @@ shapeOfType ty = List.reverse $ go (pure ty) Nil
93102 TypeWildcard ->
94103 go rest (PVar : acc)
95104
96− TypeApp (TypeApp (TypeConstructor (QualifiedName { moduleName: ["Prim"]
97− , name: "Function" })) t1) t2 ->
105+ TypeApp (TypeApp (TypeConstructor (QualifiedName { moduleNameParts: ["Prim"]
106+ , name: Identifier "Function" })) t1) t2 ->
98107 go (t1 : t2 : rest) (PFun : acc)
99108
100109 TypeConstructor (QualifiedName { name }) ->
@@ -106,6 +115,9 @@ shapeOfType ty = List.reverse $ go (pure ty) Nil
106115 TypeApp child1 child2 ->
107116 go (child1 : child2 : rest) (PApp : acc)
108117
118+ KindApp child1 child2 ->
119+ go (child1 : child2 : rest) (PApp : acc)
120+
109121 forallType@(ForAll _ _ _) ->
110122 go (foralls.ty : rest) (PForAll (List.length foralls.binders) : acc)
111123 where foralls = joinForAlls forallType
@@ -127,7 +139,10 @@ shapeOfType ty = List.reverse $ go (pure ty) Nil
127139 sorted = List.sortBy (\x y -> compare x.row y.row) joined.rows
128140 typesInRow = sorted <#> (_.ty)
129141
142+ Kinded t1 _t2 -> go (t1 : rest) acc
143+
130144 BinaryNoParensType op l r ->
131145 go (TypeApp (TypeApp op l) r : rest) acc
132146
147+
133148foreign 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 ""
test/Main.purs+18−414
@@ -2,424 +2,28 @@ module Test.Main where
22
33import Prelude
44
5−import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), Kind(..), QualifiedName(..), Type(..))
6−import Test.TypeQuery as TypeQuery
7−
8−import Data.Argonaut.Decode (decodeJson)
9−import Data.Argonaut.Encode (encodeJson)
10−import Data.Argonaut.Parser (jsonParser)
11−import Data.Either (Either(..), fromRight)
12−import Data.Maybe (Maybe(..))
135import Effect (Effect)
14−import Effect.Aff (Aff)
15−import Partial.Unsafe (unsafePartial)
16−import Test.Unit (TestSuite, suite, test)
17−import Test.Unit.Assert as Assert
18−import Test.Unit.Main (runTest)
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
15+import Test.TypeQuery as TypeQuery
1916
2017main :: Effect Unit
2118main = do
22− runTest mainTest
19+ launchAff_ $ runSpec [consoleReporter] mainTest
20+ -- UI.main
2321
24−mainTest :: TestSuite
22+mainTest :: Spec Unit
2523mainTest = do
24+ ModuleParser.tests
2625 TypeQuery.tests
27−
28− let mkJson x = unsafePartial $ fromRight $ jsonParser x
29− suite "FunDeps decoder" do
30− test "FunDeps" do
31− let funDeps = mkJson """
32− [
33− [
34− [
35− "lhs",
36− "rhs"
37− ],
38− [
39− "output"
40− ]
41− ]
42− ]
43− """
44− assertRight (decodeJson funDeps)
45− (FunDeps [ FunDep { lhs: [ "lhs", "rhs" ]
46− , rhs: [ "output"]
47− }
48− ])
49−
50− suite "Kind decoder" do
51−
52− test "QualifiedName" do
53−
54− let qualifiedName = mkJson """
55− [
56− [
57− "Prim"
58− ],
59− "Type"
60− ]
61− """
62−
63− assertRight (decodeJson qualifiedName)
64− (QualifiedName { moduleName: ["Prim"]
65− , name: "Type"
66− }
67− )
68−
69− test "NamedKind" do
70− let namedKind = mkJson """
71− {
72− "annotation": [],
73− "tag": "NamedKind",
74− "contents": [
75− [
76− "Prim"
77− ],
78− "Type"
79− ]
80− }
81− """
82−
83− assertRight (decodeJson namedKind)
84− (NamedKind $ QualifiedName { moduleName: ["Prim"]
85− , name: "Type"
86− }
87− )
88−
89− test "Row" do
90− let row = mkJson """
91− {
92− "annotation": [],
93− "tag": "Row",
94− "contents": {
95− "annotation": [],
96− "tag": "NamedKind",
97− "contents": [
98− [
99− "Prim"
100− ],
101− "Type"
102− ]
103− }
104− }
105− """
106−
107− assertRight (decodeJson row)
108− (Row $ NamedKind $ QualifiedName { moduleName: ["Prim"]
109− , name: "Type"
110− }
111− )
112−
113− test "FunKind" do
114− let funKind = mkJson """
115− {
116− "annotation": [],
117− "tag": "FunKind",
118− "contents": [
119− {
120− "annotation": [],
121− "tag": "Row",
122− "contents": {
123− "annotation": [],
124− "tag": "NamedKind",
125− "contents": [
126− [
127− "Prim"
128− ],
129− "Type"
130− ]
131− }
132− },
133− {
134− "annotation": [],
135− "tag": "Row",
136− "contents": {
137− "annotation": [],
138− "tag": "NamedKind",
139− "contents": [
140− [
141− "Prim"
142− ],
143− "Type"
144− ]
145− }
146− }
147− ]
148− }
149− """
150− assertRight (decodeJson funKind)
151− (FunKind (Row $ NamedKind $ QualifiedName { moduleName: ["Prim"]
152− , name: "Type"
153− }
154− )
155− (Row $ NamedKind $ QualifiedName { moduleName: ["Prim"]
156− , name: "Type"
157− }
158− )
159− )
160−
161−
162− suite "Constraint decoder" do
163− test "Constraint" do
164− let constraint = mkJson """
165− {
166− "constraintAnn": [],
167− "constraintClass": [
168− [
169− "Prim"
170− ],
171− "Partial"
172− ],
173− "constraintArgs": [],
174− "constraintData": null
175− }
176− """
177− assertRight (decodeJson constraint)
178− (Constraint { constraintClass: QualifiedName { moduleName: ["Prim"]
179− , name: "Partial"
180− }
181− , constraintArgs: []
182− })
183−
184− suite "Type decoder" do
185− test "TypeVar" do
186− let typeVar = mkJson """
187− {
188− "annotation": [],
189− "tag": "TypeVar",
190− "contents": "m"
191− }
192− """
193−
194− assertRight (decodeJson typeVar)
195− (TypeVar "m")
196−
197− test "TypeApp" do
198− let typeApp1 = mkJson """
199− {
200− "annotation": [],
201− "tag": "TypeApp",
202− "contents": [
203− {
204− "annotation": [],
205− "tag": "TypeConstructor",
206− "contents": [
207− [
208− "Control",
209− "Monad",
210− "ST",
211− "Internal"
212− ],
213− "ST"
214− ]
215− },
216− {
217− "annotation": [],
218− "tag": "TypeVar",
219− "contents": "h"
220− }
221− ]
222− }
223− """
224−
225− assertRight (decodeJson typeApp1) $
226− TypeApp
227− (TypeConstructor (QualifiedName { moduleName:
228− [ "Control"
229− , "Monad"
230− , "ST"
231− , "Internal"
232− ],
233− name: "ST"
234− }
235− ))
236− (TypeVar "h")
237−
238− test "TypeOp" do
239− let typeOp = mkJson """
240− {
241− "annotation": [],
242− "tag": "TypeOp",
243− "contents": [
244− [
245− "Data",
246− "NaturalTransformation"
247− ],
248− "~>"
249− ]
250− }
251− """
252− assertRight (decodeJson typeOp) $
253− TypeOp $ QualifiedName { moduleName: [ "Data", "NaturalTransformation" ]
254− , name: "~>"
255− }
256−
257− test "BinaryNoParens" do
258− let binaryNoParens = mkJson """
259− {
260− "annotation": [],
261− "tag": "BinaryNoParensType",
262− "contents": [
263− {
264− "annotation": [],
265− "tag": "TypeOp",
266− "contents": [
267− [
268− "Data",
269− "NaturalTransformation"
270− ],
271− "~>"
272− ]
273− },
274− {
275− "annotation": [],
276− "tag": "TypeVar",
277− "contents": "m"
278− },
279− {
280− "annotation": [],
281− "tag": "TypeVar",
282− "contents": "n"
283− }
284− ]
285− }
286− """
287−
288− assertRight (decodeJson binaryNoParens) $
289− BinaryNoParensType
290− (TypeOp $ QualifiedName { moduleName: ["Data", "NaturalTransformation"], name: "~>" })
291− (TypeVar "m")
292− (TypeVar "n")
293−
294− test "ParensInType" do
295− let parensInType = mkJson """
296− {
297− "annotation": [],
298− "tag": "ParensInType",
299− "contents": {
300− "annotation": [],
301− "tag": "TypeApp",
302− "contents": [
303− {
304− "annotation": [],
305− "tag": "TypeConstructor",
306− "contents": [
307− [
308− "Data",
309− "Maybe"
310− ],
311− "Maybe"
312− ]
313− },
314− {
315− "annotation": [],
316− "tag": "TypeConstructor",
317− "contents": [
318− [
319− "Prim"
320− ],
321− "String"
322− ]
323− }
324− ]
325− }
326− }
327− """
328−
329− assertRight (decodeJson parensInType) $
330− ParensInType $
331− TypeApp
332− (TypeConstructor (QualifiedName { moduleName:
333− [ "Data"
334− , "Maybe"
335− ],
336− name: "Maybe"
337− }
338− ))
339− (TypeConstructor (QualifiedName { moduleName:
340− [ "Prim"
341− ],
342− name: "String"
343− }
344− ))
345− test "RCons" do
346−
347− let rcons = mkJson """
348− {
349− "annotation": [],
350− "tag": "RCons",
351− "contents": [
352− "tail",
353− {
354− "annotation": [],
355− "tag": "TypeApp",
356− "contents": [
357− {
358− "annotation": [],
359− "tag": "TypeConstructor",
360− "contents": [
361− [
362− "Data",
363− "Symbol"
364− ],
365− "SProxy"
366− ]
367− },
368− {
369− "annotation": [],
370− "tag": "TypeVar",
371− "contents": "t"
372− }
373− ]
374− },
375− {
376− "annotation": [],
377− "tag": "REmpty"
378− }
379− ]
380− }
381− """
382−
383− assertRight (decodeJson rcons) $
384− RCons
385− "tail"
386− (TypeApp (TypeConstructor $ QualifiedName { moduleName: [ "Data", "Symbol" ], name: "SProxy" })
387− (TypeVar "t"))
388− REmpty
389−
390− test "ForAll" do
391− let forallJson = mkJson """
392− {"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]}
393− """
394− assertRight (decodeJson forallJson) $
395− ForAll "a" (TypeApp (TypeApp (TypeConstructor $ qualified ["Prim"] "Function")
396− (TypeConstructor $ qualified ["Prim"] "String"))
397− (TypeVar "a")) Nothing
398− suite "jsons" do
399−
400− test "jsons #1" do
401− let json = mkJson """
402−{"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]}
403− """
404−
405− 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)
406−
407− suite "Kind encoder" do
408− test "FunKind" do
409− let k1 =
410− FunKind (Row (NamedKind $ qualified [] "a"))
411− (FunKind (NamedKind $ qualified [] "b")
412− (NamedKind $ qualified [] "b"))
413− assertRight (decodeJson $ encodeJson $ k1) k1
414−
415−
416−qualified :: Array String -> String -> QualifiedName
417−qualified moduleName name = QualifiedName { moduleName, name }
418−
419−assertRight :: forall a. Show a => Eq a => Either String a -> a -> Aff Unit
420−assertRight eiActual expected =
421− case eiActual of
422− Left string -> do
423− Assert.equal (Right expected) eiActual
424− Right actual -> do
425− Assert.equal (Right expected) eiActual
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+438−246
@@ -1,12 +1,12 @@
11module Test.TypeQuery where
22
3−import Prelude
4−
3+import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..))
54import Docs.Search.TypeQuery (Substitution(..), TypeQuery(..), getFreeVariables, parseTypeQuery, penalty, typeVarPenalty)
6−import Docs.Search.TypeShape (ShapeChunk(..), shapeOfTypeQuery)
7−import Docs.Search.TypeDecoder (QualifiedName(..), Type(..))
5+import Docs.Search.TypeShape (ShapeChunk(..), shapeOfType, shapeOfTypeQuery)
6+import Docs.Search.Types (Identifier(..))
87
9−import Data.Either (Either(..))
8+import Prelude
9+import Prim hiding (Constraint, Type)
1010import Data.Foldable (class Foldable)
1111import Data.List (List(..), (:))
1212import Data.List as List
@@ -14,375 +14,567 @@ import Data.List.NonEmpty (NonEmptyList)
1414import Data.List.NonEmpty as NonEmptyList
1515import Data.Set as Set
1616import Data.Tuple (Tuple(..))
17−import Effect.Aff (Aff)
18−import Test.Unit (TestSuite, suite, test)
19−import Test.Unit.Assert as Assert
17+import Test.Extra (assertRight)
18+import Test.Spec (Spec, describe, it)
19+import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
20+import Data.Maybe (Maybe(..))
2021
21−tests :: TestSuite
22+tests :: Spec Unit
2223tests = do
23− suite "TypeQuery parser" do
24+ describe "TypeQuery parser" do
2425
25− test "test #0" do
26+ it "test #0" do
2627 let input = "a"
27− assertRight (parseTypeQuery input) (QVar "a")
28+ assertRight (parseTypeQuery input) (qVar "a")
2829
29− test "test #1" do
30+ it "test #1" do
3031 let input = "ab"
31− assertRight (parseTypeQuery input) (QVar "ab")
32+ assertRight (parseTypeQuery input) (qVar "ab")
3233
33− test "test #2" do
34+ it "test #2" do
3435 let input = "a b"
35− assertRight (parseTypeQuery input) (QApp (QVar "a") (QVar "b"))
36+ assertRight (parseTypeQuery input) (QApp (qVar "a") (qVar "b"))
3637
37− test "test #3" do
38+ it "test #3" do
3839 let input = "a b c"
39− assertRight (parseTypeQuery input) (QApp (QApp (QVar "a") (QVar "b")) (QVar "c"))
40+ assertRight (parseTypeQuery input) (QApp (QApp (qVar "a") (qVar "b")) (qVar "c"))
4041
41− test "test #4" do
42+ it "test #4" do
4243 let input = "a -> b"
43− assertRight (parseTypeQuery input) (QFun (QVar "a") (QVar "b"))
44+ assertRight (parseTypeQuery input) (QFun (qVar "a") (qVar "b"))
4445
45− test "test #5" do
46+ it "test #5" do
4647 let input = "a -> b c"
47− assertRight (parseTypeQuery input) (QFun (QVar "a") (QApp (QVar "b") (QVar "c")))
48+ assertRight (parseTypeQuery input) (QFun (qVar "a") (QApp (qVar "b") (qVar "c")))
4849
49− test "test #6" do
50+ it "test #6" do
5051 let input = "a b -> c"
51− assertRight (parseTypeQuery input) (QFun (QApp (QVar "a") (QVar "b")) (QVar "c"))
52+ assertRight (parseTypeQuery input) (QFun (QApp (qVar "a") (qVar "b")) (qVar "c"))
5253
53− test "test #7" do
54+ it "test #7" do
5455 let input = "a b"
55− assertRight (parseTypeQuery input) (QApp (QVar "a") (QVar "b"))
56+ assertRight (parseTypeQuery input) (QApp (qVar "a") (qVar "b"))
5657
57− test "test #8" do
58+ it "test #8" do
5859 let input = "a (b c)"
59− assertRight (parseTypeQuery input) (QApp (QVar "a") (QApp (QVar "b") (QVar "c")))
60+ assertRight (parseTypeQuery input) (QApp (qVar "a") (QApp (qVar "b") (qVar "c")))
6061
61− test "test #9" do
62+ it "test #9" do
6263 let input = "(a b) (c d)"
6364 assertRight (parseTypeQuery input)
64− (QApp (QApp (QVar "a") (QVar "b"))
65− (QApp (QVar "c") (QVar "d")))
65+ ( QApp (QApp (qVar "a") (qVar "b"))
66+ (QApp (qVar "c") (qVar "d"))
67+ )
6668
67− test "test #10" do
69+ it "test #10" do
6870 let input = "a ( b c )"
69− assertRight (parseTypeQuery input) (QApp (QVar "a") (QApp (QVar "b") (QVar "c")))
71+ assertRight (parseTypeQuery input) (QApp (qVar "a") (QApp (qVar "b") (qVar "c")))
7072
71− test "test #11" do
73+ it "test #11" do
7274 let input = "aaa"
73− assertRight (parseTypeQuery input) (QVar "aaa")
75+ assertRight (parseTypeQuery input) (qVar "aaa")
7476
75− test "test #12" do
77+ it "test #12" do
7678 let input = "aaa ( bbb ccc )"
77− assertRight (parseTypeQuery input) (QApp (QVar "aaa") (QApp (QVar "bbb") (QVar "ccc")))
79+ assertRight (parseTypeQuery input) (QApp (qVar "aaa") (QApp (qVar "bbb") (qVar "ccc")))
7880
79− test "test #13" do
81+ it "test #13" do
8082 let input = "(a -> b) -> (c -> d)"
81− assertRight (parseTypeQuery input) (QFun (QFun (QVar "a") (QVar "b"))
82− (QFun (QVar "c") (QVar "d")))
83+ assertRight (parseTypeQuery input)
84+ ( QFun (QFun (qVar "a") (qVar "b"))
85+ (QFun (qVar "c") (qVar "d"))
86+ )
8387
84− test "test #14" do
88+ it "test #14" do
8589 let input = "a -> b -> c -> d"
86− assertRight (parseTypeQuery input) (QFun (QVar "a")
87− (QFun (QVar "b")
88− (QFun (QVar "c") (QVar "d"))))
90+ assertRight (parseTypeQuery input)
91+ ( QFun (qVar "a")
92+ ( QFun (qVar "b")
93+ (QFun (qVar "c") (qVar "d"))
94+ )
95+ )
8996
90− test "test #15" do
97+ it "test #15" do
9198 let input = "a -> b -> c"
92− assertRight (parseTypeQuery input) (QFun (QVar "a")
93− (QFun (QVar "b")
94− (QVar "c")))
99+ assertRight (parseTypeQuery input)
100+ ( QFun (qVar "a")
101+ ( QFun (qVar "b")
102+ (qVar "c")
103+ )
104+ )
95105
96− test "test #16" do
106+ it "test #16" do
97107 let input = "forall a b c. c"
98− assertRight (parseTypeQuery input) (QForAll (nl "a" ["b", "c"]) (QVar "c"))
108+ assertRight (parseTypeQuery input) (QForAll (nl "a" [ "b", "c" ]) (qVar "c"))
99109
100− test "test #17" do
110+ it "test #17" do
101111 let input = "forall a. Maybe a"
102− assertRight (parseTypeQuery input) (QForAll (nl "a" $ []) (QApp (QConst "Maybe") (QVar "a")))
112+ assertRight (parseTypeQuery input) (QForAll (nl "a" $ []) (QApp (qConst "Maybe") (qVar "a")))
103113
104− test "test #18" do
114+ it "test #18" do
105115 let input = "forall m a. Monad m => a -> m a"
106116 assertRight (parseTypeQuery input)
107− (QForAll (nl "m" ["a"])
108− (QConstraint "Monad" (l [QVar "m"])
109− (QFun (QVar "a")
110− (QApp (QVar "m") (QVar "a")))))
111−
112− test "test #19" do
117+ ( QForAll (nl "m" [ "a" ])
118+ ( qConstraint "Monad" (l [ qVar "m" ])
119+ ( QFun (qVar "a")
120+ (QApp (qVar "m") (qVar "a"))
121+ )
122+ )
123+ )
124+
125+ it "test #19" do
113126 let input = "{ a :: Int }"
114127 assertRight (parseTypeQuery input)
115− (QApp (QConst "Record") (QRow (pure (Tuple "a" (QConst "Int")))))
128+ (QApp (qConst "Record") (QRow (pure (Tuple (Identifier "a") (qConst "Int")))))
116129
117− test "test #20" do
130+ it "test #20" do
118131 let input = "{a::Int}"
119132 assertRight (parseTypeQuery input)
120− (QApp (QConst "Record") (QRow (pure (Tuple "a" (QConst "Int")))))
133+ (QApp (qConst "Record") (QRow (pure (Tuple (Identifier "a") (qConst "Int")))))
121134
122− test "test #21" do
135+ it "test #21" do
123136 let input = "Int"
124− assertRight (parseTypeQuery input) (QConst "Int")
137+ assertRight (parseTypeQuery input) (qConst "Int")
125138
126− test "test #22" do
139+ it "test #22" do
127140 let input = "a->b"
128− assertRight (parseTypeQuery input) (QFun (QVar "a") (QVar "b"))
141+ assertRight (parseTypeQuery input) (QFun (qVar "a") (qVar "b"))
129142
130− test "test #23" do
143+ it "test #23" do
131144 let input = "forall m a. MonadRec m => Process m a -> m a"
132− assertRight (parseTypeQuery input) (QForAll (nl "m" ("a" : Nil))
133− (QConstraint "MonadRec" (l [QVar "m"])
134− (QFun (QApp (QApp (QConst "Process")
135− (QVar "m")) (QVar "a"))
136− (QApp (QVar "m") (QVar "a")))))
137−
138− test "test #24" do
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
139161 let input = "forall t f a. Foldable1 t => Apply f => f"
140− assertRight (parseTypeQuery input) (QForAll (nl "t" ["f", "a"])
141− (QConstraint "Foldable1" (l [QVar "t"])
142− (QConstraint "Apply" (l [QVar "f"]) (QVar "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+ )
143168
144− test "test #25" do
169+ it "test #25" do
145170 let input = "forall m a.MonadRec m=>Process m a->m a"
146− assertRight (parseTypeQuery input) ((QForAll (nl "m" ("a" : Nil))
147− (QConstraint "MonadRec" (l [QVar "m"])
148− (QFun (QApp (QApp (QConst "Process")
149− (QVar "m")) (QVar "a"))
150− (QApp (QVar "m") (QVar "a"))))))
151−
152− test "test #26" do
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
153188 let input = "m a -> (a -> m b) -> m b"
154− assertRight (parseTypeQuery input) (QFun (QApp (QVar "m") (QVar "a")) (QFun (QFun (QVar "a") (QApp (QVar "m") (QVar "b"))) (QApp (QVar "m") (QVar "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"))))
155190
156− test "test #27" do
191+ it "test #27" do
157192 let input = "forall f a. Alternative f => Lazy (f (List a)) => f a -> f (List a)"
158− assertRight (parseTypeQuery input) ((QForAll (nl "f" ["a"]))
159− (QConstraint "Alternative" (l [QVar "f"])
160− (QConstraint "Lazy" (l [QApp (QVar "f")
161− (QApp (QConst "List") (QVar "a"))])
162− (QFun (QApp (QVar "f") (QVar "a"))
163− (QApp (QVar "f")
164− (QApp (QConst "List") (QVar "a")))))))
165−
166− test "test #28" do
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
167212 let input = "forall f a. Alternative f => Lazy(f (List a))=>f a -> f (List a)"
168− assertRight (parseTypeQuery input) ((QForAll (nl "f" ["a"]))
169− (QConstraint "Alternative" (l [QVar "f"])
170− (QConstraint "Lazy" (l [QApp (QVar "f")
171− (QApp (QConst "List") (QVar "a"))])
172− (QFun (QApp (QVar "f") (QVar "a"))
173− (QApp (QVar "f")
174− (QApp (QConst "List") (QVar "a")))))))
175−
176− test "test #29" do
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
177232 let input = "{a::Int,b::Int}"
178233 assertRight (parseTypeQuery input)
179− (QApp (QConst "Record") (QRow (List.fromFoldable [ Tuple "a" (QConst "Int"), Tuple "b" (QConst "Int")])))
180−
181− test "test #30" do
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
182245 let input = "{record''' :: Int}"
183246 assertRight (parseTypeQuery input)
184− (QApp (QConst "Record") (QRow (List.fromFoldable [ Tuple "record'''" (QConst "Int")])))
247+ (QApp (qConst "Record") (QRow (List.fromFoldable [ Tuple (Identifier "record'''") (qConst "Int") ])))
185248
186− test "test #31" do
249+ it "test #31" do
187250 let input = "(row''' :: Int)"
188251 assertRight (parseTypeQuery input)
189− (QRow (List.fromFoldable [ Tuple "row'''" (QConst "Int")]))
252+ (QRow (List.fromFoldable [ Tuple (Identifier "row'''") (qConst "Int") ]))
190253
191− test "test #32" do
254+ it "test #32" do
192255 let input = "(row1 :: Int, row2 :: (),row3::(row4::{}))"
193256 assertRight (parseTypeQuery input)
194− (QRow (l [ Tuple "row1" (QConst "Int")
195− , Tuple "row2" (QRow Nil)
196− , Tuple "row3" (QRow (l [ Tuple "row4" (QApp (QConst "Record") (QRow Nil)) ])) ]))
197−
198− test "test #33" do
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
199267 let input = "Foldable1 t => Apply f => t (f a) -> f Unit"
200268 assertRight (parseTypeQuery input)
201− (QConstraint "Foldable1" ((QVar "t") : Nil) (QConstraint "Apply" ((QVar "f") : Nil) (QFun (QApp (QVar "t") (QApp (QVar "f") (QVar "a"))) (QApp (QVar "f") (QConst "Unit")))))
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")))))
202270
203− test "test #34" do
271+ it "test #34" do
204272 let input = "Foldable1 t => Apply f => t (f a) -> f a"
205273 assertRight (parseTypeQuery input)
206− (QConstraint "Foldable1" ((QVar "t") : Nil) (QConstraint "Apply" ((QVar "f") : Nil) (QFun (QApp (QVar "t") (QApp (QVar "f") (QVar "a"))) (QApp (QVar "f") (QVar "a")))))
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+ )
207284
208− suite "polish notation" do
285+ describe "polish notation" do
209286
210− test "test #1" do
287+ it "test #1" do
211288 let input = "(a -> b) -> (b -> ((a -> b) -> c))"
212289 assertRight (shapeOfTypeQuery <$> parseTypeQuery input)
213− (l [ PForAll 3, PFun, PFun, PVar, PVar, PFun, PVar, PFun, PFun
214− , PVar, PVar, PVar ])
215−
216− test "test #2" do
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
217307 let input = "forall a. (a -> b) -> (b -> ((a -> b) -> c))"
218308 assertRight (shapeOfTypeQuery <$> parseTypeQuery input)
219− (l [ PForAll 3, PFun, PFun, PVar, PVar, PFun, PVar, PFun, PFun
220− , PVar, PVar, PVar ])
221−
222− test "test #3" do
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
223326 let input = "forall a. (a -> b) -> (b -> ((a -> b) -> c))"
224327 assertRight (shapeOfTypeQuery <$> parseTypeQuery input)
225− (l [ PForAll 3, PFun, PFun, PVar, PVar, PFun, PVar, PFun, PFun
226− , PVar, PVar, PVar ])
227−
228− test "test #4" do
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
229345 let input = "forall a. (forall h. ST h (STArray h a)) -> Array a"
230346 assertRight (shapeOfTypeQuery <$> parseTypeQuery input)
231− (l [ PForAll 1, PFun, PForAll 1, PApp, PApp, PVar, PVar, PApp, PApp, PVar, PVar, PVar, PApp, PVar, PVar ])
232−
233− suite "free variable counting" do
234−
235− test "test #1" do
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
236387 let input = "forall a. (a -> b) -> (b -> ((a -> b) -> c))"
237388 assertRight (countFreeVars <$> parseTypeQuery input) 2
238389
239− test "test #2" do
390+ it "test #2" do
240391 -- `b` is not bound on the left, `a` is not bound on the right
241392 let input = "(forall a. (a -> b)) -> forall b. (b -> a)"
242393 assertRight (countFreeVars <$> parseTypeQuery input) 2
243394
244− test "test #3" do
395+ it "test #3" do
245396 let input = "a -> forall a. a"
246397 assertRight (countFreeVars <$> parseTypeQuery input) 1
247398
248− test "test #4" do
399+ it "test #4" do
249400 let input = "(forall a. a) -> a"
250401 assertRight (countFreeVars <$> parseTypeQuery input) 1
251402
252− test "test #5" do
403+ it "test #5" do
253404 let input = "forall a. a -> a"
254405 assertRight (countFreeVars <$> parseTypeQuery input) 0
255406
256− test "test #6" do
407+ it "test #6" do
257408 let input = "a -> b -> c"
258409 assertRight (countFreeVars <$> parseTypeQuery input) 3
259410
260− test "test #7" do
411+ it "test #7" do
261412 let input = "forall m a. Monad m => a -> m a"
262413 assertRight (countFreeVars <$> parseTypeQuery input) 0
263414
264− test "test #8" do
415+ it "test #8" do
265416 let input = "Monad m => a -> m a"
266417 assertRight (countFreeVars <$> parseTypeQuery input) 2
267418
268− test "test #9" do
419+ it "test #9" do
269420 let input = "Monad m => a -> a"
270421 assertRight (countFreeVars <$> parseTypeQuery input) 2
271422
272− test "test #10" do
423+ it "test #10" do
273424 let input = "forall a. (forall a. a) a"
274425 assertRight (countFreeVars <$> parseTypeQuery input) 0
275426
276− test "test #11" do
427+ it "test #11" do
277428 let input = "forall a. (forall b. a) a"
278429 assertRight (countFreeVars <$> parseTypeQuery input) 0
279430
280− test "test #12" do
431+ it "test #12" do
281432 let input = "forall a. (forall b. a) a b"
282433 assertRight (countFreeVars <$> parseTypeQuery input) 1
283434
284− suite "typeVarPenalty" do
285−
286− test "#0" do
287− Assert.equal 0 (typeVarPenalty mempty)
288−
289− test "#1" do
290− Assert.equal 0 (typeVarPenalty $ l [ Substitute "a" "b"
291− , Substitute "b" "a"
292− ])
293−
294− test "#2" do
295− Assert.equal 0 (typeVarPenalty $ l [ Substitute "a" "b"
296− , Substitute "a" "b"
297− , Substitute "a" "b"
298− ])
299−
300− test "#3" do
301− Assert.equal 1 (typeVarPenalty $ l [ Substitute "a" "b"
302− , Substitute "a" "c"
303− ])
304−
305− test "#4" do
306− Assert.equal 1 (typeVarPenalty $ l [ Substitute "a" "b"
307− , Substitute "b" "a"
308− , Substitute "b" "c"
309− ])
310−
311− test "#5" do
312− Assert.equal 0 (typeVarPenalty $ l [ Substitute "a" "b"
313− , Substitute "b" "c"
314− , Substitute "c" "a"
315− ])
316−
317− test "#6" do
318− Assert.equal 2 (typeVarPenalty $ l [ Substitute "a" "b"
319− , Substitute "a" "c"
320− , Substitute "a" "a"
321− ])
322−
323− test "#7" do
324− Assert.equal 2 (typeVarPenalty $ l [ Substitute "a" "a"
325− , Substitute "b" "a"
326− , Substitute "c" "a"
327− ])
328−
329− test "#8" do
330− Assert.equal 4 (typeVarPenalty $ l [ Substitute "a" "a"
331− , Substitute "b" "a"
332− , Substitute "c" "a"
333− , Substitute "a" "b"
334− , Substitute "a" "c"
335− , Substitute "a" "a"
336− ])
337−
338− test "#9" do
339− Assert.equal 0 (typeVarPenalty $ l [ Substitute "a" "e"
340− , Substitute "b" "d"
341− , Substitute "c" "f"
342− ])
343−
344− suite "unification" do
345− test "instantiation #0" do
346− let mVarQuery = QVar "m"
347− unitConstQuery = QConst "Unit"
348−
349− Assert.assert "instantiation #0" $
350− (penalty unitConstQuery unitType < penalty mVarQuery unitType)
351−
352− test "generalization #0" do
353− let query = QVar "m"
354− t1 = TypeVar "m"
355−
356− Assert.assert "qeneralization #0" $
357− (penalty query unitType > penalty query t1)
358−
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)
359540
360541l :: forall f. Foldable f => (forall a. f a -> List a)
361542l = List.fromFoldable
362543
363−nl :: forall t5 t6. Foldable t6 => t5 -> t6 t5 -> NonEmptyList t5
364−nl x rst = NonEmptyList.cons' x $ List.fromFoldable rst
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)
365552
366553unitType :: Type
367−unitType = TypeConstructor (QualifiedName { moduleName: []
368− , name: "Unit"
369− })
370−
371−assertRight
372− :: forall a b
373− . Show a
374− => Show b
375− => Eq a
376− => Eq b
377− => Either b a
378− -> a
379− -> Aff Unit
380−assertRight eiActual expected =
381− case eiActual of
382− Left string -> do
383− Assert.equal (Right expected) eiActual
384− Right actual -> do
385− Assert.equal (Right expected) eiActual
554+unitType = TypeConstructor
555+ ( QualifiedName
556+ { moduleNameParts: []
557+ , name: Identifier "Unit"
558+ }
559+ )
386560
387561countFreeVars :: TypeQuery -> Int
388562countFreeVars = 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