@joystyk/content 0.0.0-stage → 0.2.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/NOTICE +36 -0
- package/README.md +113 -2
- package/dist/banks.d.ts +14 -0
- package/dist/banks.js +55 -0
- package/dist/banks.js.map +1 -0
- package/dist/extract-json.d.ts +12 -0
- package/dist/extract-json.js +59 -0
- package/dist/extract-json.js.map +1 -0
- package/dist/filter.d.ts +56 -0
- package/dist/filter.js +127 -0
- package/dist/filter.js.map +1 -0
- package/dist/games.d.ts +9 -0
- package/dist/games.js +40 -0
- package/dist/games.js.map +1 -0
- package/dist/hash.d.ts +4 -0
- package/dist/hash.js +17 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +16 -0
- package/dist/log.d.ts +26 -0
- package/dist/log.js +58 -0
- package/dist/log.js.map +1 -0
- package/dist/model.d.ts +77 -0
- package/dist/model.js +268 -0
- package/dist/model.js.map +1 -0
- package/dist/packs.d.ts +144 -0
- package/dist/packs.js +466 -0
- package/dist/packs.js.map +1 -0
- package/dist/pipeline.d.ts +252 -0
- package/dist/pipeline.js +558 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/publish.d.ts +21 -0
- package/dist/publish.js +56 -0
- package/dist/publish.js.map +1 -0
- package/dist/ranked.d.ts +12 -0
- package/dist/ranked.js +40 -0
- package/dist/ranked.js.map +1 -0
- package/dist/screen.d.ts +25 -0
- package/dist/screen.js +46 -0
- package/dist/screen.js.map +1 -0
- package/dist/store.d.ts +136 -0
- package/dist/store.js +300 -0
- package/dist/store.js.map +1 -0
- package/dist/text.d.ts +31 -0
- package/dist/text.js +111 -0
- package/dist/text.js.map +1 -0
- package/dist/vocabulary.d.ts +16 -0
- package/dist/vocabulary.js +44 -0
- package/dist/vocabulary.js.map +1 -0
- package/lists/SCOWL-COPYRIGHT +252 -0
- package/lists/blocklist.txt +214 -0
- package/lists/common-extra.txt +13 -0
- package/lists/scowl-common.txt +70763 -0
- package/lists/scowl-ranked.txt +72076 -0
- package/lists/scowl-vocabulary.txt +55636 -0
- package/lists/sensitive.txt +280 -0
- package/package.json +53 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../src/store.ts"],"sourcesContent":["// content.db: the pipeline's records on node:sqlite (content-pipeline.md, \"The lifecycle of one\n// item\"). One row per item, with its status, provenance and validator reports; run records;\n// an event log of every status change and staff action; and the overlap lock that keeps two\n// runs (the scheduler's and a manual one) from working at once (D-56). WAL mode, so the site\n// can read it while the job writes, once /admin needs to.\nimport { DatabaseSync, type SQLInputValue } from 'node:sqlite';\nimport { isJson, type Json } from '@joystyk/contracts';\nimport * as z from 'zod/mini';\n\nexport type ItemStatus = 'scheduled' | 'generating' | 'failed' | 'validated' | 'published' | 'withdrawn';\nexport type ItemSource = 'generated' | 'bank' | 'staff';\n/** Reason codes for a takedown (content-pipeline.md, \"Moderation in /admin\"). */\nexport const WITHDRAW_REASONS = ['inaccurate', 'unfair', 'unsafe', 'dull', 'other'] as const;\nexport type WithdrawReason = (typeof WITHDRAW_REASONS)[number];\n\nexport interface ItemRow {\n readonly id: number;\n readonly gameId: string;\n readonly puzzleId: string;\n readonly date: string;\n readonly status: ItemStatus;\n readonly source: ItemSource | null;\n readonly inputs: Json | null;\n readonly seed: string | null;\n readonly model: string | null;\n readonly promptVersion: string | null;\n /** The answer key used for duplicate checks, e.g. the day's word. */\n readonly answer: string | null;\n readonly config: Json | null;\n readonly contentHash: string | null;\n readonly size: number | null;\n readonly attempts: number;\n /** Every attempt's report: calls, validator issues, filter and screen outcomes. */\n readonly report: Json | null;\n readonly createdAt: string;\n readonly updatedAt: string;\n readonly publishedAt: string | null;\n readonly withdrawnAt: string | null;\n readonly withdrawnReason: string | null;\n readonly withdrawnBy: string | null;\n}\n\nexport type ItemPatch = Partial<\n Pick<\n ItemRow,\n | 'status'\n | 'source'\n | 'inputs'\n | 'seed'\n | 'model'\n | 'promptVersion'\n | 'answer'\n | 'config'\n | 'contentHash'\n | 'size'\n | 'attempts'\n | 'report'\n | 'publishedAt'\n >\n>;\n\nexport interface BankChange {\n readonly gameId: string;\n readonly key: string;\n readonly config: Json | null;\n readonly retired: boolean;\n readonly actor: string;\n readonly changedAt: string;\n}\n\nexport interface RunRow {\n readonly id: number;\n readonly startedAt: string;\n readonly finishedAt: string | null;\n readonly trigger: string;\n readonly status: 'running' | 'ok' | 'partial' | 'failed' | 'aborted';\n readonly summary: Json | null;\n}\n\n/** The schema, one migration per `user_version`. Append; never edit a shipped entry. */\nconst MIGRATIONS: readonly string[] = [\n `CREATE TABLE items (\n id INTEGER PRIMARY KEY,\n game_id TEXT NOT NULL,\n puzzle_id TEXT NOT NULL,\n date TEXT NOT NULL,\n status TEXT NOT NULL,\n source TEXT,\n inputs TEXT,\n seed TEXT,\n model TEXT,\n prompt_version TEXT,\n answer TEXT,\n reserved TEXT,\n config TEXT,\n content_hash TEXT,\n size INTEGER,\n attempts INTEGER NOT NULL DEFAULT 0,\n report TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n published_at TEXT,\n withdrawn_at TEXT,\n withdrawn_reason TEXT,\n withdrawn_by TEXT\n );\n CREATE UNIQUE INDEX items_current ON items (puzzle_id) WHERE status <> 'withdrawn';\n CREATE INDEX items_game_date ON items (game_id, date);\n CREATE TABLE runs (\n id INTEGER PRIMARY KEY,\n started_at TEXT NOT NULL,\n finished_at TEXT,\n trigger TEXT NOT NULL,\n status TEXT NOT NULL,\n summary TEXT\n );\n CREATE TABLE events (\n id INTEGER PRIMARY KEY,\n item_id INTEGER REFERENCES items (id),\n at TEXT NOT NULL,\n kind TEXT NOT NULL,\n actor TEXT NOT NULL,\n detail TEXT\n );\n CREATE INDEX events_item ON events (item_id);\n CREATE TABLE locks (\n name TEXT PRIMARY KEY,\n owner TEXT NOT NULL,\n acquired_at TEXT NOT NULL,\n expires_at INTEGER NOT NULL\n );`,\n `CREATE TABLE bank_changes (\n game_id TEXT NOT NULL,\n bank_key TEXT NOT NULL,\n config TEXT,\n retired INTEGER NOT NULL CHECK (retired IN (0, 1)),\n actor TEXT NOT NULL,\n changed_at TEXT NOT NULL,\n PRIMARY KEY (game_id, bank_key)\n );`,\n];\n\nconst iso = (ms: number): string => new Date(ms).toISOString();\nconst json = (value: Json | null | undefined): string | null =>\n value === null || value === undefined ? null : JSON.stringify(value);\nconst storedJson = z.custom<Json>(isJson);\nconst parse = (text: unknown): Json | null =>\n typeof text === 'string' ? storedJson.parse(JSON.parse(text) as unknown) : null;\nconst str = (value: unknown): string | null => (typeof value === 'string' ? value : null);\n\nconst toItem = (row: Record<string, unknown>): ItemRow => ({\n id: Number(row['id']),\n gameId: String(row['game_id']),\n puzzleId: String(row['puzzle_id']),\n date: String(row['date']),\n status: String(row['status']) as ItemStatus,\n source: str(row['source']) as ItemSource | null,\n inputs: parse(row['inputs']),\n seed: str(row['seed']),\n model: str(row['model']),\n promptVersion: str(row['prompt_version']),\n answer: str(row['answer']),\n config: parse(row['config']),\n contentHash: str(row['content_hash']),\n size: row['size'] === null || row['size'] === undefined ? null : Number(row['size']),\n attempts: Number(row['attempts']),\n report: parse(row['report']),\n createdAt: String(row['created_at']),\n updatedAt: String(row['updated_at']),\n publishedAt: str(row['published_at']),\n withdrawnAt: str(row['withdrawn_at']),\n withdrawnReason: str(row['withdrawn_reason']),\n withdrawnBy: str(row['withdrawn_by']),\n});\n\nconst COLUMNS: Readonly<Record<keyof ItemPatch, string>> = {\n status: 'status',\n source: 'source',\n inputs: 'inputs',\n seed: 'seed',\n model: 'model',\n promptVersion: 'prompt_version',\n answer: 'answer',\n config: 'config',\n contentHash: 'content_hash',\n size: 'size',\n attempts: 'attempts',\n report: 'report',\n publishedAt: 'published_at',\n};\nconst JSON_FIELDS = new Set<keyof ItemPatch>(['inputs', 'config', 'report']);\n\nexport interface ContentStore {\n readonly path: string;\n bankChanges(gameId: string): readonly BankChange[];\n changeBank(input: {\n readonly gameId: string;\n readonly key: string;\n readonly config: Json | null;\n readonly retired: boolean;\n readonly actor: string;\n readonly now: number;\n }): void;\n /** Atomically archives the prior version and creates a validated staff replacement. */\n replaceItem(input: {\n readonly expectedId: number;\n readonly actor: string;\n readonly note: string;\n readonly now: number;\n readonly source: 'staff' | 'bank';\n readonly inputs?: Json | null;\n readonly status: 'validated' | 'published';\n readonly config: Json;\n readonly answer: string | null;\n readonly hash: string;\n readonly size: number;\n }): ItemRow | null;\n /** The item that currently holds a puzzle id (any status but withdrawn), or null. */\n current(puzzleId: string): ItemRow | null;\n item(id: number): ItemRow | null;\n /** Every item for a puzzle id, oldest first, withdrawn ones included. */\n history(puzzleId: string): ItemRow[];\n createItem(input: {\n gameId: string;\n puzzleId: string;\n date: string;\n inputs: Json;\n seed: string;\n answer: string | null;\n now: number;\n }): ItemRow;\n updateItem(\n id: number,\n patch: ItemPatch,\n now: number,\n event?: { kind: string; actor?: string; detail?: Json },\n ): ItemRow;\n /**\n * Answers used by a game's items dated in [from, to], withdrawn ones included (they count as\n * used), and the answers their schedules reserved, even where a bank item took the slot: a\n * word that failed on one day isn't tried again the next.\n */\n usedAnswers(gameId: string, from: string, to: string): { date: string; answer: string; status: ItemStatus }[];\n /** Published (not withdrawn) items of a game dated in [from, to], by date. */\n published(gameId: string, from: string, to: string): ItemRow[];\n /** Validated items waiting to publish, dated up to `through`. */\n validatedThrough(gameId: string, through: string): ItemRow[];\n /** Items of a game in [from, to] with any status, current ones only, by date. */\n items(gameId: string, from: string, to: string): ItemRow[];\n /** All versions in the date range, including withdrawn content, for repeat checks. */\n versions(gameId: string, from: string, to: string): ItemRow[];\n /** Takes an item down (content-pipeline.md, \"Corrections\"). Returns the withdrawn row, or null if none held the id. */\n withdraw(puzzleId: string, reason: WithdrawReason, actor: string, now: number, note?: string): ItemRow | null;\n addEvent(itemId: number | null, kind: string, actor: string, now: number, detail?: Json): void;\n events(itemId: number): { at: string; kind: string; actor: string; detail: Json | null }[];\n startRun(trigger: string, now: number): number;\n finishRun(id: number, status: RunRow['status'], summary: Json, now: number): void;\n recentRuns(limit: number): RunRow[];\n /**\n * Takes the named lock for `owner` until `now + ttlMs`, or refreshes it if `owner` holds it.\n * False when someone else holds an unexpired lock. A crashed holder's lock expires.\n */\n tryLock(name: string, owner: string, now: number, ttlMs: number): boolean;\n releaseLock(name: string, owner: string): void;\n lockHolder(name: string, now: number): { owner: string; acquiredAt: string } | null;\n close(): void;\n}\n\nexport const openStore = (path: string): ContentStore => {\n const db = new DatabaseSync(path);\n db.exec('PRAGMA busy_timeout = 5000');\n if (path !== ':memory:') db.exec('PRAGMA journal_mode = WAL');\n db.exec('PRAGMA foreign_keys = ON');\n const version = Number((db.prepare('PRAGMA user_version').get() as { user_version: number }).user_version);\n if (version > MIGRATIONS.length) {\n db.close();\n throw new Error(\n `content.db has schema version ${String(version)}, newer than this build (${String(MIGRATIONS.length)}). Roll forward, or restore an older backup.`,\n );\n }\n for (let v = version; v < MIGRATIONS.length; v++) {\n db.exec('BEGIN IMMEDIATE');\n try {\n db.exec(MIGRATIONS[v] ?? '');\n db.exec(`PRAGMA user_version = ${String(v + 1)}`);\n db.exec('COMMIT');\n } catch (error) {\n db.exec('ROLLBACK');\n throw error;\n }\n }\n\n const all = (sql: string, ...params: SQLInputValue[]) => db.prepare(sql).all(...params) as Record<string, unknown>[];\n const one = (sql: string, ...params: SQLInputValue[]) =>\n db.prepare(sql).get(...params) as Record<string, unknown> | undefined;\n const run = (sql: string, ...params: SQLInputValue[]) => db.prepare(sql).run(...params);\n const transaction = <T>(fn: () => T): T => {\n db.exec('BEGIN IMMEDIATE');\n try {\n const value = fn();\n db.exec('COMMIT');\n return value;\n } catch (error) {\n db.exec('ROLLBACK');\n throw error;\n }\n };\n const addEvent = (itemId: number | null, kind: string, actor: string, now: number, detail?: Json) => {\n run(\n 'INSERT INTO events (item_id, at, kind, actor, detail) VALUES (?, ?, ?, ?, ?)',\n itemId,\n iso(now),\n kind,\n actor,\n json(detail),\n );\n };\n const item = (id: number) => {\n const row = one('SELECT * FROM items WHERE id = ?', id);\n return row === undefined ? null : toItem(row);\n };\n\n return {\n path,\n current: (puzzleId) => {\n const row = one(\"SELECT * FROM items WHERE puzzle_id = ? AND status <> 'withdrawn'\", puzzleId);\n return row === undefined ? null : toItem(row);\n },\n item,\n history: (puzzleId) => all('SELECT * FROM items WHERE puzzle_id = ? ORDER BY id', puzzleId).map(toItem),\n bankChanges: (gameId) =>\n all('SELECT * FROM bank_changes WHERE game_id = ? ORDER BY bank_key', gameId).map((row) => ({\n gameId: String(row['game_id']),\n key: String(row['bank_key']),\n config: parse(row['config']),\n retired: row['retired'] === 1,\n actor: String(row['actor']),\n changedAt: String(row['changed_at']),\n })),\n changeBank: ({ gameId, key, config, retired, actor, now }) =>\n transaction(() => {\n run(\n 'INSERT INTO bank_changes (game_id, bank_key, config, retired, actor, changed_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (game_id, bank_key) DO UPDATE SET config = excluded.config, retired = excluded.retired, actor = excluded.actor, changed_at = excluded.changed_at',\n gameId,\n key,\n json(config),\n retired ? 1 : 0,\n actor,\n iso(now),\n );\n addEvent(null, 'bank.changed', actor, now, { gameId, key, retired });\n }),\n replaceItem: (input) =>\n transaction(() => {\n const previous = item(input.expectedId);\n if (previous === null || previous.status === 'withdrawn') return null;\n const at = iso(input.now);\n run(\n \"UPDATE items SET status = 'withdrawn', withdrawn_at = ?, withdrawn_reason = 'other', withdrawn_by = ?, updated_at = ? WHERE id = ?\",\n at,\n input.actor,\n at,\n previous.id,\n );\n addEvent(previous.id, 'withdrawn', input.actor, input.now, {\n reason: 'other',\n from: previous.status,\n note: input.note || 'Staff correction.',\n });\n const report: Json = [\n {\n checks: {\n staff: {\n schema: true,\n gameValidators: true,\n allAgesFilter: true,\n modelChecks: 'not rerun; human correction',\n previousItem: previous.id,\n },\n },\n },\n ];\n const result = run(\n `INSERT INTO items (game_id, puzzle_id, date, status, source, inputs, seed, answer, reserved, config, content_hash, size, prompt_version, report, created_at, updated_at, published_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n previous.gameId,\n previous.puzzleId,\n previous.date,\n input.status,\n input.source,\n json(input.inputs === undefined ? previous.inputs : input.inputs),\n previous.seed,\n input.answer,\n input.answer,\n json(input.config),\n input.hash,\n input.size,\n 'staff-validated-v1',\n json(report),\n at,\n at,\n input.status === 'published' ? at : null,\n );\n const id = Number(result.lastInsertRowid);\n addEvent(id, input.source === 'bank' ? 'bank.selected' : 'staff.edited', input.actor, input.now, {\n previousItem: previous.id,\n note: input.note,\n hash: input.hash,\n });\n return item(id);\n }),\n createItem: ({ gameId, puzzleId, date, inputs, seed, answer, now }) =>\n transaction(() => {\n const result = run(\n `INSERT INTO items (game_id, puzzle_id, date, status, inputs, seed, answer, reserved, created_at, updated_at)\n VALUES (?, ?, ?, 'scheduled', ?, ?, ?, ?, ?, ?)`,\n gameId,\n puzzleId,\n date,\n json(inputs),\n seed,\n answer,\n answer,\n iso(now),\n iso(now),\n );\n const id = Number(result.lastInsertRowid);\n addEvent(id, 'scheduled', 'content', now, { inputs });\n return item(id) as ItemRow;\n }),\n updateItem: (id, patch, now, event) =>\n transaction(() => {\n const sets: string[] = ['updated_at = ?'];\n const values: SQLInputValue[] = [iso(now)];\n for (const [key, value] of Object.entries(patch) as [keyof ItemPatch, unknown][]) {\n if (value === undefined) continue;\n sets.push(`${COLUMNS[key]} = ?`);\n values.push(JSON_FIELDS.has(key) ? json(value as Json) : (value as SQLInputValue));\n }\n const changed = run(\n `UPDATE items SET ${sets.join(', ')} WHERE id = ? AND status <> 'withdrawn'`,\n ...values,\n id,\n );\n if (changed.changes !== 0 && event !== undefined)\n addEvent(id, event.kind, event.actor ?? 'content', now, event.detail);\n return item(id) as ItemRow;\n }),\n usedAnswers: (gameId, from, to) =>\n all(\n `SELECT date, answer, status FROM items WHERE game_id = ? AND date >= ? AND date <= ? AND answer IS NOT NULL\n UNION SELECT date, reserved, status FROM items WHERE game_id = ? AND date >= ? AND date <= ? AND reserved IS NOT NULL\n ORDER BY date`,\n gameId,\n from,\n to,\n gameId,\n from,\n to,\n ).map((row) => ({\n date: String(row['date']),\n answer: String(row['answer']),\n status: String(row['status']) as ItemStatus,\n })),\n published: (gameId, from, to) =>\n all(\n \"SELECT * FROM items WHERE game_id = ? AND status = 'published' AND date >= ? AND date <= ? ORDER BY date\",\n gameId,\n from,\n to,\n ).map(toItem),\n validatedThrough: (gameId, through) =>\n all(\n \"SELECT * FROM items WHERE game_id = ? AND status = 'validated' AND date <= ? ORDER BY date\",\n gameId,\n through,\n ).map(toItem),\n items: (gameId, from, to) =>\n all(\n \"SELECT * FROM items WHERE game_id = ? AND status <> 'withdrawn' AND date >= ? AND date <= ? ORDER BY date\",\n gameId,\n from,\n to,\n ).map(toItem),\n versions: (gameId, from, to) =>\n all('SELECT * FROM items WHERE game_id = ? AND date >= ? AND date <= ? ORDER BY date, id', gameId, from, to).map(\n toItem,\n ),\n withdraw: (puzzleId, reason, actor, now, note) =>\n transaction(() => {\n const row = one(\"SELECT * FROM items WHERE puzzle_id = ? AND status <> 'withdrawn'\", puzzleId);\n if (row === undefined) return null;\n const before = toItem(row);\n run(\n \"UPDATE items SET status = 'withdrawn', withdrawn_at = ?, withdrawn_reason = ?, withdrawn_by = ?, updated_at = ? WHERE id = ?\",\n iso(now),\n reason,\n actor,\n iso(now),\n before.id,\n );\n addEvent(before.id, 'withdrawn', actor, now, {\n reason,\n from: before.status,\n ...(note === undefined ? {} : { note }),\n });\n return item(before.id);\n }),\n addEvent,\n events: (itemId) =>\n all('SELECT at, kind, actor, detail FROM events WHERE item_id = ? ORDER BY id', itemId).map((row) => ({\n at: String(row['at']),\n kind: String(row['kind']),\n actor: String(row['actor']),\n detail: parse(row['detail']),\n })),\n startRun: (trigger, now) =>\n Number(\n run(\"INSERT INTO runs (started_at, trigger, status) VALUES (?, ?, 'running')\", iso(now), trigger)\n .lastInsertRowid,\n ),\n finishRun: (id, status, summary, now) => {\n run('UPDATE runs SET finished_at = ?, status = ?, summary = ? WHERE id = ?', iso(now), status, json(summary), id);\n },\n recentRuns: (limit) =>\n all('SELECT * FROM runs ORDER BY id DESC LIMIT ?', limit).map((row) => ({\n id: Number(row['id']),\n startedAt: String(row['started_at']),\n finishedAt: str(row['finished_at']),\n trigger: String(row['trigger']),\n status: String(row['status']) as RunRow['status'],\n summary: parse(row['summary']),\n })),\n tryLock: (name, owner, now, ttlMs) =>\n transaction(() => {\n const row = one('SELECT owner, expires_at FROM locks WHERE name = ?', name);\n if (row !== undefined && row['owner'] !== owner && Number(row['expires_at']) > now) return false;\n run(\n `INSERT INTO locks (name, owner, acquired_at, expires_at) VALUES (?, ?, ?, ?)\n ON CONFLICT (name) DO UPDATE SET owner = excluded.owner, expires_at = excluded.expires_at,\n acquired_at = CASE WHEN locks.owner = excluded.owner THEN locks.acquired_at ELSE excluded.acquired_at END`,\n name,\n owner,\n iso(now),\n now + ttlMs,\n );\n return true;\n }),\n releaseLock: (name, owner) => {\n run('DELETE FROM locks WHERE name = ? AND owner = ?', name, owner);\n },\n lockHolder: (name, now) => {\n const row = one('SELECT owner, acquired_at, expires_at FROM locks WHERE name = ?', name);\n return row === undefined || Number(row['expires_at']) <= now\n ? null\n : { owner: String(row['owner']), acquiredAt: String(row['acquired_at']) };\n },\n close: () => {\n if (db.isOpen) db.close();\n },\n };\n};\n"],"mappings":"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"}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Lowercase, accents removed, curly apostrophes made straight. */
|
|
2
|
+
export declare const fold: (text: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* The words in a text, folded: letter runs, with inner apostrophes kept ("don't") and a
|
|
5
|
+
* possessive "'s" dropped ("cat's" → "cat"). Digits, hyphens and punctuation split words.
|
|
6
|
+
*/
|
|
7
|
+
export declare const wordsOf: (text: string) => string[];
|
|
8
|
+
/**
|
|
9
|
+
* The regular inflected and simple derived forms of a word: plurals and verb endings,
|
|
10
|
+
* comparatives, and -ly adverbs, with the usual spelling changes (final e, final y, doubled
|
|
11
|
+
* final consonant). Generous on purpose: it's used to *find* the word, never to build text.
|
|
12
|
+
*/
|
|
13
|
+
export declare const formsOf: (input: string) => Set<string>;
|
|
14
|
+
/**
|
|
15
|
+
* A word's stem for leak checks: the word without its longest common suffix, kept only when
|
|
16
|
+
* at least five letters remain (shorter stems such as "prob" from "probe" would match
|
|
17
|
+
* innocent words). Null when no useful stem exists.
|
|
18
|
+
*/
|
|
19
|
+
export declare const stemOf: (input: string) => string | null;
|
|
20
|
+
/** True when `text` uses `word` in one of its forms, or a word starting with its stem. */
|
|
21
|
+
export declare const mentions: (text: string, word: string) => boolean;
|
|
22
|
+
/** True when `text` contains `word` in one of its regular forms (not just its stem). */
|
|
23
|
+
export declare const usesForm: (text: string, word: string) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The strict leak check for games where players guess letters (Daily Word, Hangman,
|
|
26
|
+
* crosswords): the answer, its stem, or any run of `minRun` or more consecutive letters of it
|
|
27
|
+
* appears in the text, compared case- and accent-insensitively.
|
|
28
|
+
*/
|
|
29
|
+
export declare const leaksLetters: (text: string, answer: string, minRun?: number) => boolean;
|
|
30
|
+
/** Text compared for sameness: folded, punctuation and extra spaces removed. */
|
|
31
|
+
export declare const sameText: (a: string, b: string) => boolean;
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//#region src/text.ts
|
|
2
|
+
/** Lowercase, accents removed, curly apostrophes made straight. */
|
|
3
|
+
var fold = (text) => text.normalize("NFKD").replace(/\p{M}/gu, "").replace(/[‘’ʼ]/g, "'").toLowerCase();
|
|
4
|
+
/**
|
|
5
|
+
* The words in a text, folded: letter runs, with inner apostrophes kept ("don't") and a
|
|
6
|
+
* possessive "'s" dropped ("cat's" → "cat"). Digits, hyphens and punctuation split words.
|
|
7
|
+
*/
|
|
8
|
+
var wordsOf = (text) => (fold(text).match(/\p{L}+(?:'\p{L}+)*/gu) ?? []).map((word) => word.replace(/'s$/, ""));
|
|
9
|
+
var VOWEL = /[aeiou]/;
|
|
10
|
+
var isConsonant = (ch) => ch !== void 0 && /[a-z]/.test(ch) && !VOWEL.test(ch);
|
|
11
|
+
/**
|
|
12
|
+
* The regular inflected and simple derived forms of a word: plurals and verb endings,
|
|
13
|
+
* comparatives, and -ly adverbs, with the usual spelling changes (final e, final y, doubled
|
|
14
|
+
* final consonant). Generous on purpose: it's used to *find* the word, never to build text.
|
|
15
|
+
*/
|
|
16
|
+
var formsOf = (input) => {
|
|
17
|
+
const word = fold(input);
|
|
18
|
+
const forms = /* @__PURE__ */ new Set([word]);
|
|
19
|
+
const add = (...items) => items.forEach((item) => forms.add(item));
|
|
20
|
+
const last = word.at(-1);
|
|
21
|
+
const stemE = word.endsWith("e") ? word.slice(0, -1) : word;
|
|
22
|
+
add(`${word}s`, `${word}es`, `${word}d`, `${word}ed`, `${word}ing`, `${word}er`, `${word}ers`, `${word}est`, `${word}ly`, `${word}ness`);
|
|
23
|
+
add(`${stemE}ing`, `${stemE}ed`, `${stemE}er`, `${stemE}est`, `${stemE}ers`);
|
|
24
|
+
if (last === "y" && isConsonant(word.at(-2))) {
|
|
25
|
+
const base = word.slice(0, -1);
|
|
26
|
+
add(`${base}ies`, `${base}ied`, `${base}ier`, `${base}iest`, `${base}ily`, `${base}iness`);
|
|
27
|
+
}
|
|
28
|
+
if (word.endsWith("le")) add(`${word.slice(0, -1)}y`);
|
|
29
|
+
if (word.endsWith("ie")) add(`${word.slice(0, -2)}ying`);
|
|
30
|
+
if (word.endsWith("ic")) add(`${word}ally`, `${word}ked`, `${word}king`);
|
|
31
|
+
if (isConsonant(last) && last !== "w" && last !== "x" && last !== "y" && VOWEL.test(word.at(-2) ?? "")) add(`${word}${last}ed`, `${word}${last}ing`, `${word}${last}er`, `${word}${last}est`, `${word}${last}y`);
|
|
32
|
+
if (/(s|x|z|ch|sh)$/.test(word)) add(`${word}es`);
|
|
33
|
+
return forms;
|
|
34
|
+
};
|
|
35
|
+
var SUFFIXES = [
|
|
36
|
+
"ically",
|
|
37
|
+
"ation",
|
|
38
|
+
"ously",
|
|
39
|
+
"ness",
|
|
40
|
+
"ment",
|
|
41
|
+
"ance",
|
|
42
|
+
"ence",
|
|
43
|
+
"ible",
|
|
44
|
+
"able",
|
|
45
|
+
"ious",
|
|
46
|
+
"eous",
|
|
47
|
+
"ous",
|
|
48
|
+
"ive",
|
|
49
|
+
"ity",
|
|
50
|
+
"ism",
|
|
51
|
+
"ist",
|
|
52
|
+
"ing",
|
|
53
|
+
"ate",
|
|
54
|
+
"ize",
|
|
55
|
+
"ise",
|
|
56
|
+
"ful",
|
|
57
|
+
"ial",
|
|
58
|
+
"ian",
|
|
59
|
+
"ent",
|
|
60
|
+
"ant",
|
|
61
|
+
"ary",
|
|
62
|
+
"ory",
|
|
63
|
+
"al",
|
|
64
|
+
"ic",
|
|
65
|
+
"ed",
|
|
66
|
+
"er",
|
|
67
|
+
"ly",
|
|
68
|
+
"es",
|
|
69
|
+
"y",
|
|
70
|
+
"e",
|
|
71
|
+
"s"
|
|
72
|
+
];
|
|
73
|
+
/**
|
|
74
|
+
* A word's stem for leak checks: the word without its longest common suffix, kept only when
|
|
75
|
+
* at least five letters remain (shorter stems such as "prob" from "probe" would match
|
|
76
|
+
* innocent words). Null when no useful stem exists.
|
|
77
|
+
*/
|
|
78
|
+
var stemOf = (input) => {
|
|
79
|
+
const word = fold(input);
|
|
80
|
+
for (const suffix of SUFFIXES) if (word.endsWith(suffix) && word.length - suffix.length >= 5) return word.slice(0, -suffix.length);
|
|
81
|
+
return word.length >= 6 ? word : null;
|
|
82
|
+
};
|
|
83
|
+
/** True when `text` uses `word` in one of its forms, or a word starting with its stem. */
|
|
84
|
+
var mentions = (text, word) => {
|
|
85
|
+
const forms = formsOf(word);
|
|
86
|
+
const stem = stemOf(word);
|
|
87
|
+
return wordsOf(text).some((token) => forms.has(token) || stem !== null && token.startsWith(stem));
|
|
88
|
+
};
|
|
89
|
+
/** True when `text` contains `word` in one of its regular forms (not just its stem). */
|
|
90
|
+
var usesForm = (text, word) => {
|
|
91
|
+
const forms = formsOf(word);
|
|
92
|
+
return wordsOf(text).some((token) => forms.has(token));
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The strict leak check for games where players guess letters (Daily Word, Hangman,
|
|
96
|
+
* crosswords): the answer, its stem, or any run of `minRun` or more consecutive letters of it
|
|
97
|
+
* appears in the text, compared case- and accent-insensitively.
|
|
98
|
+
*/
|
|
99
|
+
var leaksLetters = (text, answer, minRun = 4) => {
|
|
100
|
+
if (mentions(text, answer)) return true;
|
|
101
|
+
const letters = fold(answer).replace(/[^a-z]/g, "");
|
|
102
|
+
const haystack = wordsOf(text).join(" ");
|
|
103
|
+
for (let i = 0; i + minRun <= letters.length; i++) if (haystack.includes(letters.slice(i, i + minRun))) return true;
|
|
104
|
+
return false;
|
|
105
|
+
};
|
|
106
|
+
/** Text compared for sameness: folded, punctuation and extra spaces removed. */
|
|
107
|
+
var sameText = (a, b) => wordsOf(a).join(" ") === wordsOf(b).join(" ");
|
|
108
|
+
//#endregion
|
|
109
|
+
export { fold, formsOf, leaksLetters, mentions, sameText, stemOf, usesForm, wordsOf };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","names":[],"sources":["../src/text.ts"],"sourcesContent":["// Text helpers shared by the all-ages filter and the games' validators: tokenizing, the\n// common inflected forms of a word, and answer-leak checks (content-pipeline.md, \"Validation\").\n\n/** Lowercase, accents removed, curly apostrophes made straight. */\nexport const fold = (text: string): string =>\n text.normalize('NFKD').replace(/\\p{M}/gu, '').replace(/[‘’ʼ]/g, \"'\").toLowerCase();\n\n/**\n * The words in a text, folded: letter runs, with inner apostrophes kept (\"don't\") and a\n * possessive \"'s\" dropped (\"cat's\" → \"cat\"). Digits, hyphens and punctuation split words.\n */\nexport const wordsOf = (text: string): string[] =>\n (fold(text).match(/\\p{L}+(?:'\\p{L}+)*/gu) ?? []).map((word) => word.replace(/'s$/, ''));\n\nconst VOWEL = /[aeiou]/;\nconst isConsonant = (ch: string | undefined): boolean => ch !== undefined && /[a-z]/.test(ch) && !VOWEL.test(ch);\n\n/**\n * The regular inflected and simple derived forms of a word: plurals and verb endings,\n * comparatives, and -ly adverbs, with the usual spelling changes (final e, final y, doubled\n * final consonant). Generous on purpose: it's used to *find* the word, never to build text.\n */\nexport const formsOf = (input: string): Set<string> => {\n const word = fold(input);\n const forms = new Set([word]);\n const add = (...items: string[]) => items.forEach((item) => forms.add(item));\n const last = word.at(-1);\n const stemE = word.endsWith('e') ? word.slice(0, -1) : word;\n add(\n `${word}s`,\n `${word}es`,\n `${word}d`,\n `${word}ed`,\n `${word}ing`,\n `${word}er`,\n `${word}ers`,\n `${word}est`,\n `${word}ly`,\n `${word}ness`,\n );\n add(`${stemE}ing`, `${stemE}ed`, `${stemE}er`, `${stemE}est`, `${stemE}ers`);\n if (last === 'y' && isConsonant(word.at(-2))) {\n const base = word.slice(0, -1);\n add(`${base}ies`, `${base}ied`, `${base}ier`, `${base}iest`, `${base}ily`, `${base}iness`);\n }\n if (word.endsWith('le')) add(`${word.slice(0, -1)}y`);\n if (word.endsWith('ie')) add(`${word.slice(0, -2)}ying`);\n if (word.endsWith('ic')) add(`${word}ally`, `${word}ked`, `${word}king`);\n if (isConsonant(last) && last !== 'w' && last !== 'x' && last !== 'y' && VOWEL.test(word.at(-2) ?? '')) {\n add(`${word}${last}ed`, `${word}${last}ing`, `${word}${last}er`, `${word}${last}est`, `${word}${last}y`);\n }\n if (/(s|x|z|ch|sh)$/.test(word)) add(`${word}es`);\n return forms;\n};\n\nconst SUFFIXES = [\n 'ically',\n 'ation',\n 'ously',\n 'ness',\n 'ment',\n 'ance',\n 'ence',\n 'ible',\n 'able',\n 'ious',\n 'eous',\n 'ous',\n 'ive',\n 'ity',\n 'ism',\n 'ist',\n 'ing',\n 'ate',\n 'ize',\n 'ise',\n 'ful',\n 'ial',\n 'ian',\n 'ent',\n 'ant',\n 'ary',\n 'ory',\n 'al',\n 'ic',\n 'ed',\n 'er',\n 'ly',\n 'es',\n 'y',\n 'e',\n 's',\n];\n\n/**\n * A word's stem for leak checks: the word without its longest common suffix, kept only when\n * at least five letters remain (shorter stems such as \"prob\" from \"probe\" would match\n * innocent words). Null when no useful stem exists.\n */\nexport const stemOf = (input: string): string | null => {\n const word = fold(input);\n for (const suffix of SUFFIXES) {\n if (word.endsWith(suffix) && word.length - suffix.length >= 5) return word.slice(0, -suffix.length);\n }\n return word.length >= 6 ? word : null;\n};\n\n/** True when `text` uses `word` in one of its forms, or a word starting with its stem. */\nexport const mentions = (text: string, word: string): boolean => {\n const forms = formsOf(word);\n const stem = stemOf(word);\n return wordsOf(text).some((token) => forms.has(token) || (stem !== null && token.startsWith(stem)));\n};\n\n/** True when `text` contains `word` in one of its regular forms (not just its stem). */\nexport const usesForm = (text: string, word: string): boolean => {\n const forms = formsOf(word);\n return wordsOf(text).some((token) => forms.has(token));\n};\n\n/**\n * The strict leak check for games where players guess letters (Daily Word, Hangman,\n * crosswords): the answer, its stem, or any run of `minRun` or more consecutive letters of it\n * appears in the text, compared case- and accent-insensitively.\n */\nexport const leaksLetters = (text: string, answer: string, minRun = 4): boolean => {\n if (mentions(text, answer)) return true;\n const letters = fold(answer).replace(/[^a-z]/g, '');\n const haystack = wordsOf(text).join(' ');\n for (let i = 0; i + minRun <= letters.length; i++) {\n if (haystack.includes(letters.slice(i, i + minRun))) return true;\n }\n return false;\n};\n\n/** Text compared for sameness: folded, punctuation and extra spaces removed. */\nexport const sameText = (a: string, b: string): boolean => wordsOf(a).join(' ') === wordsOf(b).join(' ');\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Word membership questions the validators ask. Tests may pass a small fake. */
|
|
2
|
+
export interface Vocabulary {
|
|
3
|
+
/** In SCOWL at size 50 or below: fine in any player-facing text. */
|
|
4
|
+
isCommon(word: string): boolean;
|
|
5
|
+
/** In SCOWL at size 70 or below: a real word a player may meet as an answer or option. */
|
|
6
|
+
isKnown(word: string): boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Lowercase word families only, plus the reviewed supplement; excludes proper-name lists. */
|
|
9
|
+
export declare const isPlainCommonWord: (input: string) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The SCOWL vocabulary: common is sizes 10–50 plus the short reviewed supplement of modern
|
|
12
|
+
* everyday words in common-extra.txt; known adds sizes 55–70.
|
|
13
|
+
*/
|
|
14
|
+
export declare const scowlVocabulary: () => Vocabulary;
|
|
15
|
+
/** A vocabulary over explicit word sets, for tests and small tools. */
|
|
16
|
+
export declare const vocabularyOf: (common: Iterable<string>, known?: Iterable<string>) => Vocabulary;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fold } from "./text.js";
|
|
2
|
+
import { rankedWords } from "./ranked.js";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
//#region src/vocabulary.ts
|
|
5
|
+
var readWords = (name) => new Set(readFileSync(new URL(`../lists/${name}`, import.meta.url), "utf8").split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#")));
|
|
6
|
+
var cached = null;
|
|
7
|
+
var plainExtra = null;
|
|
8
|
+
/** Lowercase word families only, plus the reviewed supplement; excludes proper-name lists. */
|
|
9
|
+
var isPlainCommonWord = (input) => {
|
|
10
|
+
plainExtra ??= readWords("common-extra.txt");
|
|
11
|
+
const word = fold(input), size = rankedWords().get(word);
|
|
12
|
+
return size !== void 0 && size <= 50 || plainExtra.has(word);
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The SCOWL vocabulary: common is sizes 10–50 plus the short reviewed supplement of modern
|
|
16
|
+
* everyday words in common-extra.txt; known adds sizes 55–70.
|
|
17
|
+
*/
|
|
18
|
+
var scowlVocabulary = () => {
|
|
19
|
+
if (cached !== null) return cached;
|
|
20
|
+
const common = readWords("scowl-common.txt");
|
|
21
|
+
for (const word of readWords("common-extra.txt")) common.add(word);
|
|
22
|
+
const extra = readWords("scowl-vocabulary.txt");
|
|
23
|
+
cached = {
|
|
24
|
+
isCommon: (word) => common.has(fold(word)),
|
|
25
|
+
isKnown: (word) => {
|
|
26
|
+
const folded = fold(word);
|
|
27
|
+
return common.has(folded) || extra.has(folded);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
return cached;
|
|
31
|
+
};
|
|
32
|
+
/** A vocabulary over explicit word sets, for tests and small tools. */
|
|
33
|
+
var vocabularyOf = (common, known = []) => {
|
|
34
|
+
const commonSet = new Set([...common].map(fold));
|
|
35
|
+
const knownSet = new Set([...known].map(fold));
|
|
36
|
+
return {
|
|
37
|
+
isCommon: (word) => commonSet.has(fold(word)),
|
|
38
|
+
isKnown: (word) => commonSet.has(fold(word)) || knownSet.has(fold(word))
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { isPlainCommonWord, scowlVocabulary, vocabularyOf };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=vocabulary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vocabulary.js","names":[],"sources":["../src/vocabulary.ts"],"sourcesContent":["// The SCOWL-derived word lists (lists/build-lists.ts): which words are real, and which are\n// common enough for a confident young reader. Loaded once, lazily, from the package's lists/\n// folder.\nimport { readFileSync } from 'node:fs';\nimport { fold } from './text.ts';\nimport { rankedWords } from './ranked.ts';\n\n/** Word membership questions the validators ask. Tests may pass a small fake. */\nexport interface Vocabulary {\n /** In SCOWL at size 50 or below: fine in any player-facing text. */\n isCommon(word: string): boolean;\n /** In SCOWL at size 70 or below: a real word a player may meet as an answer or option. */\n isKnown(word: string): boolean;\n}\n\nconst readWords = (name: 'scowl-common.txt' | 'scowl-vocabulary.txt' | 'common-extra.txt'): Set<string> =>\n new Set(\n readFileSync(new URL(`../lists/${name}`, import.meta.url), 'utf8')\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line !== '' && !line.startsWith('#')),\n );\n\nlet cached: Vocabulary | null = null;\nlet plainExtra: ReadonlySet<string> | null = null;\n\n/** Lowercase word families only, plus the reviewed supplement; excludes proper-name lists. */\nexport const isPlainCommonWord = (input: string): boolean => {\n plainExtra ??= readWords('common-extra.txt');\n const word = fold(input),\n size = rankedWords().get(word);\n return (size !== undefined && size <= 50) || plainExtra.has(word);\n};\n\n/**\n * The SCOWL vocabulary: common is sizes 10–50 plus the short reviewed supplement of modern\n * everyday words in common-extra.txt; known adds sizes 55–70.\n */\nexport const scowlVocabulary = (): Vocabulary => {\n if (cached !== null) return cached;\n const common = readWords('scowl-common.txt');\n for (const word of readWords('common-extra.txt')) common.add(word);\n const extra = readWords('scowl-vocabulary.txt');\n cached = {\n isCommon: (word) => common.has(fold(word)),\n isKnown: (word) => {\n const folded = fold(word);\n return common.has(folded) || extra.has(folded);\n },\n };\n return cached;\n};\n\n/** A vocabulary over explicit word sets, for tests and small tools. */\nexport const vocabularyOf = (common: Iterable<string>, known: Iterable<string> = []): Vocabulary => {\n const commonSet = new Set([...common].map(fold));\n const knownSet = new Set([...known].map(fold));\n return {\n isCommon: (word) => commonSet.has(fold(word)),\n isKnown: (word) => commonSet.has(fold(word)) || knownSet.has(fold(word)),\n };\n};\n"],"mappings":";;;;AAeA,IAAM,aAAa,SACjB,IAAI,IACF,aAAa,IAAI,IAAI,YAAY,QAAQ,YAAY,GAAG,GAAG,MAAM,CAAC,CAC/D,MAAM,IAAI,CAAC,CACX,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,QAAQ,SAAS,SAAS,MAAM,CAAC,KAAK,WAAW,GAAG,CAAC,CAC1D;AAEF,IAAI,SAA4B;AAChC,IAAI,aAAyC;;AAG7C,IAAa,qBAAqB,UAA2B;CAC3D,eAAe,UAAU,kBAAkB;CAC3C,MAAM,OAAO,KAAK,KAAK,GACrB,OAAO,YAAY,CAAC,CAAC,IAAI,IAAI;CAC/B,OAAQ,SAAS,KAAA,KAAa,QAAQ,MAAO,WAAW,IAAI,IAAI;AAClE;;;;;AAMA,IAAa,wBAAoC;CAC/C,IAAI,WAAW,MAAM,OAAO;CAC5B,MAAM,SAAS,UAAU,kBAAkB;CAC3C,KAAK,MAAM,QAAQ,UAAU,kBAAkB,GAAG,OAAO,IAAI,IAAI;CACjE,MAAM,QAAQ,UAAU,sBAAsB;CAC9C,SAAS;EACP,WAAW,SAAS,OAAO,IAAI,KAAK,IAAI,CAAC;EACzC,UAAU,SAAS;GACjB,MAAM,SAAS,KAAK,IAAI;GACxB,OAAO,OAAO,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM;EAC/C;CACF;CACA,OAAO;AACT;;AAGA,IAAa,gBAAgB,QAA0B,QAA0B,CAAC,MAAkB;CAClG,MAAM,YAAY,IAAI,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC;CAC/C,MAAM,WAAW,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC;CAC7C,OAAO;EACL,WAAW,SAAS,UAAU,IAAI,KAAK,IAAI,CAAC;EAC5C,UAAU,SAAS,UAAU,IAAI,KAAK,IAAI,CAAC,KAAK,SAAS,IAAI,KAAK,IAAI,CAAC;CACzE;AACF"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Derived from SCOWL 2020.12.07 (https://downloads.sourceforge.net/wordlist/scowl-2020.12.07.tar.gz, sha256 5587667caa20c4891390c2d42dbb4d5c4c3f41bee77af1457ece3ba23fb859cc).
|
|
2
|
+
scowl-common.txt: sizes 10 to 50; scowl-vocabulary.txt: sizes 55 to 70.
|
|
3
|
+
Families: english-words, american-words, english-upper, american-upper, english-contractions, english-proper-names, american-proper-names. Lowercased; possessives and protected game names removed.
|
|
4
|
+
|
|
5
|
+
The collective work is Copyright 2000-2018 by Kevin Atkinson as well
|
|
6
|
+
as any of the copyrights mentioned below:
|
|
7
|
+
|
|
8
|
+
Copyright 2000-2018 by Kevin Atkinson
|
|
9
|
+
|
|
10
|
+
Permission to use, copy, modify, distribute and sell these word
|
|
11
|
+
lists, the associated scripts, the output created from the scripts,
|
|
12
|
+
and its documentation for any purpose is hereby granted without fee,
|
|
13
|
+
provided that the above copyright notice appears in all copies and
|
|
14
|
+
that both that copyright notice and this permission notice appear in
|
|
15
|
+
supporting documentation. Kevin Atkinson makes no representations
|
|
16
|
+
about the suitability of this array for any purpose. It is provided
|
|
17
|
+
"as is" without express or implied warranty.
|
|
18
|
+
|
|
19
|
+
Alan Beale <biljir@pobox.com> also deserves special credit as he has,
|
|
20
|
+
in addition to providing the 12Dicts package and being a major
|
|
21
|
+
contributor to the ENABLE word list, given me an incredible amount of
|
|
22
|
+
feedback and created a number of special lists (those found in the
|
|
23
|
+
Supplement) in order to help improve the overall quality of SCOWL.
|
|
24
|
+
|
|
25
|
+
The 10 level includes the 1000 most common English words (according to
|
|
26
|
+
the Moby (TM) Words II [MWords] package), a subset of the 1000 most
|
|
27
|
+
common words on the Internet (again, according to Moby Words II), and
|
|
28
|
+
frequently class 16 from Brian Kelk's "UK English Wordlist
|
|
29
|
+
with Frequency Classification".
|
|
30
|
+
|
|
31
|
+
The MWords package was explicitly placed in the public domain:
|
|
32
|
+
|
|
33
|
+
The Moby lexicon project is complete and has
|
|
34
|
+
been place into the public domain. Use, sell,
|
|
35
|
+
rework, excerpt and use in any way on any platform.
|
|
36
|
+
|
|
37
|
+
Placing this material on internal or public servers is
|
|
38
|
+
also encouraged. The compiler is not aware of any
|
|
39
|
+
export restrictions so freely distribute world-wide.
|
|
40
|
+
|
|
41
|
+
You can verify the public domain status by contacting
|
|
42
|
+
|
|
43
|
+
Grady Ward
|
|
44
|
+
3449 Martha Ct.
|
|
45
|
+
Arcata, CA 95521-4884
|
|
46
|
+
|
|
47
|
+
grady@netcom.com
|
|
48
|
+
grady@northcoast.com
|
|
49
|
+
|
|
50
|
+
The "UK English Wordlist With Frequency Classification" is also in the
|
|
51
|
+
Public Domain:
|
|
52
|
+
|
|
53
|
+
Date: Sat, 08 Jul 2000 20:27:21 +0100
|
|
54
|
+
From: Brian Kelk <Brian.Kelk@cl.cam.ac.uk>
|
|
55
|
+
|
|
56
|
+
> I was wondering what the copyright status of your "UK English
|
|
57
|
+
> Wordlist With Frequency Classification" word list as it seems to
|
|
58
|
+
> be lacking any copyright notice.
|
|
59
|
+
|
|
60
|
+
There were many many sources in total, but any text marked
|
|
61
|
+
"copyright" was avoided. Locally-written documentation was one
|
|
62
|
+
source. An earlier version of the list resided in a filespace called
|
|
63
|
+
PUBLIC on the University mainframe, because it was considered public
|
|
64
|
+
domain.
|
|
65
|
+
|
|
66
|
+
Date: Tue, 11 Jul 2000 19:31:34 +0100
|
|
67
|
+
|
|
68
|
+
> So are you saying your word list is also in the public domain?
|
|
69
|
+
|
|
70
|
+
That is the intention.
|
|
71
|
+
|
|
72
|
+
The 20 level includes frequency classes 7-15 from Brian's word list.
|
|
73
|
+
|
|
74
|
+
The 35 level includes frequency classes 2-6 and words appearing in at
|
|
75
|
+
least 11 of 12 dictionaries as indicated in the 12Dicts package. All
|
|
76
|
+
words from the 12Dicts package have had likely inflections added via
|
|
77
|
+
my inflection database.
|
|
78
|
+
|
|
79
|
+
The 12Dicts package and Supplement is in the Public Domain.
|
|
80
|
+
|
|
81
|
+
The WordNet database, which was used in the creation of the
|
|
82
|
+
Inflections database, is under the following copyright:
|
|
83
|
+
|
|
84
|
+
This software and database is being provided to you, the LICENSEE,
|
|
85
|
+
by Princeton University under the following license. By obtaining,
|
|
86
|
+
using and/or copying this software and database, you agree that you
|
|
87
|
+
have read, understood, and will comply with these terms and
|
|
88
|
+
conditions.:
|
|
89
|
+
|
|
90
|
+
Permission to use, copy, modify and distribute this software and
|
|
91
|
+
database and its documentation for any purpose and without fee or
|
|
92
|
+
royalty is hereby granted, provided that you agree to comply with
|
|
93
|
+
the following copyright notice and statements, including the
|
|
94
|
+
disclaimer, and that the same appear on ALL copies of the software,
|
|
95
|
+
database and documentation, including modifications that you make
|
|
96
|
+
for internal use or for distribution.
|
|
97
|
+
|
|
98
|
+
WordNet 1.6 Copyright 1997 by Princeton University. All rights
|
|
99
|
+
reserved.
|
|
100
|
+
|
|
101
|
+
THIS SOFTWARE AND DATABASE IS PROVIDED "AS IS" AND PRINCETON
|
|
102
|
+
UNIVERSITY MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
|
103
|
+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PRINCETON
|
|
104
|
+
UNIVERSITY MAKES NO REPRESENTATIONS OR WARRANTIES OF MERCHANT-
|
|
105
|
+
ABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE
|
|
106
|
+
LICENSED SOFTWARE, DATABASE OR DOCUMENTATION WILL NOT INFRINGE ANY
|
|
107
|
+
THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
|
|
108
|
+
|
|
109
|
+
The name of Princeton University or Princeton may not be used in
|
|
110
|
+
advertising or publicity pertaining to distribution of the software
|
|
111
|
+
and/or database. Title to copyright in this software, database and
|
|
112
|
+
any associated documentation shall at all times remain with
|
|
113
|
+
Princeton University and LICENSEE agrees to preserve same.
|
|
114
|
+
|
|
115
|
+
The 40 level includes words from Alan's 3esl list found in version 4.0
|
|
116
|
+
of his 12dicts package. Like his other stuff the 3esl list is also in the
|
|
117
|
+
public domain.
|
|
118
|
+
|
|
119
|
+
The 50 level includes Brian's frequency class 1, words appearing
|
|
120
|
+
in at least 5 of 12 of the dictionaries as indicated in the 12Dicts
|
|
121
|
+
package, and uppercase words in at least 4 of the previous 12
|
|
122
|
+
dictionaries. A decent number of proper names is also included: The
|
|
123
|
+
top 1000 male, female, and Last names from the 1990 Census report; a
|
|
124
|
+
list of names sent to me by Alan Beale; and a few names that I added
|
|
125
|
+
myself. Finally a small list of abbreviations not commonly found in
|
|
126
|
+
other word lists is included.
|
|
127
|
+
|
|
128
|
+
The name files form the Census report is a government document which I
|
|
129
|
+
don't think can be copyrighted.
|
|
130
|
+
|
|
131
|
+
The file special-jargon.50 uses common.lst and word.lst from the
|
|
132
|
+
"Unofficial Jargon File Word Lists" which is derived from "The Jargon
|
|
133
|
+
File". All of which is in the Public Domain. This file also contain
|
|
134
|
+
a few extra UNIX terms which are found in the file "unix-terms" in the
|
|
135
|
+
special/ directory.
|
|
136
|
+
|
|
137
|
+
The 55 level includes words from Alan's 2of4brif list found in version
|
|
138
|
+
4.0 of his 12dicts package. Like his other stuff the 2of4brif is also
|
|
139
|
+
in the public domain.
|
|
140
|
+
|
|
141
|
+
The 60 level includes all words appearing in at least 2 of the 12
|
|
142
|
+
dictionaries as indicated by the 12Dicts package.
|
|
143
|
+
|
|
144
|
+
The 70 level includes Brian's frequency class 0 and the 74,550 common
|
|
145
|
+
dictionary words from the MWords package. The common dictionary words,
|
|
146
|
+
like those from the 12Dicts package, have had all likely inflections
|
|
147
|
+
added. The 70 level also included the 5desk list from version 4.0 of
|
|
148
|
+
the 12Dics package which is in the public domain.
|
|
149
|
+
|
|
150
|
+
The 80 level includes the ENABLE word list, all the lists in the
|
|
151
|
+
ENABLE supplement package (except for ABLE), the "UK Advanced Cryptics
|
|
152
|
+
Dictionary" (UKACD), the list of signature words from the YAWL package,
|
|
153
|
+
and the 10,196 places list from the MWords package.
|
|
154
|
+
|
|
155
|
+
The ENABLE package, mainted by M\Cooper <thegrendel@theriver.com>,
|
|
156
|
+
is in the Public Domain:
|
|
157
|
+
|
|
158
|
+
The ENABLE master word list, WORD.LST, is herewith formally released
|
|
159
|
+
into the Public Domain. Anyone is free to use it or distribute it in
|
|
160
|
+
any manner they see fit. No fee or registration is required for its
|
|
161
|
+
use nor are "contributions" solicited (if you feel you absolutely
|
|
162
|
+
must contribute something for your own peace of mind, the authors of
|
|
163
|
+
the ENABLE list ask that you make a donation on their behalf to your
|
|
164
|
+
favorite charity). This word list is our gift to the Scrabble
|
|
165
|
+
community, as an alternate to "official" word lists. Game designers
|
|
166
|
+
may feel free to incorporate the WORD.LST into their games. Please
|
|
167
|
+
mention the source and credit us as originators of the list. Note
|
|
168
|
+
that if you, as a game designer, use the WORD.LST in your product,
|
|
169
|
+
you may still copyright and protect your product, but you may *not*
|
|
170
|
+
legally copyright or in any way restrict redistribution of the
|
|
171
|
+
WORD.LST portion of your product. This *may* under law restrict your
|
|
172
|
+
rights to restrict your users' rights, but that is only fair.
|
|
173
|
+
|
|
174
|
+
UKACD, by J Ross Beresford <ross@bryson.demon.co.uk>, is under the
|
|
175
|
+
following copyright:
|
|
176
|
+
|
|
177
|
+
Copyright (c) J Ross Beresford 1993-1999. All Rights Reserved.
|
|
178
|
+
|
|
179
|
+
The following restriction is placed on the use of this publication:
|
|
180
|
+
if The UK Advanced Cryptics Dictionary is used in a software package
|
|
181
|
+
or redistributed in any form, the copyright notice must be
|
|
182
|
+
prominently displayed and the text of this document must be included
|
|
183
|
+
verbatim.
|
|
184
|
+
|
|
185
|
+
There are no other restrictions: I would like to see the list
|
|
186
|
+
distributed as widely as possible.
|
|
187
|
+
|
|
188
|
+
The 95 level includes the 354,984 single words, 256,772 compound
|
|
189
|
+
words, 4,946 female names and the 3,897 male names, and 21,986 names
|
|
190
|
+
from the MWords package, ABLE.LST from the ENABLE Supplement, and some
|
|
191
|
+
additional words found in my part-of-speech database that were not
|
|
192
|
+
found anywhere else.
|
|
193
|
+
|
|
194
|
+
Accent information was taken from UKACD.
|
|
195
|
+
|
|
196
|
+
The VarCon package was used to create the American, British, Canadian,
|
|
197
|
+
and Australian word list. It is under the following copyright:
|
|
198
|
+
|
|
199
|
+
Copyright 2000-2016 by Kevin Atkinson
|
|
200
|
+
|
|
201
|
+
Permission to use, copy, modify, distribute and sell this array, the
|
|
202
|
+
associated software, and its documentation for any purpose is hereby
|
|
203
|
+
granted without fee, provided that the above copyright notice appears
|
|
204
|
+
in all copies and that both that copyright notice and this permission
|
|
205
|
+
notice appear in supporting documentation. Kevin Atkinson makes no
|
|
206
|
+
representations about the suitability of this array for any
|
|
207
|
+
purpose. It is provided "as is" without express or implied warranty.
|
|
208
|
+
|
|
209
|
+
Copyright 2016 by Benjamin Titze
|
|
210
|
+
|
|
211
|
+
Permission to use, copy, modify, distribute and sell this array, the
|
|
212
|
+
associated software, and its documentation for any purpose is hereby
|
|
213
|
+
granted without fee, provided that the above copyright notice appears
|
|
214
|
+
in all copies and that both that copyright notice and this permission
|
|
215
|
+
notice appear in supporting documentation. Benjamin Titze makes no
|
|
216
|
+
representations about the suitability of this array for any
|
|
217
|
+
purpose. It is provided "as is" without express or implied warranty.
|
|
218
|
+
|
|
219
|
+
Since the original words lists come from the Ispell distribution:
|
|
220
|
+
|
|
221
|
+
Copyright 1993, Geoff Kuenning, Granada Hills, CA
|
|
222
|
+
All rights reserved.
|
|
223
|
+
|
|
224
|
+
Redistribution and use in source and binary forms, with or without
|
|
225
|
+
modification, are permitted provided that the following conditions
|
|
226
|
+
are met:
|
|
227
|
+
|
|
228
|
+
1. Redistributions of source code must retain the above copyright
|
|
229
|
+
notice, this list of conditions and the following disclaimer.
|
|
230
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
231
|
+
notice, this list of conditions and the following disclaimer in the
|
|
232
|
+
documentation and/or other materials provided with the distribution.
|
|
233
|
+
3. All modifications to the source code must be clearly marked as
|
|
234
|
+
such. Binary redistributions based on modified source code
|
|
235
|
+
must be clearly marked as modified versions in the documentation
|
|
236
|
+
and/or other materials provided with the distribution.
|
|
237
|
+
(clause 4 removed with permission from Geoff Kuenning)
|
|
238
|
+
5. The name of Geoff Kuenning may not be used to endorse or promote
|
|
239
|
+
products derived from this software without specific prior
|
|
240
|
+
written permission.
|
|
241
|
+
|
|
242
|
+
THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS IS'' AND
|
|
243
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
244
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
245
|
+
ARE DISCLAIMED. IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS BE LIABLE
|
|
246
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
247
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
248
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
249
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
250
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
251
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
252
|
+
SUCH DAMAGE.
|