From 0952fce79358f6af7bc417d41fe6697aa1b5689f Mon Sep 17 00:00:00 2001 From: vxern Date: Mon, 28 Aug 2023 21:45:04 +0100 Subject: [PATCH] feat: Initial commit. --- .gitignore | 2 + .mocharc.json | 6 + .npmignore | 3 + CHANGELOG.md | 3 + LICENSE | 20 + README.md | 215 +++ package-lock.json | 1488 +++++++++++++++++ package.json | 56 + rome.json | 22 + src/constants/links.ts | 6 + src/constants/parts-of-speech.ts | 6 + src/constants/parts-of-speech/english.ts | 65 + src/constants/patterns.ts | 18 + src/constants/sections.ts | 6 + src/constants/sections/english.ts | 41 + src/constants/selectors.ts | 42 + src/index.ts | 51 + src/options.ts | 32 + src/parsers/parser.ts | 241 +++ src/parsers/parsers.ts | 68 + src/parsers/shared/alternative-forms.ts | 8 + .../shared/alternative-reconstructions.ts | 11 + src/parsers/shared/anagrams.ts | 8 + src/parsers/shared/conjugation.ts | 8 + src/parsers/shared/declension.ts | 8 + src/parsers/shared/definitions.ts | 136 ++ src/parsers/shared/description.ts | 8 + src/parsers/shared/etymology.ts | 30 + src/parsers/shared/examples.ts | 8 + src/parsers/shared/further-reading.ts | 8 + src/parsers/shared/glyph-origin.ts | 8 + src/parsers/shared/inflection.ts | 8 + src/parsers/shared/mutation.ts | 8 + src/parsers/shared/production.ts | 8 + src/parsers/shared/pronunciation.ts | 8 + src/parsers/shared/quotations.ts | 8 + src/parsers/shared/reconstruction-notes.ts | 8 + src/parsers/shared/references.ts | 8 + src/parsers/shared/relations.ts | 8 + src/parsers/shared/see-also.ts | 8 + src/parsers/shared/translations.ts | 8 + src/parsers/shared/trivia.ts | 8 + src/parsers/shared/usage-notes.ts | 8 + src/types.ts | 195 +++ src/utils.ts | 33 + test/index.spec.ts | 35 + tsconfig.json | 31 + 47 files changed, 3022 insertions(+) create mode 100644 .gitignore create mode 100644 .mocharc.json create mode 100644 .npmignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 rome.json create mode 100644 src/constants/links.ts create mode 100644 src/constants/parts-of-speech.ts create mode 100644 src/constants/parts-of-speech/english.ts create mode 100644 src/constants/patterns.ts create mode 100644 src/constants/sections.ts create mode 100644 src/constants/sections/english.ts create mode 100644 src/constants/selectors.ts create mode 100644 src/index.ts create mode 100644 src/options.ts create mode 100644 src/parsers/parser.ts create mode 100644 src/parsers/parsers.ts create mode 100644 src/parsers/shared/alternative-forms.ts create mode 100644 src/parsers/shared/alternative-reconstructions.ts create mode 100644 src/parsers/shared/anagrams.ts create mode 100644 src/parsers/shared/conjugation.ts create mode 100644 src/parsers/shared/declension.ts create mode 100644 src/parsers/shared/definitions.ts create mode 100644 src/parsers/shared/description.ts create mode 100644 src/parsers/shared/etymology.ts create mode 100644 src/parsers/shared/examples.ts create mode 100644 src/parsers/shared/further-reading.ts create mode 100644 src/parsers/shared/glyph-origin.ts create mode 100644 src/parsers/shared/inflection.ts create mode 100644 src/parsers/shared/mutation.ts create mode 100644 src/parsers/shared/production.ts create mode 100644 src/parsers/shared/pronunciation.ts create mode 100644 src/parsers/shared/quotations.ts create mode 100644 src/parsers/shared/reconstruction-notes.ts create mode 100644 src/parsers/shared/references.ts create mode 100644 src/parsers/shared/relations.ts create mode 100644 src/parsers/shared/see-also.ts create mode 100644 src/parsers/shared/translations.ts create mode 100644 src/parsers/shared/trivia.ts create mode 100644 src/parsers/shared/usage-notes.ts create mode 100644 src/types.ts create mode 100644 src/utils.ts create mode 100644 test/index.spec.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..763301f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ \ No newline at end of file diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 0000000..ada4de2 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/mocharc", + "loader": "ts-node/esm", + "spec": "test/**/*.spec.ts", + "timeout": 10000 +} \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8e15216 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +node_modules/ +src/ +test/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d99c78d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.0.1 + +- Initial commit. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..922eb11 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2023 Dorian "vxern" Oszczęda + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3d15f1 --- /dev/null +++ b/README.md @@ -0,0 +1,215 @@ +A lightweight scraper to fetch information about words in various languages from Wiktionary. + +## Table of contents + +- [Table of contents](#table-of-contents) +- [Usage](#usage) +- [Completeness](#completeness) + - [Features](#features) + - [Section support](#section-support) + - [Recognised parts of speech](#recognised-parts-of-speech) + - [Parts of speech](#parts-of-speech) + - [Morphemes](#morphemes) + - [Symbols](#symbols) + - [Phrases](#phrases) + - [Han characters and language-specific varieties](#han-characters-and-language-specific-varieties) + - [Other](#other) + - [Explicitly disallowed parts of speech](#explicitly-disallowed-parts-of-speech) + - [Library additions](#library-additions) + + +## Usage + +To start using the scraper, first install it using the following command: + +```shell +npm install wiktionary-scraper +``` + +The simplest way of using the scraper is as follows: + +```ts +import * as Wiktionary from "wiktionary-scraper"; + +const results = await Wiktionary.get("word"); +``` + +You can change the language of the target word by setting the `lemmaLanguage`: + +```ts +import * as Wiktionary from "wiktionary-scraper"; + +const results = await Wiktionary.get('o', { + lemmaLanguage: "Romanian", +}); +``` + +You can specify if redirects should be followed by setting `followRedirects` to `true`: + +```ts +import * as Wiktionary from "wiktionary-scraper"; + +// Redirects to and returns results for "Germany". +const results = await Wiktionary.get('germany', { + followRedirects: true, +}); +``` + +By default, the `User-Agent` header used in requests is filled in using a default value mentioning `wiktionary-scraper`. + +To remove it, set `userAgent` to `undefined`. + +If you want to change it, specify `userAgent`: + +```ts +import * as Wiktionary from "wiktionary-scraper"; + +const results = await Wiktionary.get('word', { + userAgent: "Your App (https://example.com)", +}); +``` + +You can also parse HTML of the website directly, bypassing the fetch step. + +ℹ️ Notice that, as opposed to `get()`, `parse()` is synchronous: + +```ts +import * as Wiktionary from "wiktionary-scraper"; + +const results = Wiktionary.parse(html); +``` + +## Completeness + +This library currently only supports the English version of Wiktionary. + +#### Features + +- Parses both single- and multiple-etymology entries. +- Recognises standard, non-standard and some explicitly disallowed parts of speech, as defined [here](https://en.wiktionary.org/wiki/Wiktionary:Entry_layout#Part_of_speech). In total, there are 60+ recognised parts of speech, which should cover the vast majority of definitions. + - Note, however, that it is very possible that the library will fail to recognise certain niche, non-standard parts of speech. Should you come across any, please post an issue. + +#### Section support + +- [ ] Description +- [ ] Glyph origin +- [x] Etymology +- [ ] Pronunciation +- [ ] Production +- [x] Definitions +- [ ] Usage notes +- [ ] Reconstruction notes +- [ ] _Inflection sections_: + - [ ] Inflection + - [ ] Conjugation + - [ ] Declension +- [ ] Mutation +- [ ] Quotations +- [ ] Alternative forms +- [ ] Alternative reconstructions +- [ ] _Relations_: + - [ ] Synonyms + - [ ] Antonyms + - [ ] Hypernyms + - [ ] Hyponyms + - [ ] Meronyms + - [ ] Holonyms + - [ ] Comeronyms + - [ ] Troponyms + - [ ] Parasynonyms + - [ ] Coordinate terms + - [ ] Derived terms + - [ ] Related terms +- [ ] Translations +- [ ] Trivia +- [ ] See also +- [ ] References +- [ ] Further reading +- [ ] Anagrams +- [ ] Examples + +#### Recognised parts of speech + +###### Parts of speech + +- Adjective +- Adverb +- Ambiposition +- Article +- Circumposition +- Classifier +- Conjunction +- Contraction +- Counter +- Determiner +- Ideophone +- Interjection +- Noun +- Numeral +- Participle +- Particle +- Postposition +- Preposition +- Pronoun +- Proper noun +- Verb + +###### Morphemes + +- Circumfix +- Combining form +- Infix +- Interfix +- Prefix +- Root +- Suffix + +###### Symbols + +- Diacritical mark +- Letter +- Ligature +- Number +- Punctuation mark +- Syllable +- Symbol + +###### Phrases + +- Phrase +- Proverb +- Prepositional phrase + +###### Han characters and language-specific varieties + +- Han character +- Hanzi +- Kanji +- Hanja + +###### Other + +- Romanization +- Logogram +- Determinative + +###### Explicitly disallowed parts of speech + +You know, just in case somebody didn't follow the rules on Wiktionary. + +- Abbreviation +- Acronym +- Initialism +- Cardinal-number +- Ordinal-number +- Cardinal-numeral +- Ordinal-numeral +- Clitic +- Gerund +- Idiom + +###### Library additions + +- Adposition +- Affix +- Character \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..58eebc1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1488 @@ +{ + "name": "wiktionary-scraper", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wiktionary-scraper", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "cheerio": "^1.0.0-rc.12" + }, + "devDependencies": { + "@types/chai": "^4.3.5", + "@types/mocha": "^10.0.1", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "rome": "12.1.3", + "ts-node": "^10.9.1", + "typescript": "^5.1.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@rometools/cli-darwin-arm64": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/@rometools/cli-darwin-arm64/-/cli-darwin-arm64-12.1.3.tgz", + "integrity": "sha512-AmFTUDYjBuEGQp/Wwps+2cqUr+qhR7gyXAUnkL5psCuNCz3807TrUq/ecOoct5MIavGJTH6R4aaSL6+f+VlBEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rometools/cli-darwin-x64": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/@rometools/cli-darwin-x64/-/cli-darwin-x64-12.1.3.tgz", + "integrity": "sha512-k8MbWna8q4LRlb005N2X+JS1UQ+s3ZLBBvwk4fP8TBxlAJXUz17jLLu/Fi+7DTTEmMhM84TWj4FDKW+rNar28g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rometools/cli-linux-arm64": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/@rometools/cli-linux-arm64/-/cli-linux-arm64-12.1.3.tgz", + "integrity": "sha512-X/uLhJ2/FNA3nu5TiyeNPqiD3OZoFfNfRvw6a3ut0jEREPvEn72NI7WPijH/gxSz55znfQ7UQ6iM4DZumUknJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rometools/cli-linux-x64": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/@rometools/cli-linux-x64/-/cli-linux-x64-12.1.3.tgz", + "integrity": "sha512-csP17q1eWiUXx9z6Jr/JJPibkplyKIwiWPYNzvPCGE8pHlKhwZj3YHRuu7Dm/4EOqx0XFIuqqWZUYm9bkIC8xg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rometools/cli-win32-arm64": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/@rometools/cli-win32-arm64/-/cli-win32-arm64-12.1.3.tgz", + "integrity": "sha512-RymHWeod57EBOJY4P636CgUwYA6BQdkQjh56XKk4pLEHO6X1bFyMet2XL7KlHw5qOTalzuzf5jJqUs+vf3jdXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rometools/cli-win32-x64": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/@rometools/cli-win32-x64/-/cli-win32-x64-12.1.3.tgz", + "integrity": "sha512-yHSKYidqJMV9nADqg78GYA+cZ0hS1twANAjiFibQdXj9aGzD+s/IzIFEIi/U/OBLvWYg/SCw0QVozi2vTlKFDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", + "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.5.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.6.tgz", + "integrity": "sha512-Gi5wRGPbbyOTX+4Y2iULQ27oUPrefaB0PxGQJnfyWN3kvEDGM3mIB5M/gQLmitZf7A9FmLeaqxD3L1CXpm3VKQ==", + "dev": true, + "peer": true + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rome": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/rome/-/rome-12.1.3.tgz", + "integrity": "sha512-e+ff72hxDpe/t5/Us7YRBVw3PBET7SeczTQNn6tvrWdrCaAw3qOukQQ+tDCkyFtS4yGsnhjrJbm43ctNbz27Yg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "rome": "bin/rome" + }, + "engines": { + "node": ">=14.*" + }, + "optionalDependencies": { + "@rometools/cli-darwin-arm64": "12.1.3", + "@rometools/cli-darwin-x64": "12.1.3", + "@rometools/cli-linux-arm64": "12.1.3", + "@rometools/cli-linux-x64": "12.1.3", + "@rometools/cli-win32-arm64": "12.1.3", + "@rometools/cli-win32-x64": "12.1.3" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4242269 --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "wiktionary-scraper", + "description": "A lightweight scraper to fetch information about words in various languages from Wiktionary.", + "license": "MIT", + "version": "0.0.1", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", + "keywords": [ + "javascript", + "typescript", + "parser", + "scraper", + "wiktionary", + "definitions", + "words", + "language", + "dictionary", + "english", + "german", + "polish" + ], + "homepage": "https://github.com/vxern/wiktionary-scraper", + "bugs": { + "url": "https://github.com/vxern/wiktionary-scraper/issues", + "email": "contact@wordcollector.co.uk" + }, + "contributors": [ + { + "name": "Dorian \"vxern\" Oszczęda", + "email": "vxern@wordcollector.co.uk", + "url": "https://github.com/vxern" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/vxern/wiktionary-scraper" + }, + "scripts": { + "build": "tsc", + "test": "mocha --no-warnings", + "publish": "tsc && npm publish" + }, + "devDependencies": { + "@types/chai": "^4.3.5", + "@types/mocha": "^10.0.1", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "rome": "12.1.3", + "ts-node": "^10.9.1", + "typescript": "^5.1.3" + }, + "dependencies": { + "cheerio": "^1.0.0-rc.12" + } +} diff --git a/rome.json b/rome.json new file mode 100644 index 0000000..6672b67 --- /dev/null +++ b/rome.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://docs.rome.tools/schemas/12.1.3/schema.json", + "linter": { + "enabled": true, + "rules": { + "all": true, + "suspicious": { + "noEmptyInterface": "off" + }, + "nursery": { + "useCamelCase": "off" + } + } + }, + "formatter": { + "enabled": true, + "formatWithErrors": true, + "indentSize": 2, + "indentStyle": "tab", + "lineWidth": 120 + } +} diff --git a/src/constants/links.ts b/src/constants/links.ts new file mode 100644 index 0000000..6c91002 --- /dev/null +++ b/src/constants/links.ts @@ -0,0 +1,6 @@ +import { ScraperOptions } from "../options.js"; + +export default { + definition: (word: string, options: ScraperOptions): string => + `https://${options.siteLanguage}.wiktionary.org/wiki/${word}#${options.lemmaLanguage}`, +}; diff --git a/src/constants/parts-of-speech.ts b/src/constants/parts-of-speech.ts new file mode 100644 index 0000000..2c0ce44 --- /dev/null +++ b/src/constants/parts-of-speech.ts @@ -0,0 +1,6 @@ +import { SiteLanguage } from "../options.js"; +import english from "./parts-of-speech/english.js"; + +export default { + en: english, +} satisfies Record; diff --git a/src/constants/parts-of-speech/english.ts b/src/constants/parts-of-speech/english.ts new file mode 100644 index 0000000..cdaff57 --- /dev/null +++ b/src/constants/parts-of-speech/english.ts @@ -0,0 +1,65 @@ +import { PartOfSpeech } from "../../types.js"; +import { reverseObject } from "../../utils.js"; + +export default reverseObject({ + adposition: "Adposition", + affix: "Affix", + character: "Character", + // Standard parts of speech + number: "Number", + symbol: "Symbol", + adjective: "Adjective", + adverb: "Adverb", + ambiposition: "Ambiposition", + article: "Article", + circumposition: "Circumposition", + classifier: "Classifier", + conjunction: "Conjunction", + contraction: "Contraction", + counter: "Counter", + determiner: "Determiner", + ideophone: "Ideophone", + interjection: "Interjection", + noun: "Noun", + numeral: "Numeral", + participle: "Participle", + particle: "Particle", + postposition: "Postposition", + preposition: "Preposition", + pronoun: "Pronoun", + "proper-noun": "Proper noun", + verb: "Verb", + circumfix: "Circumfix", + "combining-form": "Combining form", + infix: "Infix", + interfix: "Interfix", + prefix: "Prefix", + root: "Root", + suffix: "Suffix", + "diacritical-mark": "Diacritical mark", + letter: "Letter", + ligature: "Ligature", + "punctuation-mark": "Punctuation mark", + syllable: "Syllable", + phrase: "Phrase", + proverb: "Proverb", + "prepositional-phrase": "Prepositional phrase", + "han-character": "Han character", + hanzi: "Hanzi", + kanji: "Kanji", + hanja: "Hanja", + romanization: "Romanization", + logogram: "Logogram", + determinative: "Determinative", + // Disallowed parts of speech + abbreviation: "Abbreviation", + acronym: "Acronym", + initialism: "Initialism", + "cardinal-number": "Cardinal number", + "ordinal-number": "Ordinal number", + "cardinal-numeral": "Cardinal numeral", + "ordinal-numeral": "Ordinal numeral", + clitic: "Clitic", + gerund: "Gerund", + idiom: "Idiom", +} satisfies Record as Record); diff --git a/src/constants/patterns.ts b/src/constants/patterns.ts new file mode 100644 index 0000000..7f5f203 --- /dev/null +++ b/src/constants/patterns.ts @@ -0,0 +1,18 @@ +export default { + multipleWhitespace: /\s{2,}/g, + // e.g. "Etymology" -> "Etymology", undefined + // e.g. "Etymology 1" -> "Etymology", "1" + sectionName: /^([a-zA-Z ]+)(?: ([0-9]{1,}))?$/, + // e.g. "Sample sentence." -> undefined, "Sample sentence." + // e.g. "(historical, uncommon, regional) Sample sentence." -> "historical, uncommon, regional", "Sample sentence." + withPrefixedLabels: /^(?:\(([^()]+)\) ?)? ?(.+)$/, + // e.g. "Sample sentence." -> "Sample sentence.", undefined + // e.g. "Sample sentence. (dated)" -> "Sample sentence.", "dated" + withSuffixedLabels: /^(.+?) ?(?:\(([^()]+)\) ?)?$/, + // e.g. "historical, uncommon, regional" + // e.g. "historical,uncommon,regional" + labelSeparator: /, ?/g, + // e.g. "(historical) Sample sentence; (uncommon) Different sample sentence." + // e.g. "(historical) Sample sentence;(uncommon) Different sample sentence." + fieldSeparator: /; ?/g, +}; diff --git a/src/constants/sections.ts b/src/constants/sections.ts new file mode 100644 index 0000000..97d14c2 --- /dev/null +++ b/src/constants/sections.ts @@ -0,0 +1,6 @@ +import { SiteLanguage } from "../options.js"; +import english from "./sections/english.js"; + +export default { + en: english, +} satisfies Record; diff --git a/src/constants/sections/english.ts b/src/constants/sections/english.ts new file mode 100644 index 0000000..53288b5 --- /dev/null +++ b/src/constants/sections/english.ts @@ -0,0 +1,41 @@ +import { SectionType } from "../../types.js"; +import { reverseObject } from "../../utils.js"; + +export default reverseObject({ + description: "Description", + glyphOrigin: "Glyph origin", + etymology: "Etymology", + pronunciation: "Pronunciation", + production: "Production", + usageNotes: "Usage notes", + reconstructionNotes: "Reconstruction notes", + inflection: "Inflection", + conjugation: "Conjugation", + declension: "Declension", + mutation: "Mutation", + quotations: "Quotations", + alternativeForms: "Alternative forms", + alternativeReconstructions: "Alternative reconstructions", + synonyms: "Synonyms", + antonyms: "Antonyms", + hypernyms: "Hypernyms", + hyponyms: "Hyponyms", + meronyms: "Meronyms", + holonyms: "Holonyms", + comeronyms: "Comeronyms", + troponyms: "Troponyms", + parasynonyms: "Parasynonyms", + coordinate: "Coordinate terms", + derived: "Derived terms", + related: "Related terms", + collocations: "Collocations", + descendants: "Descendants", + translations: "Translations", + trivia: "Trivia", + seeAlso: "See also", + references: "References", + furtherReading: "Further reading", + anagrams: "Anagrams", + definitions: "Definitions", + examples: "Examples", +} satisfies Record as Record); diff --git a/src/constants/selectors.ts b/src/constants/selectors.ts new file mode 100644 index 0000000..325a48f --- /dev/null +++ b/src/constants/selectors.ts @@ -0,0 +1,42 @@ +export default { + didYouMean: "#did-you-mean > a", + tableOfContents: { + tableOfContents: "div#toc", + entries: { + entries: (depth: number) => `li.toclevel-${depth + 1}`, + root: { + root: "a", + text: ".toctext", + }, + }, + }, + section: "h1, h2, h3, h4, h5, h6", + definitions: { + heading: { + heading: "p", + headword: "strong", + }, + definitions: { + examples: { + list: "dl", + examples: "dd", + }, + quotations: { + list: "ul", + quotations: "li", + }, + definitions: { + list: "ol", + definitions: "li", + }, + }, + }, + etymology: { + paragraph: "p", + lists: { + unordered: "ul", + ordered: "ol", + elements: "li", + }, + }, +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4ec2e42 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,51 @@ +import * as cheerio from "cheerio"; +import { ScraperOptions } from "./options.js"; +import links from "./constants/links.js"; +import selectors from "./constants/selectors.js"; +import { Entry } from "./types.js"; +import { parse } from "./parsers/parser.js"; + +const defaultScraperOptions: ScraperOptions = { + lemmaLanguage: "English", + siteLanguage: "en", + userAgent: "wiktionary-scraper (github.com/vxern/wiktionary-scraper)", + followRedirect: false, +} as const; + +export async function get( + lemma: string, + options: Partial = defaultScraperOptions, +): Promise { + const optionsFilled: ScraperOptions = { ...defaultScraperOptions, ...options }; + + let response; + try { + response = await fetch(links.definition(lemma, optionsFilled), { + headers: optionsFilled.userAgent !== undefined ? { "User-Agent": optionsFilled.userAgent } : {}, + }); + } catch { + return undefined; + } + + const body = await response.text(); + const $ = cheerio.load(body); + + if (!response.ok) { + if (response.status === 404 && options.followRedirect) { + const suggestedLemma = $(selectors.didYouMean).html() ?? undefined; + if (suggestedLemma === undefined) { + return undefined; + } + + return get(suggestedLemma, optionsFilled); + } else { + return undefined; + } + } + + return parse(optionsFilled, $, { value: lemma }); +} + +export * from "./parsers/parser.js"; +export * from "./options.js"; +export * from "./types.js"; diff --git a/src/options.ts b/src/options.ts new file mode 100644 index 0000000..7e2c315 --- /dev/null +++ b/src/options.ts @@ -0,0 +1,32 @@ +export type SiteLanguage = "en"; + +/** Defines the available options for getting a lemma from the dictionary. */ +export interface ScraperOptions { + /** + * Specifies the language of the lemma. + * + * @defaultValue `"English"` + */ + lemmaLanguage: string; + + /** + * Specifies the language of the website. + * + * @defaultValue `"en"` + */ + siteLanguage: SiteLanguage; + + /** + * Specifies the User-Agent header to use for self-identification. + * + * Set to {@link undefined} to omit the header. + * + * @defaultValue `"wiktionary-scraper (github.com/vxern/wiktionary-scraper)"` + */ + userAgent: string | undefined; + + /** + * Whether the scraper should follow redirects to similar terms if the term does not exist. + */ + followRedirect: boolean; +} diff --git a/src/parsers/parser.ts b/src/parsers/parser.ts new file mode 100644 index 0000000..cf6ed59 --- /dev/null +++ b/src/parsers/parser.ts @@ -0,0 +1,241 @@ +import * as cheerio from "cheerio"; +import { Entry, Lemma, PartOfSpeech, SectionType, Sections } from "../types.js"; +import selectors from "../constants/selectors.js"; +import { ScraperOptions } from "../options.js"; +import patterns from "../constants/patterns.js"; +import sections from "../constants/sections.js"; +import parsers from "./parsers.js"; +import partsOfSpeech from "../constants/parts-of-speech.js"; +import { addMissingProperties } from "../utils.js"; + +export type Parser, K extends keyof S> = ( + $: cheerio.CheerioAPI, + sectionSkeleton: EntrySectionSkeleton, +) => S[K]; + +export interface EntrySectionSkeleton { + id: string; + name: string; + sections: EntrySectionSkeleton[]; +} + +export function parse(options: ScraperOptions, $: cheerio.CheerioAPI, lemma: Lemma): Entry[] | undefined { + const $tableOfContents = $(selectors.tableOfContents.tableOfContents).first(); + const $entries = $tableOfContents.find(selectors.tableOfContents.entries.entries(0)); + + const skeletons: EntrySectionSkeleton[] = []; + for (const entryElement of $entries) { + const skeleton = parseSectionSkeleton($, entryElement, 1); + if (skeleton === undefined) { + continue; + } + + skeletons.push(skeleton); + } + + const skeletonForLanguage = skeletons.find((skeleton) => skeleton.name === options.lemmaLanguage); + if (skeletonForLanguage === undefined) { + return undefined; + } + + return parseEntrySkeleton(options, $, skeletonForLanguage, lemma); +} + +function parseSectionSkeleton( + $: cheerio.CheerioAPI, + section: cheerio.Element, + depth: number, +): EntrySectionSkeleton | undefined { + const $section = $(section); + const $root = $section.find(selectors.tableOfContents.entries.root.root).first(); + const id = $root.attr("href"); + if (id === undefined) { + return undefined; + } + + const $name = $root.find(selectors.tableOfContents.entries.root.text).first(); + const name = $name.html() ?? undefined; + if (name === undefined) { + return undefined; + } + + const $sections = $section.find(selectors.tableOfContents.entries.entries(depth)); + if ($sections === undefined) { + return { id, name, sections: [] }; + } + + const sections: EntrySectionSkeleton[] = []; + for (const sectionElement of $sections) { + const section = parseSectionSkeleton($, sectionElement, depth + 1); + if (section === undefined) { + continue; + } + + sections.push(section); + } + + return { id, name, sections }; +} + +function parseEntrySkeleton( + options: ScraperOptions, + $: cheerio.CheerioAPI, + skeleton: EntrySectionSkeleton, + lemma: Lemma, +): Entry[] | undefined { + const isMultipleEntries = skeleton.sections.some((skeleton) => { + const [_, sectionName, sectionIndex] = patterns.sectionName.exec(skeleton.name) ?? []; + if (sectionName === undefined) { + return false; + } + + const sectionType = sections[options.siteLanguage][sectionName]; + if (sectionType === undefined) { + return false; + } + + return sectionType === "etymology" && sectionIndex !== undefined; + }); + if (isMultipleEntries) { + return parseMultipleEntries(options, $, skeleton, lemma); + } + + const entry = parseSingleEntry(options, $, skeleton, lemma); + if (entry === undefined) { + return []; + } + + return [entry]; +} + +type SectionTypeTuple = [sectionType: SectionType, sectionIndex: number | undefined, skeleton: EntrySectionSkeleton]; +type SectionNameTuple = [sectionName: string, skeleton: EntrySectionSkeleton]; + +function parseMultipleEntries( + options: ScraperOptions, + $: cheerio.CheerioAPI, + skeleton: EntrySectionSkeleton, + lemma: Lemma, +): Entry[] | undefined { + const sectionsMapped = sections[options.siteLanguage]; + + const skeletonWithoutEtymologySections: EntrySectionSkeleton = { id: skeleton.id, name: skeleton.name, sections: [] }; + + const etymologySections: EntrySectionSkeleton[] = []; + for (const sectionSkeleton of skeleton.sections) { + const [_, sectionName] = patterns.sectionName.exec(sectionSkeleton.name) ?? []; + if (sectionName === undefined) { + continue; + } + + const sectionType = sectionsMapped[sectionName]; + if (sectionType === undefined) { + continue; + } + + if (sectionType === "etymology") { + etymologySections.push(sectionSkeleton); + } else { + skeletonWithoutEtymologySections.sections.push(sectionSkeleton); + } + } + + const topLevelEntry = parseSingleEntry(options, $, skeletonWithoutEtymologySections, lemma); + + const entries: Entry[] = []; + for (const etymologySection of etymologySections) { + const etymology = parsers.etymology($, etymologySection); + + const entry = parseSingleEntry(options, $, etymologySection, lemma); + if (entry === undefined) { + continue; + } + + if (etymology !== undefined) { + entry.etymology = etymology; + } + + entries.push(entry); + } + + if (topLevelEntry !== undefined) { + if (entries.length === 0) { + return [topLevelEntry]; + } + + for (const entry of entries) { + addMissingProperties(entry, topLevelEntry); + } + + return entries; + } else { + return entries; + } +} + +function parseSingleEntry( + options: ScraperOptions, + $: cheerio.CheerioAPI, + skeleton: EntrySectionSkeleton, + lemma: Lemma, +): Entry | undefined { + const sectionsMapped = sections[options.siteLanguage]; + const partsOfSpeechMapped = partsOfSpeech[options.siteLanguage]; + + const sectionTuples: SectionTypeTuple[] = []; + const sectionsUnrecognised: SectionNameTuple[] = []; + for (const sectionSkeleton of skeleton.sections) { + const [_, sectionName] = patterns.sectionName.exec(sectionSkeleton.name) ?? []; + if (sectionName === undefined) { + continue; + } + + const sectionType = sectionsMapped[sectionName]; + if (sectionType === undefined) { + sectionsUnrecognised.push([sectionName, sectionSkeleton]); + continue; + } + + sectionTuples.push([sectionType, undefined, sectionSkeleton]); + } + + const entrySections: Partial = {}; + for (const [sectionType, _, sectionSkeleton] of sectionTuples) { + if (sectionType in entrySections) { + continue; + } + + const parse = parsers[sectionType]; + const section = parse($, sectionSkeleton); + if (section === undefined) { + continue; + } + + // @ts-ignore: This is fine. + entrySections[sectionType] = section; + } + + let partOfSpeech: PartOfSpeech | undefined; + for (const [sectionName, skeleton] of sectionsUnrecognised) { + const partOfSpeechMatched = partsOfSpeechMapped[sectionName]; + if (partOfSpeechMatched === undefined) { + continue; + } + + const definitions = parsers.definitions($, skeleton); + if (definitions === undefined) { + continue; + } + + partOfSpeech = partOfSpeechMatched; + entrySections.definitions = definitions; + } + + const entry: Entry = { lemma, ...entrySections }; + + if (partOfSpeech !== undefined) { + entry.partOfSpeech = partOfSpeech; + } + + return entry; +} diff --git a/src/parsers/parsers.ts b/src/parsers/parsers.ts new file mode 100644 index 0000000..bda85ea --- /dev/null +++ b/src/parsers/parsers.ts @@ -0,0 +1,68 @@ +import { Sections } from "../types.js"; +import { Parser } from "./parser.js"; +import description from "./shared/description.js"; +import glyphOrigin from "./shared/glyph-origin.js"; +import etymology from "./shared/etymology.js"; +import pronunciation from "./shared/pronunciation.js"; +import production from "./shared/production.js"; +import usageNotes from "./shared/usage-notes.js"; +import reconstructionNotes from "./shared/reconstruction-notes.js"; +import inflection from "./shared/inflection.js"; +import conjugation from "./shared/conjugation.js"; +import declension from "./shared/declension.js"; +import mutation from "./shared/mutation.js"; +import quotations from "./shared/quotations.js"; +import alternativeForms from "./shared/alternative-forms.js"; +import alternativeReconstructions from "./shared/alternative-reconstructions.js"; +import relations from "./shared/relations.js"; +import translations from "./shared/translations.js"; +import trivia from "./shared/trivia.js"; +import seeAlso from "./shared/see-also.js"; +import references from "./shared/references.js"; +import furtherReading from "./shared/further-reading.js"; +import anagrams from "./shared/anagrams.js"; +import definitions from "./shared/definitions.js"; +import examples from "./shared/examples.js"; + +type Parsers = { + [K in keyof Sections]: Parser, K>; +}; + +export default { + description, + glyphOrigin, + etymology, + pronunciation, + production, + usageNotes, + reconstructionNotes, + inflection, + conjugation, + declension, + mutation, + quotations, + alternativeForms, + alternativeReconstructions, + synonyms: relations, + antonyms: relations, + hypernyms: relations, + hyponyms: relations, + meronyms: relations, + holonyms: relations, + comeronyms: relations, + troponyms: relations, + parasynonyms: relations, + coordinate: relations, + derived: relations, + related: relations, + collocations: relations, + descendants: relations, + translations, + trivia, + seeAlso, + references, + furtherReading, + anagrams, + definitions, + examples, +} satisfies Parsers as Parsers; diff --git a/src/parsers/shared/alternative-forms.ts b/src/parsers/shared/alternative-forms.ts new file mode 100644 index 0000000..2ca8e98 --- /dev/null +++ b/src/parsers/shared/alternative-forms.ts @@ -0,0 +1,8 @@ +import { AlternativeForm } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): AlternativeForm[] | undefined { + return undefined; +} diff --git a/src/parsers/shared/alternative-reconstructions.ts b/src/parsers/shared/alternative-reconstructions.ts new file mode 100644 index 0000000..3b64ab8 --- /dev/null +++ b/src/parsers/shared/alternative-reconstructions.ts @@ -0,0 +1,11 @@ +import { AlternativeReconstruction } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse( + _: cheerio.CheerioAPI, + __: EntrySectionSkeleton, +): AlternativeReconstruction[] | undefined { + return undefined; +} diff --git a/src/parsers/shared/anagrams.ts b/src/parsers/shared/anagrams.ts new file mode 100644 index 0000000..9a40f84 --- /dev/null +++ b/src/parsers/shared/anagrams.ts @@ -0,0 +1,8 @@ +import { Anagram } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Anagram[] | undefined { + return undefined; +} diff --git a/src/parsers/shared/conjugation.ts b/src/parsers/shared/conjugation.ts new file mode 100644 index 0000000..c0bcfe5 --- /dev/null +++ b/src/parsers/shared/conjugation.ts @@ -0,0 +1,8 @@ +import { Inflection } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Inflection | undefined { + return undefined; +} diff --git a/src/parsers/shared/declension.ts b/src/parsers/shared/declension.ts new file mode 100644 index 0000000..c0bcfe5 --- /dev/null +++ b/src/parsers/shared/declension.ts @@ -0,0 +1,8 @@ +import { Inflection } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Inflection | undefined { + return undefined; +} diff --git a/src/parsers/shared/definitions.ts b/src/parsers/shared/definitions.ts new file mode 100644 index 0000000..b9487d7 --- /dev/null +++ b/src/parsers/shared/definitions.ts @@ -0,0 +1,136 @@ +import { Definition, Example, LabelledTextField, Quotation } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; +import selectors from "../../constants/selectors.js"; +import { clean } from "../../utils.js"; +import patterns from "../../constants/patterns.js"; + +export default function parse($: cheerio.CheerioAPI, skeleton: EntrySectionSkeleton): Definition[] | undefined { + const $root = $(skeleton.id); + const $row = $root.parent(); + + const $heading = $row.next(selectors.definitions.heading.heading); + // const $headword = $heading.find(selectors.definitions.heading.headword); + // const lemma = clean($headword.text()); + + const $list = $heading.next(selectors.definitions.definitions.definitions.list); + const $definitions = $list.children(selectors.definitions.definitions.definitions.definitions); + const definitions: Definition[] = []; + for (const definitionElement of $definitions) { + const definition = parseDefinition($, definitionElement); + if (definition === undefined) { + continue; + } + + definitions.push(definition); + } + + return definitions; +} + +function parseDefinition($: cheerio.CheerioAPI, element: cheerio.Element): Definition | undefined { + const $root = $(element); + + let examples: Example[] | undefined; + let quotations: Quotation[] | undefined; + let definitions: Definition[] | undefined; + + const $exampleSection = $root.find(selectors.definitions.definitions.examples.list).first(); + if ($exampleSection.length !== 0) { + const examples_: Example[] = []; + + const $examples = $exampleSection.children(selectors.definitions.definitions.examples.examples); + $examples.remove(); + for (const _ of $examples) { + // TODO(vxern): Parse examples. + } + + if (examples_.length !== 0) { + examples = examples_; + } + } + + const $quotationSection = $root.find(selectors.definitions.definitions.quotations.list).first(); + if ($quotationSection.length !== 0) { + const quotations_: Quotation[] = []; + + const $quotations = $quotationSection.children(selectors.definitions.definitions.quotations.quotations); + $quotations.remove(); + for (const _ of $quotations) { + // TODO(vxern): Parse quotations. + } + + if (quotations_.length !== 0) { + quotations = quotations_; + } + } + + const $definitionSection = $root.find(selectors.definitions.definitions.definitions.list).first(); + if ($definitionSection.length !== 0) { + const definitions_: Definition[] = []; + + const $definitions = $definitionSection.children(selectors.definitions.definitions.definitions.definitions); + $definitions.remove(); + for (const definitionElement of $definitions) { + const definition = parseDefinition($, definitionElement); + if (definition === undefined) { + continue; + } + + definitions_.push(definition); + } + + if (definitions_.length !== 0) { + definitions = definitions_; + } + } + + const contentsRaw = clean($root.contents().text()); + const semicolonSeparated = contentsRaw.split(patterns.fieldSeparator); + + const fieldsRaw: [labels: string[] | undefined, contents: string][] = []; + for (const sentence of semicolonSeparated) { + const [_, labelsRaw, contents] = patterns.withPrefixedLabels.exec(sentence) ?? []; + if (contents === undefined) { + return undefined; + } + + if (labelsRaw === undefined) { + const previousField = fieldsRaw.at(-1); + if (previousField === undefined) { + fieldsRaw.push([undefined, contents]); + } else { + previousField[1] = `${previousField[1]}; ${contents}`; + } + continue; + } + + const labels = labelsRaw.split(patterns.labelSeparator); + fieldsRaw.push([labels, contents]); + } + + const fields: LabelledTextField[] = []; + for (const [labels, value] of fieldsRaw) { + if (labels !== undefined) { + fields.push({ labels, value }); + } else { + fields.push({ value }); + } + } + + const definition: Definition = { fields }; + + if (examples !== undefined) { + definition.examples = examples; + } + + if (quotations !== undefined) { + definition.quotations = quotations; + } + + if (definitions !== undefined) { + definition.definitions = definitions; + } + + return definition; +} diff --git a/src/parsers/shared/description.ts b/src/parsers/shared/description.ts new file mode 100644 index 0000000..6d52c54 --- /dev/null +++ b/src/parsers/shared/description.ts @@ -0,0 +1,8 @@ +import { Description } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Description | undefined { + return undefined; +} diff --git a/src/parsers/shared/etymology.ts b/src/parsers/shared/etymology.ts new file mode 100644 index 0000000..2d0d2ec --- /dev/null +++ b/src/parsers/shared/etymology.ts @@ -0,0 +1,30 @@ +import { Etymology } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; +import selectors from "../../constants/selectors.js"; +import { clean } from "../../utils.js"; + +export default function parse($: cheerio.CheerioAPI, skeleton: EntrySectionSkeleton): Etymology | undefined { + const $root = $(skeleton.id); + const $row = $root.parent(); + + const $elements = $row.nextUntil(selectors.section); + + const $paragraphs = $elements.filter((_, element) => element.name === selectors.etymology.paragraph); + const paragraphs = $paragraphs.toArray().map((paragraph) => clean($(paragraph).text())); + + const $lists = $elements.filter( + (_, element) => + element.name === selectors.etymology.lists.unordered || element.name === selectors.etymology.lists.ordered, + ); + const listEntries = $lists + .children(selectors.etymology.lists.elements) + .toArray() + .map((field) => clean($(field).text())); + + if (paragraphs.length === 0 && listEntries.length === 0) { + return undefined; + } + + return { paragraphs, listEntries }; +} diff --git a/src/parsers/shared/examples.ts b/src/parsers/shared/examples.ts new file mode 100644 index 0000000..b6db531 --- /dev/null +++ b/src/parsers/shared/examples.ts @@ -0,0 +1,8 @@ +import { Example } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Example[] | undefined { + return undefined; +} diff --git a/src/parsers/shared/further-reading.ts b/src/parsers/shared/further-reading.ts new file mode 100644 index 0000000..636e009 --- /dev/null +++ b/src/parsers/shared/further-reading.ts @@ -0,0 +1,8 @@ +import { FurtherReading } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): FurtherReading | undefined { + return undefined; +} diff --git a/src/parsers/shared/glyph-origin.ts b/src/parsers/shared/glyph-origin.ts new file mode 100644 index 0000000..e6c1d42 --- /dev/null +++ b/src/parsers/shared/glyph-origin.ts @@ -0,0 +1,8 @@ +import { GlyphOrigin } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): GlyphOrigin | undefined { + return undefined; +} diff --git a/src/parsers/shared/inflection.ts b/src/parsers/shared/inflection.ts new file mode 100644 index 0000000..c0bcfe5 --- /dev/null +++ b/src/parsers/shared/inflection.ts @@ -0,0 +1,8 @@ +import { Inflection } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Inflection | undefined { + return undefined; +} diff --git a/src/parsers/shared/mutation.ts b/src/parsers/shared/mutation.ts new file mode 100644 index 0000000..d6c7936 --- /dev/null +++ b/src/parsers/shared/mutation.ts @@ -0,0 +1,8 @@ +import { Mutation } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Mutation | undefined { + return undefined; +} diff --git a/src/parsers/shared/production.ts b/src/parsers/shared/production.ts new file mode 100644 index 0000000..c6ccac0 --- /dev/null +++ b/src/parsers/shared/production.ts @@ -0,0 +1,8 @@ +import { Production } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Production | undefined { + return undefined; +} diff --git a/src/parsers/shared/pronunciation.ts b/src/parsers/shared/pronunciation.ts new file mode 100644 index 0000000..ddbafc6 --- /dev/null +++ b/src/parsers/shared/pronunciation.ts @@ -0,0 +1,8 @@ +import { Pronunciation } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Pronunciation | undefined { + return undefined; +} diff --git a/src/parsers/shared/quotations.ts b/src/parsers/shared/quotations.ts new file mode 100644 index 0000000..64bfc2a --- /dev/null +++ b/src/parsers/shared/quotations.ts @@ -0,0 +1,8 @@ +import { Quotation } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Quotation[] | undefined { + return undefined; +} diff --git a/src/parsers/shared/reconstruction-notes.ts b/src/parsers/shared/reconstruction-notes.ts new file mode 100644 index 0000000..48b109c --- /dev/null +++ b/src/parsers/shared/reconstruction-notes.ts @@ -0,0 +1,8 @@ +import { ReconstructionNotes } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): ReconstructionNotes | undefined { + return undefined; +} diff --git a/src/parsers/shared/references.ts b/src/parsers/shared/references.ts new file mode 100644 index 0000000..473b285 --- /dev/null +++ b/src/parsers/shared/references.ts @@ -0,0 +1,8 @@ +import { Reference } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Reference[] | undefined { + return undefined; +} diff --git a/src/parsers/shared/relations.ts b/src/parsers/shared/relations.ts new file mode 100644 index 0000000..faf3d3a --- /dev/null +++ b/src/parsers/shared/relations.ts @@ -0,0 +1,8 @@ +import { Relation } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Relation[] | undefined { + return undefined; +} diff --git a/src/parsers/shared/see-also.ts b/src/parsers/shared/see-also.ts new file mode 100644 index 0000000..0841c0c --- /dev/null +++ b/src/parsers/shared/see-also.ts @@ -0,0 +1,8 @@ +import { SeeAlso } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): SeeAlso | undefined { + return undefined; +} diff --git a/src/parsers/shared/translations.ts b/src/parsers/shared/translations.ts new file mode 100644 index 0000000..d4c243b --- /dev/null +++ b/src/parsers/shared/translations.ts @@ -0,0 +1,8 @@ +import { Translation } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Translation[] | undefined { + return undefined; +} diff --git a/src/parsers/shared/trivia.ts b/src/parsers/shared/trivia.ts new file mode 100644 index 0000000..0cfa005 --- /dev/null +++ b/src/parsers/shared/trivia.ts @@ -0,0 +1,8 @@ +import { Trivia } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): Trivia | undefined { + return undefined; +} diff --git a/src/parsers/shared/usage-notes.ts b/src/parsers/shared/usage-notes.ts new file mode 100644 index 0000000..8400954 --- /dev/null +++ b/src/parsers/shared/usage-notes.ts @@ -0,0 +1,8 @@ +import { UsageNotes } from "../../types.js"; +import * as cheerio from "cheerio"; +import { EntrySectionSkeleton } from "../parser.js"; + +// TODO(vxern): Implement. +export default function parse(_: cheerio.CheerioAPI, __: EntrySectionSkeleton): UsageNotes | undefined { + return undefined; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..6d1f009 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,195 @@ +export interface LabelledTextField { + labels?: string[]; + value: string; +} + +export type UsageNotes = string; +export type ReconstructionNotes = string; +export type Inflection = string[][]; +export type Mutation = string; +export type Quotation = string; +export type AlternativeForm = LabelledTextField; +export type AlternativeReconstruction = string; +export type Relation = LabelledTextField; +export type Translation = { + language: string; + words: string[]; +}; +export type Trivia = string; +export type SeeAlso = string; +export type Reference = string; +export type FurtherReading = string; +export type Anagram = string; +export type Lemma = LabelledTextField; +export interface Definition extends Partial { + fields: LabelledTextField[]; + definitions?: Definition[]; +} +export type Example = string; + +export interface Inflections { + inflection: Inflection; + conjugation: Inflection; + declension: Inflection; +} + +export interface Relations { + synonyms: Relation[]; + antonyms: Relation[]; + hypernyms: Relation[]; + hyponyms: Relation[]; + meronyms: Relation[]; + holonyms: Relation[]; + /** + * @remarks + * Not present in headings after definitions, but present in relations. + */ + comeronyms: Relation[]; + troponyms: Relation[]; + /** + * @remarks + * Not present in headings after definitions, but present in relations. + */ + parasynonyms: Relation[]; + coordinate: Relation[]; + derived: Relation[]; + related: Relation[]; + collocations: Relation[]; + descendants: Relation[]; +} + +/** + * @see https://en.wiktionary.org/wiki/Wiktionary:Entry_layout#List_of_headings + */ +type SharedSections = { + usageNotes: UsageNotes; + reconstructionNotes: ReconstructionNotes; +} & Inflections & { + mutation: Mutation; + quotations: Quotation[]; + alternativeForms: AlternativeForm[]; + alternativeReconstructions: AlternativeReconstruction[]; + } & Relations & { + translations: Translation[]; + trivia: Trivia; + seeAlso: SeeAlso; + references: Reference[]; + furtherReading: FurtherReading; + anagrams: Anagram[]; + /** + * @remarks + * Not present in headings after definitions. + */ + examples: Example[]; + }; + +export type Description = string; +export type GlyphOrigin = string; +export type Etymology = { + paragraphs: string[]; + listEntries: string[]; +}; +export interface Transcription extends LabelledTextField { + system: string; + key: string; +} +export type AudioFile = LabelledTextField; +export type Pronunciation = { + transcriptions: Transcription[]; + audioFiles: AudioFile[]; + rhymes: string[]; + homophones: string[]; + hyphenation: string[]; +}; +export type Production = string; + +export type EntrySections = { + // "Headings before the definitions" + description: Description; + glyphOrigin: GlyphOrigin; + etymology: Etymology; + pronunciation: Partial; + production: Production; + /** + * @remarks + * Not present in headings after definitions. + */ + definitions: Definition[]; +}; + +export type Sections = SharedSections & EntrySections; +export type SectionType = keyof Sections; + +/** + * @see https://en.wiktionary.org/wiki/Wiktionary:Entry_layout#Part_of_speech + */ +export type StandardPartsOfSpeech = + // Parts of speech + | "adjective" + | "adverb" + | "ambiposition" + | "article" + | "circumposition" + | "classifier" + | "conjunction" + | "contraction" + | "counter" + | "determiner" + | "ideophone" + | "interjection" + | "noun" + | "numeral" + | "participle" + | "particle" + | "postposition" + | "preposition" + | "pronoun" + | "proper-noun" + | "verb" + // Morphemes + | "circumfix" + | "combining-form" + | "infix" + | "interfix" + | "prefix" + | "root" + | "suffix" + // Symbols and characters + | "diacritical-mark" + | "letter" + | "ligature" + | "number" + | "punctuation-mark" + | "syllable" + | "symbol" + // Phrases + | "phrase" + | "proverb" + | "prepositional-phrase" + // Han characters and language-specific varieties + | "han-character" + | "hanzi" + | "kanji" + | "hanja" + // Other + | "romanization" + | "logogram" + | "determinative"; +export type DisallowedPartOfSpeech = + | "abbreviation" + | "acronym" + | "initialism" + | "cardinal-number" + | "ordinal-number" + | "cardinal-numeral" + | "ordinal-numeral" + | "clitic" + | "gerund" + | "idiom"; + +export type PartOfSpeech = "adposition" | "affix" | "character" | StandardPartsOfSpeech | DisallowedPartOfSpeech; + +export type Entry = { + lemma: Lemma; + partOfSpeech?: PartOfSpeech; +} & Partial; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..f8ed806 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,33 @@ +import patterns from "./constants/patterns.js"; + +type Reverse> = { + [K in keyof O as O[K]]: K; +}; +export function reverseObject>(object: O): Reverse { + const reversed: Partial> = {}; + for (const key of Object.keys(object) as (keyof O)[]) { + // @ts-ignore: This is okay. + reversed[object[key]] = key; + } + return reversed as unknown as Reverse; +} + +export function addMissingProperties>(object: O, from: O): O { + for (const key of Object.keys(from) as (keyof O)[]) { + if (!(key in object)) { + object[key] = from[key]; + } else if (Array.isArray(object[key])) { + for (const element of from[key] as []) { + (object[key] as []).push(element); + } + } else if (typeof object[key] === "object") { + addMissingProperties(object[key] as Record, from[key] as Record); + } + } + + return object; +} + +export function clean(string: string): string { + return string.trim().replaceAll(patterns.multipleWhitespace, " "); +} diff --git a/test/index.spec.ts b/test/index.spec.ts new file mode 100644 index 0000000..006d925 --- /dev/null +++ b/test/index.spec.ts @@ -0,0 +1,35 @@ +import { describe, it } from "mocha"; +import * as Wiktionary from "../src/index.js"; +import { expect } from "chai"; + +describe("The parser", () => { + it("returns `undefined` for terms that do not exist.", async () => { + const results = await Wiktionary.get("this.is.a.string.that.does.not.exist"); + + expect(results).to.be.undefined; + }); + + it("returns English results for terms that do exist.", async () => { + const results = await Wiktionary.get("o"); + + expect(results).to.not.be.undefined; + }); + + it("returns results for terms in a language different to English.", async () => { + const results = await Wiktionary.get("o", { lemmaLanguage: "Romanian" }); + + expect(results).to.not.be.undefined; + }); + + it("returns `undefined` for redirects when following redirects is disabled.", async () => { + const results = await Wiktionary.get("germany"); + + expect(results).to.be.undefined; + }); + + it("returns results for redirects when following redirects is enabled.", async () => { + const results = await Wiktionary.get("a", { followRedirect: true }); + + expect(results).to.not.be.undefined; + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..92b3b3f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Node", + "target": "ESNext", + "outDir": "dist/", + "declaration": true, + "declarationDir": "dist/", + "allowJs": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "checkJs": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noUncheckedIndexedAccess": true, + "strict": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "strictNullChecks": true, + "suppressExcessPropertyErrors": false, + "suppressImplicitAnyIndexErrors": false + }, + "compileOnSave": true, + "exclude": ["dist/", "node_modules/", "test/"], + "include": ["src/"] +} \ No newline at end of file