easyen 0.1.1
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/LICENSE +21 -0
- package/README.EN.md +22 -0
- package/README.md +20 -0
- package/SKILL.md +106 -0
- package/dist/base-form.d.ts +6 -0
- package/dist/base-form.js +98 -0
- package/dist/classify.d.ts +22 -0
- package/dist/classify.js +50 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +108 -0
- package/dist/coverage.d.ts +64 -0
- package/dist/coverage.js +120 -0
- package/dist/dictionaries/academic.json +1 -0
- package/dist/dictionaries/everyday.json +1 -0
- package/dist/dictionaries/frameworks.json +1 -0
- package/dist/dictionaries/index.d.ts +19 -0
- package/dist/dictionaries/index.js +49 -0
- package/dist/dictionaries/tech.json +1 -0
- package/dist/dictionary.d.ts +8 -0
- package/dist/dictionary.js +30 -0
- package/dist/extract.d.ts +14 -0
- package/dist/extract.js +37 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +53 -0
- package/dist/irregulars.d.ts +11 -0
- package/dist/irregulars.js +108 -0
- package/dist/normalize.d.ts +11 -0
- package/dist/normalize.js +38 -0
- package/dist/pipe.d.ts +8 -0
- package/dist/pipe.js +13 -0
- package/dist/sentences.d.ts +23 -0
- package/dist/sentences.js +36 -0
- package/package.json +75 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* String-level cleaning steps. Each function does exactly one transform and
|
|
4
|
+
* is pure: same input always gives the same output, no side effects.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.normalizeApostrophes = normalizeApostrophes;
|
|
8
|
+
exports.expandContractions = expandContractions;
|
|
9
|
+
/** Turn curly apostrophes (’) into straight ones ('). */
|
|
10
|
+
function normalizeApostrophes(text) {
|
|
11
|
+
return text.replace(/[’]/g, "'");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Ordered contraction rules. Specials (won't, can't) must run before the
|
|
15
|
+
* generic "n't" rule, otherwise "can't" would wrongly become "ca not".
|
|
16
|
+
* Expansions are lower-case function words; that is fine because matching
|
|
17
|
+
* is case-insensitive.
|
|
18
|
+
*/
|
|
19
|
+
const CONTRACTION_RULES = [
|
|
20
|
+
[/\bwon't\b/gi, "will not"],
|
|
21
|
+
[/\bcan't\b/gi, "can not"],
|
|
22
|
+
[/\bshan't\b/gi, "shall not"],
|
|
23
|
+
[/\bain't\b/gi, "be not"],
|
|
24
|
+
[/n't\b/gi, " not"], // don't -> do not, isn't -> is not
|
|
25
|
+
[/'re\b/gi, " are"], // they're -> they are
|
|
26
|
+
[/'ve\b/gi, " have"], // I've -> I have
|
|
27
|
+
[/'ll\b/gi, " will"], // we'll -> we will
|
|
28
|
+
[/'m\b/gi, " am"], // I'm -> I am
|
|
29
|
+
[/'d\b/gi, ""], // I'd -> I (had/would is ambiguous, keep the stem)
|
|
30
|
+
[/'s\b/gi, ""], // it's / child's -> drop (is/has or possessive)
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Expand the common English contractions so their base words can be matched.
|
|
34
|
+
* Assumes apostrophes are already straight (run `normalizeApostrophes` first).
|
|
35
|
+
*/
|
|
36
|
+
function expandContractions(text) {
|
|
37
|
+
return CONTRACTION_RULES.reduce((acc, [pattern, replacement]) => acc.replace(pattern, replacement), text);
|
|
38
|
+
}
|
package/dist/pipe.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Left-to-right function composition for same-typed steps.
|
|
3
|
+
*
|
|
4
|
+
* const f = pipe(a, b, c); // f(x) === c(b(a(x)))
|
|
5
|
+
*
|
|
6
|
+
* Used to chain the string-cleaning steps into one readable pipeline.
|
|
7
|
+
*/
|
|
8
|
+
export declare function pipe<T>(...steps: Array<(value: T) => T>): (value: T) => T;
|
package/dist/pipe.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pipe = pipe;
|
|
4
|
+
/**
|
|
5
|
+
* Left-to-right function composition for same-typed steps.
|
|
6
|
+
*
|
|
7
|
+
* const f = pipe(a, b, c); // f(x) === c(b(a(x)))
|
|
8
|
+
*
|
|
9
|
+
* Used to chain the string-cleaning steps into one readable pipeline.
|
|
10
|
+
*/
|
|
11
|
+
function pipe(...steps) {
|
|
12
|
+
return (value) => steps.reduce((acc, step) => step(acc), value);
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** A sentence that is longer than the limit. */
|
|
2
|
+
export interface LongSentence {
|
|
3
|
+
text: string;
|
|
4
|
+
words: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SentenceCheck {
|
|
7
|
+
/** How many sentences were found. */
|
|
8
|
+
sentences: number;
|
|
9
|
+
/** Average words per sentence (the main number; lower reads easier). */
|
|
10
|
+
wordsPerSentence: number;
|
|
11
|
+
/** Words in the longest sentence. */
|
|
12
|
+
longest: number;
|
|
13
|
+
/** Sentences over the limit, longest first. These are the ones to break up. */
|
|
14
|
+
longSentences: LongSentence[];
|
|
15
|
+
}
|
|
16
|
+
export interface SentenceOptions {
|
|
17
|
+
/**
|
|
18
|
+
* A sentence with more words than this is "long".
|
|
19
|
+
* @default 30
|
|
20
|
+
*/
|
|
21
|
+
longSentenceWords?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare function checkSentences(text: string, options?: SentenceOptions): SentenceCheck;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkSentences = checkSentences;
|
|
4
|
+
/**
|
|
5
|
+
* The second readability signal: sentence length.
|
|
6
|
+
*
|
|
7
|
+
* Word coverage cannot see that a text is hard because its sentences are long
|
|
8
|
+
* and winding. This counts sentence length — pure measurement, no judgement.
|
|
9
|
+
* The AI decides whether a long sentence should be broken up.
|
|
10
|
+
*/
|
|
11
|
+
const extract_1 = require("./extract");
|
|
12
|
+
/** Split text into sentence-like pieces (on . ! ? and line breaks). */
|
|
13
|
+
function splitSentences(text) {
|
|
14
|
+
return text
|
|
15
|
+
.split(/[.!?]+(?=\s|$)|\n+/)
|
|
16
|
+
.map((piece) => piece.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
function checkSentences(text, options = {}) {
|
|
20
|
+
const limit = options.longSentenceWords ?? 30;
|
|
21
|
+
const pieces = splitSentences(text)
|
|
22
|
+
.map((piece) => ({ text: piece, words: (0, extract_1.splitWords)(piece).length }))
|
|
23
|
+
.filter((s) => s.words > 0);
|
|
24
|
+
const sentences = pieces.length;
|
|
25
|
+
const totalWords = pieces.reduce((sum, s) => sum + s.words, 0);
|
|
26
|
+
const longest = pieces.reduce((max, s) => Math.max(max, s.words), 0);
|
|
27
|
+
const longSentences = pieces
|
|
28
|
+
.filter((s) => s.words > limit)
|
|
29
|
+
.sort((a, b) => b.words - a.words);
|
|
30
|
+
return {
|
|
31
|
+
sentences,
|
|
32
|
+
wordsPerSentence: sentences === 0 ? 0 : totalWords / sentences,
|
|
33
|
+
longest,
|
|
34
|
+
longSentences,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "easyen",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Check how easy an English text is to read — vocabulary level and sentence length. Helps keep AI output simple. Pure TypeScript, zero runtime dependencies.",
|
|
5
|
+
"author": "zhangxiangliang",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"english",
|
|
8
|
+
"readability",
|
|
9
|
+
"vocabulary",
|
|
10
|
+
"plain-english",
|
|
11
|
+
"sentence-length",
|
|
12
|
+
"ai",
|
|
13
|
+
"typescript",
|
|
14
|
+
"可读性",
|
|
15
|
+
"词汇",
|
|
16
|
+
"英语"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/zhangxiangliang/easyen.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/zhangxiangliang/easyen#readme",
|
|
23
|
+
"bugs": "https://github.com/zhangxiangliang/easyen/issues",
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"bin": {
|
|
27
|
+
"easyen": "dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"require": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
41
|
+
"prebuild": "npm run clean",
|
|
42
|
+
"build": "tsc -p tsconfig.build.json",
|
|
43
|
+
"postbuild": "node scripts/copy-dictionaries.mjs && node -e \"require('fs').chmodSync('dist/cli.js', 0o755)\"",
|
|
44
|
+
"prepack": "npm run build",
|
|
45
|
+
"prepare": "husky",
|
|
46
|
+
"lint": "eslint .",
|
|
47
|
+
"lint:fix": "eslint . --fix",
|
|
48
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
|
|
49
|
+
"test": "jest --config jest.config.js --coverage",
|
|
50
|
+
"test:unit": "npm test -- --runInBand",
|
|
51
|
+
"ci": "npm run build && npm run typecheck && npm test",
|
|
52
|
+
"validate": "npm run lint && npm run ci",
|
|
53
|
+
"validate:release": "npm run validate"
|
|
54
|
+
},
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"files": [
|
|
57
|
+
"dist/**/*",
|
|
58
|
+
"SKILL.md"
|
|
59
|
+
],
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@eslint/js": "^10.0.1",
|
|
62
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
63
|
+
"@semantic-release/git": "^10.0.1",
|
|
64
|
+
"@types/jest": "^30.0.0",
|
|
65
|
+
"@types/node": "^24.0.0",
|
|
66
|
+
"eslint": "^10.2.1",
|
|
67
|
+
"globals": "^17.5.0",
|
|
68
|
+
"husky": "^9.1.7",
|
|
69
|
+
"jest": "^30.3.0",
|
|
70
|
+
"semantic-release": "^25.0.3",
|
|
71
|
+
"ts-jest": "^29.4.9",
|
|
72
|
+
"typescript": "^6.0.3",
|
|
73
|
+
"typescript-eslint": "^8.59.1"
|
|
74
|
+
}
|
|
75
|
+
}
|