podns 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +13 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/parser.d.ts +9 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +117 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/package.json +21 -0
- package/src/http.test.ts +9 -0
- package/src/http.ts +10 -0
- package/src/index.ts +23 -0
- package/src/parser.test.ts +137 -0
- package/src/parser.ts +119 -0
- package/src/types.ts +36 -0
- package/tsconfig.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# PoDNS
|
|
2
|
+
This is a Node.JS implementation of [Pronouns over DNS](https://github.com/CutieZone/pronouns-over-dns), a way to define pronouns using DNS TXT records.
|
|
3
|
+
|
|
4
|
+
## Usage
|
|
5
|
+
```js
|
|
6
|
+
const getPronouns = require("podns");
|
|
7
|
+
await getPronouns("mauve.beer");
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Return values
|
|
11
|
+
```js
|
|
12
|
+
{
|
|
13
|
+
type: "pronouns"|"wildcard"|"none"|"comment-only"
|
|
14
|
+
raw // raw data fetched
|
|
15
|
+
cleanedRaw // cleaned up version of the fetch data (e.g. "SHE/ Her #hello!" -> "she/her")
|
|
16
|
+
comment?
|
|
17
|
+
|
|
18
|
+
// everything below this requires type to be "pronouns" in order to exist
|
|
19
|
+
tags // array that can only contain "preferred" and/or "plural"
|
|
20
|
+
pronouns: {
|
|
21
|
+
subject //first part of the pronouns
|
|
22
|
+
object // second
|
|
23
|
+
possessiveDeterminer // third
|
|
24
|
+
possessivePronoun // fourth
|
|
25
|
+
reflexive // fifth
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAEA,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAO1E"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPronounsRecords = getPronounsRecords;
|
|
4
|
+
const node_dns_1 = require("node:dns");
|
|
5
|
+
async function getPronounsRecords(domain) {
|
|
6
|
+
try {
|
|
7
|
+
const addresses = await node_dns_1.promises.resolveTxt("pronouns." + domain);
|
|
8
|
+
return addresses.flat();
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PronounRecord } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Get pronouns from a domain name and parse their values
|
|
4
|
+
* @param domain The domain to check. Must follow the format of `domain-name.tld`.
|
|
5
|
+
* @param silenceParseErrors Whether to silence errors and ignore misformed pronouns or not. Defaults to `true`.
|
|
6
|
+
* @returns The parsed pronouns
|
|
7
|
+
*/
|
|
8
|
+
export default function getPronouns(domain: string, silenceParseErrors?: boolean): Promise<PronounRecord[]>;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;GAKG;AACH,wBAA8B,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,kBAAkB,UAAO,4BAYlF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = getPronouns;
|
|
4
|
+
const http_js_1 = require("./http.js");
|
|
5
|
+
const parser_js_1 = require("./parser.js");
|
|
6
|
+
/**
|
|
7
|
+
* Get pronouns from a domain name and parse their values
|
|
8
|
+
* @param domain The domain to check. Must follow the format of `domain-name.tld`.
|
|
9
|
+
* @param silenceParseErrors Whether to silence errors and ignore misformed pronouns or not. Defaults to `true`.
|
|
10
|
+
* @returns The parsed pronouns
|
|
11
|
+
*/
|
|
12
|
+
async function getPronouns(domain, silenceParseErrors = true) {
|
|
13
|
+
const pronounsRecords = await (0, http_js_1.getPronounsRecords)(domain);
|
|
14
|
+
let processed = [];
|
|
15
|
+
for (let r in pronounsRecords) {
|
|
16
|
+
const parsed = (0, parser_js_1.parseRecord)(r, silenceParseErrors);
|
|
17
|
+
if (parsed != null) {
|
|
18
|
+
processed.push(parsed);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return processed;
|
|
22
|
+
}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PronounRecord } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parses a record of pronouns
|
|
4
|
+
* @param record The record to parse
|
|
5
|
+
* @param silenceErrors Whether to silently error and return null or to `throw` errors
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseRecord(record: string, silenceErrors?: boolean): PronounRecord | null;
|
|
9
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,aAAa,EAA8C,MAAM,YAAY,CAAC;AAEhI;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,aAAa,GAAG,IAAI,CA8GvF"}
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseRecord = parseRecord;
|
|
4
|
+
const types_js_1 = require("./types.js");
|
|
5
|
+
/**
|
|
6
|
+
* Parses a record of pronouns
|
|
7
|
+
* @param record The record to parse
|
|
8
|
+
* @param silenceErrors Whether to silently error and return null or to `throw` errors
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
function parseRecord(record, silenceErrors = false) {
|
|
12
|
+
let parsed = record;
|
|
13
|
+
// separating the comment from the rest
|
|
14
|
+
let comment;
|
|
15
|
+
if (parsed.includes("#")) {
|
|
16
|
+
comment = record.split("#")[1];
|
|
17
|
+
parsed = record.split("#")[0];
|
|
18
|
+
}
|
|
19
|
+
// remove all whitespaces, make everything lowercase
|
|
20
|
+
parsed = parsed.trim().toLocaleLowerCase().replace(/\s/g, "");
|
|
21
|
+
// check special cases (wildcard, none, nothing other than a comment)
|
|
22
|
+
switch (parsed) {
|
|
23
|
+
case "*": {
|
|
24
|
+
return {
|
|
25
|
+
type: "wildcard",
|
|
26
|
+
comment: comment,
|
|
27
|
+
raw: record,
|
|
28
|
+
cleanedRaw: parsed
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
case "!": {
|
|
32
|
+
return {
|
|
33
|
+
type: "none",
|
|
34
|
+
comment: comment,
|
|
35
|
+
raw: record,
|
|
36
|
+
cleanedRaw: parsed
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
case "": {
|
|
40
|
+
if (comment) {
|
|
41
|
+
return {
|
|
42
|
+
type: "comment-only",
|
|
43
|
+
comment: comment,
|
|
44
|
+
raw: record,
|
|
45
|
+
cleanedRaw: parsed
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// check for tags
|
|
54
|
+
let tags = [];
|
|
55
|
+
if (parsed.includes(";")) {
|
|
56
|
+
const parsedTags = parsed.split(";").slice(1);
|
|
57
|
+
for (const i in parsedTags) {
|
|
58
|
+
let t = parsedTags[i];
|
|
59
|
+
if (t == "") {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!types_js_1.PRONOUN_TAGS.includes(t)) {
|
|
63
|
+
if (!silenceErrors) {
|
|
64
|
+
throw Error("fetched pronoun \"" + record + "\" contains invalid tag(s)");
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!tags.includes(t)) {
|
|
71
|
+
tags.push(t);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
parsed = parsed.split(";")[0];
|
|
75
|
+
}
|
|
76
|
+
// check for pronoun parts
|
|
77
|
+
let parts = parsed.split("/");
|
|
78
|
+
if (parts.length < 2 || parts.length > 5) {
|
|
79
|
+
if (!silenceErrors) {
|
|
80
|
+
throw Error("fetched pronoun \"" + record + "\" is not formatted correctly");
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const i in parts) {
|
|
87
|
+
if (!/^[a-z]+$/.test(parts[i])) {
|
|
88
|
+
if (!silenceErrors) {
|
|
89
|
+
throw Error("fetched pronoun \"" + record + "\" contains invalid characters in its pronoun set");
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// special cases
|
|
97
|
+
if (parts[0] == "it" && parts[1] == "its") {
|
|
98
|
+
parts = ["it", "it", "its", "its", "itself"];
|
|
99
|
+
}
|
|
100
|
+
if (parts[0] == "they" && parts[1] == "them" && !tags.includes("plural")) {
|
|
101
|
+
tags.push("plural");
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
pronouns: {
|
|
105
|
+
subject: parts[0],
|
|
106
|
+
object: parts[1],
|
|
107
|
+
possessiveDeterminer: parts[2],
|
|
108
|
+
possessivePronoun: parts[3],
|
|
109
|
+
reflexive: parts[4]
|
|
110
|
+
},
|
|
111
|
+
tags: tags,
|
|
112
|
+
type: "pronouns",
|
|
113
|
+
raw: record,
|
|
114
|
+
comment: comment,
|
|
115
|
+
cleanedRaw: parsed
|
|
116
|
+
};
|
|
117
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
interface PronounSet {
|
|
2
|
+
subject: string;
|
|
3
|
+
object: string;
|
|
4
|
+
possessiveDeterminer?: string;
|
|
5
|
+
possessivePronoun?: string;
|
|
6
|
+
reflexive?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const PRONOUN_TAGS: readonly ["preferred", "plural"];
|
|
9
|
+
export type PronounTag = typeof PRONOUN_TAGS[number];
|
|
10
|
+
interface Record {
|
|
11
|
+
raw: string;
|
|
12
|
+
cleanedRaw: string;
|
|
13
|
+
comment?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface PronounsRecord extends Record {
|
|
16
|
+
type: 'pronouns';
|
|
17
|
+
pronouns: PronounSet;
|
|
18
|
+
tags: PronounTag[];
|
|
19
|
+
}
|
|
20
|
+
export interface WildcardRecord extends Record {
|
|
21
|
+
type: 'wildcard';
|
|
22
|
+
}
|
|
23
|
+
export interface NoneRecord extends Record {
|
|
24
|
+
type: 'none';
|
|
25
|
+
}
|
|
26
|
+
export interface CommentRecord extends Record {
|
|
27
|
+
type: 'comment-only';
|
|
28
|
+
}
|
|
29
|
+
export type PronounRecord = PronounsRecord | WildcardRecord | NoneRecord | CommentRecord;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,UAAU,UAAU;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,YAAY,kCAAmC,CAAC;AAC7D,MAAM,MAAM,UAAU,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AAErD,UAAU,MAAM;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAe,SAAQ,MAAM;IAC1C,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,UAAU,CAAC;IACrB,IAAI,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,cAAe,SAAQ,MAAM;IAC1C,IAAI,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,UAAW,SAAQ,MAAM;IACtC,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAc,SAAQ,MAAM;IACzC,IAAI,EAAE,cAAc,CAAA;CACvB;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,cAAc,GAAG,UAAU,GAAG,aAAa,CAAC"}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "podns",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "lumap <lumap@duck.com>",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://codeberg.org/lumap/podns-js"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/node": "^25.0.9",
|
|
12
|
+
"typescript": "^5.9.3",
|
|
13
|
+
"vitest": "^4.0.17"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"test": "vitest run ./src/"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT"
|
|
21
|
+
}
|
package/src/http.test.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { test, assert } from "vitest";
|
|
2
|
+
import { getPronounsRecords } from "./http.js"
|
|
3
|
+
|
|
4
|
+
test("fetch pronouns from domain name & tld", async (t) => {
|
|
5
|
+
const pronouns = await getPronounsRecords("mauve.beer")
|
|
6
|
+
assert.equal(pronouns.includes("she/her;preferred"), true)
|
|
7
|
+
assert.equal(pronouns.includes("they/them"), true)
|
|
8
|
+
assert.equal(pronouns.length, 2)
|
|
9
|
+
})
|
package/src/http.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { promises as dnsPromises } from "node:dns";
|
|
2
|
+
|
|
3
|
+
export async function getPronounsRecords(domain: string): Promise<string[]> {
|
|
4
|
+
try {
|
|
5
|
+
const addresses = await dnsPromises.resolveTxt("pronouns." + domain);
|
|
6
|
+
return addresses.flat();
|
|
7
|
+
} catch (err) {
|
|
8
|
+
throw err;
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getPronounsRecords} from "./http.js";
|
|
2
|
+
import { parseRecord } from "./parser.js";
|
|
3
|
+
import { PronounRecord } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get pronouns from a domain name and parse their values
|
|
7
|
+
* @param domain The domain to check. Must follow the format of `domain-name.tld`.
|
|
8
|
+
* @param silenceParseErrors Whether to silence errors and ignore misformed pronouns or not. Defaults to `true`.
|
|
9
|
+
* @returns The parsed pronouns
|
|
10
|
+
*/
|
|
11
|
+
export default async function getPronouns(domain: string, silenceParseErrors = true) {
|
|
12
|
+
const pronounsRecords = await getPronounsRecords(domain);
|
|
13
|
+
|
|
14
|
+
let processed: PronounRecord[] = []
|
|
15
|
+
for (let r in pronounsRecords) {
|
|
16
|
+
const parsed = parseRecord(r, silenceParseErrors)
|
|
17
|
+
if (parsed != null) {
|
|
18
|
+
processed.push(parsed)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return processed
|
|
23
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { parseRecord } from "./parser.js";
|
|
2
|
+
import {test, assert, expect} from "vitest";
|
|
3
|
+
import { CommentRecord, NoneRecord, PronounsRecord, WildcardRecord } from "./types.js";
|
|
4
|
+
|
|
5
|
+
// special cases
|
|
6
|
+
test("parse nothing", () => {
|
|
7
|
+
assert.equal(parseRecord(""), null)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test("parse comment-only", () => {
|
|
11
|
+
const parsed = parseRecord("#meow") as CommentRecord;
|
|
12
|
+
assert.notEqual(parsed, null)
|
|
13
|
+
assert.equal(parsed.type, "comment-only")
|
|
14
|
+
assert.equal(parsed.comment, "meow")
|
|
15
|
+
assert.equal(parsed.raw, "#meow")
|
|
16
|
+
assert.equal(parsed.cleanedRaw, "")
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("parse none", () => {
|
|
20
|
+
const parsed = parseRecord("! ") as NoneRecord;
|
|
21
|
+
assert.notEqual(parsed, null)
|
|
22
|
+
assert.equal(parsed.type, "none")
|
|
23
|
+
assert.equal(parsed.comment, undefined)
|
|
24
|
+
assert.equal(parsed.raw, "! ")
|
|
25
|
+
assert.equal(parsed.cleanedRaw, "!")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("parse wildcard", () => {
|
|
29
|
+
const parsed = parseRecord(" *") as WildcardRecord;
|
|
30
|
+
assert.notEqual(parsed, null)
|
|
31
|
+
assert.equal(parsed.type, "wildcard")
|
|
32
|
+
assert.equal(parsed.comment, undefined)
|
|
33
|
+
assert.equal(parsed.raw, " *")
|
|
34
|
+
assert.equal(parsed.cleanedRaw, "*")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// invalid cases
|
|
38
|
+
test("parse wildcard and none", () => {
|
|
39
|
+
expect(()=>parseRecord("*!")).toThrowError("fetched pronoun \"*!\" is not formatted correctly")
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("parse invalid tag", () => {
|
|
43
|
+
expect(() => parseRecord("she;meow")).toThrowError("fetched pronoun \"she;meow\" contains invalid tag(s)")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("parse invalid pronoun set", () => {
|
|
47
|
+
expect(() => parseRecord("she/Ìê®;")).toThrowError("fetched pronoun \"she/Ìê®;\" contains invalid characters in its pronoun set")
|
|
48
|
+
})
|
|
49
|
+
test("parse invalid pronoun set 2", () => {
|
|
50
|
+
expect(() => parseRecord("she/")).toThrowError("fetched pronoun \"she/\" contains invalid characters in its pronoun set")
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("parse return null if errors are silenced", () => {
|
|
54
|
+
assert.equal(parseRecord("meow3", true), null)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// valid cases
|
|
58
|
+
test("parse pronouns without tags or comments",() => {
|
|
59
|
+
const parsed = parseRecord("she/her") as PronounsRecord;
|
|
60
|
+
assert.notEqual(parsed, null)
|
|
61
|
+
assert.equal(parsed.type, "pronouns")
|
|
62
|
+
assert.equal(parsed.comment, undefined)
|
|
63
|
+
assert.equal(Array.isArray(parsed.tags), true)
|
|
64
|
+
assert.equal(parsed.tags[0], undefined)
|
|
65
|
+
assert.equal(parsed.raw, "she/her")
|
|
66
|
+
assert.equal(parsed.pronouns.subject, "she")
|
|
67
|
+
assert.equal(parsed.pronouns.object, "her")
|
|
68
|
+
assert.equal(parsed.cleanedRaw, "she/her")
|
|
69
|
+
assert.equal(parsed.pronouns.possessiveDeterminer, undefined)
|
|
70
|
+
assert.equal(parsed.pronouns.possessivePronoun, undefined)
|
|
71
|
+
assert.equal(parsed.pronouns.reflexive, undefined)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("parse pronouns with a tag and a comment", () => {
|
|
75
|
+
const parsed = parseRecord("she/HUr/hur/HUR/hur; preferred #O Cholera Czy To Freddy Fazbear") as PronounsRecord;
|
|
76
|
+
assert.notEqual(parsed, null)
|
|
77
|
+
assert.equal(parsed.type, "pronouns")
|
|
78
|
+
assert.equal(parsed.comment, "O Cholera Czy To Freddy Fazbear")
|
|
79
|
+
assert.equal(Array.isArray(parsed.tags), true)
|
|
80
|
+
assert.equal(parsed.tags[0], "preferred")
|
|
81
|
+
assert.equal(parsed.raw, "she/HUr/hur/HUR/hur; preferred #O Cholera Czy To Freddy Fazbear")
|
|
82
|
+
assert.equal(parsed.cleanedRaw, "she/hur/hur/hur/hur")
|
|
83
|
+
assert.equal(parsed.pronouns.subject, "she")
|
|
84
|
+
assert.equal(parsed.pronouns.object, "hur")
|
|
85
|
+
assert.equal(parsed.pronouns.possessiveDeterminer, "hur")
|
|
86
|
+
assert.equal(parsed.pronouns.possessivePronoun, "hur")
|
|
87
|
+
assert.equal(parsed.pronouns.reflexive, "hur")
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("parse pronouns with multiple tags and a comment", () => {
|
|
91
|
+
const parsed = parseRecord("she/ HUr/hur/HUR/hur; preferred;;;plural #O Cholera Czy To Freddy Fazbear") as PronounsRecord;
|
|
92
|
+
assert.notEqual(parsed, null)
|
|
93
|
+
assert.equal(parsed.type, "pronouns")
|
|
94
|
+
assert.equal(parsed.comment, "O Cholera Czy To Freddy Fazbear")
|
|
95
|
+
assert.equal(Array.isArray(parsed.tags), true)
|
|
96
|
+
assert.equal(parsed.tags[0], "preferred")
|
|
97
|
+
assert.equal(parsed.tags[1], "plural")
|
|
98
|
+
assert.equal(parsed.raw, "she/ HUr/hur/HUR/hur; preferred;;;plural #O Cholera Czy To Freddy Fazbear")
|
|
99
|
+
assert.equal(parsed.cleanedRaw, "she/hur/hur/hur/hur")
|
|
100
|
+
assert.equal(parsed.pronouns.subject, "she")
|
|
101
|
+
assert.equal(parsed.pronouns.object, "hur")
|
|
102
|
+
assert.equal(parsed.pronouns.possessiveDeterminer, "hur")
|
|
103
|
+
assert.equal(parsed.pronouns.possessivePronoun, "hur")
|
|
104
|
+
assert.equal(parsed.pronouns.reflexive, "hur")
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test("parse it/its", () => {
|
|
108
|
+
const parsed = parseRecord("it/its # meow!") as PronounsRecord;
|
|
109
|
+
assert.notEqual(parsed, null)
|
|
110
|
+
assert.equal(parsed.type, "pronouns")
|
|
111
|
+
assert.equal(parsed.comment, " meow!")
|
|
112
|
+
assert.equal(Array.isArray(parsed.tags), true)
|
|
113
|
+
assert.equal(parsed.tags[0], undefined)
|
|
114
|
+
assert.equal(parsed.raw, "it/its # meow!")
|
|
115
|
+
assert.equal(parsed.cleanedRaw, "it/its")
|
|
116
|
+
assert.equal(parsed.pronouns.subject, "it")
|
|
117
|
+
assert.equal(parsed.pronouns.object, "it")
|
|
118
|
+
assert.equal(parsed.pronouns.possessiveDeterminer, "its")
|
|
119
|
+
assert.equal(parsed.pronouns.possessivePronoun, "its")
|
|
120
|
+
assert.equal(parsed.pronouns.reflexive, "itself")
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test("parse they/them is automatically plural", () => {
|
|
124
|
+
const parsed = parseRecord("they/them") as PronounsRecord;
|
|
125
|
+
assert.notEqual(parsed, null)
|
|
126
|
+
assert.equal(parsed.type, "pronouns")
|
|
127
|
+
assert.equal(parsed.comment, undefined)
|
|
128
|
+
assert.equal(Array.isArray(parsed.tags), true)
|
|
129
|
+
assert.equal(parsed.tags[0], "plural")
|
|
130
|
+
assert.equal(parsed.raw, "they/them")
|
|
131
|
+
assert.equal(parsed.cleanedRaw, "they/them")
|
|
132
|
+
assert.equal(parsed.pronouns.subject, "they")
|
|
133
|
+
assert.equal(parsed.pronouns.object, "them")
|
|
134
|
+
assert.equal(parsed.pronouns.possessiveDeterminer, undefined)
|
|
135
|
+
assert.equal(parsed.pronouns.possessivePronoun, undefined)
|
|
136
|
+
assert.equal(parsed.pronouns.reflexive, undefined)
|
|
137
|
+
})
|
package/src/parser.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { CommentRecord, NoneRecord, PRONOUN_TAGS, PronounRecord, PronounsRecord, PronounTag, WildcardRecord } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parses a record of pronouns
|
|
5
|
+
* @param record The record to parse
|
|
6
|
+
* @param silenceErrors Whether to silently error and return null or to `throw` errors
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
export function parseRecord(record: string, silenceErrors = false): PronounRecord | null {
|
|
10
|
+
let parsed = record;
|
|
11
|
+
// separating the comment from the rest
|
|
12
|
+
let comment;
|
|
13
|
+
if (parsed.includes("#")) {
|
|
14
|
+
comment = record.split("#")[1]
|
|
15
|
+
parsed = record.split("#")[0]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// remove all whitespaces, make everything lowercase
|
|
19
|
+
parsed = parsed.trim().toLocaleLowerCase().replace(/\s/g, "")
|
|
20
|
+
|
|
21
|
+
// check special cases (wildcard, none, nothing other than a comment)
|
|
22
|
+
switch (parsed) {
|
|
23
|
+
case "*": {
|
|
24
|
+
return {
|
|
25
|
+
type: "wildcard",
|
|
26
|
+
comment: comment,
|
|
27
|
+
raw: record,
|
|
28
|
+
cleanedRaw: parsed
|
|
29
|
+
} as WildcardRecord
|
|
30
|
+
}
|
|
31
|
+
case "!": {
|
|
32
|
+
return {
|
|
33
|
+
type: "none",
|
|
34
|
+
comment: comment,
|
|
35
|
+
raw: record,
|
|
36
|
+
cleanedRaw: parsed
|
|
37
|
+
} as NoneRecord
|
|
38
|
+
}
|
|
39
|
+
case "": {
|
|
40
|
+
if (comment) {
|
|
41
|
+
return {
|
|
42
|
+
type: "comment-only",
|
|
43
|
+
comment: comment,
|
|
44
|
+
raw: record,
|
|
45
|
+
cleanedRaw: parsed
|
|
46
|
+
} as CommentRecord
|
|
47
|
+
} else {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// check for tags
|
|
54
|
+
let tags: PronounTag[] = [];
|
|
55
|
+
if (parsed.includes(";")) {
|
|
56
|
+
const parsedTags = parsed.split(";").slice(1);
|
|
57
|
+
for (const i in parsedTags) {
|
|
58
|
+
let t = parsedTags[i]
|
|
59
|
+
if (t == "") {
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
if (!PRONOUN_TAGS.includes(t as PronounTag)) {
|
|
63
|
+
if (!silenceErrors) {
|
|
64
|
+
throw Error("fetched pronoun \""+record+"\" contains invalid tag(s)")
|
|
65
|
+
} else {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!tags.includes(t as PronounTag)) {
|
|
70
|
+
tags.push(t as PronounTag)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
parsed = parsed.split(";")[0]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// check for pronoun parts
|
|
77
|
+
let parts = parsed.split("/")
|
|
78
|
+
if (parts.length < 2 || parts.length > 5) {
|
|
79
|
+
if (!silenceErrors) {
|
|
80
|
+
throw Error("fetched pronoun \"" + record + "\" is not formatted correctly")
|
|
81
|
+
} else {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const i in parts) {
|
|
87
|
+
if (!/^[a-z]+$/.test(parts[i])) {
|
|
88
|
+
if (!silenceErrors) {
|
|
89
|
+
throw Error("fetched pronoun \"" + record +"\" contains invalid characters in its pronoun set")
|
|
90
|
+
} else {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// special cases
|
|
97
|
+
if (parts[0] == "it" && parts[1] == "its") {
|
|
98
|
+
parts = ["it","it","its","its","itself"]
|
|
99
|
+
}
|
|
100
|
+
if (parts[0] == "they" && parts[1] == "them" && !tags.includes("plural")) {
|
|
101
|
+
tags.push("plural")
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
pronouns: {
|
|
106
|
+
subject: parts[0],
|
|
107
|
+
object: parts[1],
|
|
108
|
+
possessiveDeterminer: parts[2],
|
|
109
|
+
possessivePronoun: parts[3],
|
|
110
|
+
reflexive: parts[4]
|
|
111
|
+
|
|
112
|
+
},
|
|
113
|
+
tags: tags,
|
|
114
|
+
type: "pronouns",
|
|
115
|
+
raw: record,
|
|
116
|
+
comment: comment,
|
|
117
|
+
cleanedRaw: parsed
|
|
118
|
+
} as PronounsRecord
|
|
119
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
interface PronounSet {
|
|
2
|
+
subject: string;
|
|
3
|
+
object: string;
|
|
4
|
+
possessiveDeterminer?: string;
|
|
5
|
+
possessivePronoun?: string;
|
|
6
|
+
reflexive?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const PRONOUN_TAGS = ['preferred', 'plural'] as const;
|
|
10
|
+
export type PronounTag = typeof PRONOUN_TAGS[number];
|
|
11
|
+
|
|
12
|
+
interface Record {
|
|
13
|
+
raw: string;
|
|
14
|
+
cleanedRaw: string;
|
|
15
|
+
comment?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PronounsRecord extends Record {
|
|
19
|
+
type: 'pronouns';
|
|
20
|
+
pronouns: PronounSet;
|
|
21
|
+
tags: PronounTag[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface WildcardRecord extends Record {
|
|
25
|
+
type: 'wildcard';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface NoneRecord extends Record {
|
|
29
|
+
type: 'none';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CommentRecord extends Record {
|
|
33
|
+
type: 'comment-only'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PronounRecord = PronounsRecord | WildcardRecord | NoneRecord | CommentRecord;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*"
|
|
16
|
+
],
|
|
17
|
+
"exclude": [
|
|
18
|
+
"node_modules",
|
|
19
|
+
"dist",
|
|
20
|
+
"src/**/*.test.ts"
|
|
21
|
+
]
|
|
22
|
+
}
|