Pop-up dictionary browser extension for language learning. Successor to Yomichan. (PERSONAL FORK)
at lambda-fork/main 150 lines 5.9 kB view raw
1/* 2 * Copyright (C) 2023-2025 Yomitan Authors 3 * Copyright (C) 2020-2022 Yomichan Authors 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19import fs from 'fs'; 20import path from 'path'; 21import {performance} from 'perf_hooks'; 22import {fileURLToPath} from 'url'; 23import {getDictionaryArchiveEntries, getDictionaryArchiveJson, getIndexFileName, readArchiveEntryDataJson} from './dictionary-archive-util.js'; 24import {parseJson} from './json.js'; 25import {createJsonSchema} from './schema-validate.js'; 26import {toError} from './to-error.js'; 27 28const dirname = path.dirname(fileURLToPath(import.meta.url)); 29 30/** 31 * @param {string} relativeFileName 32 * @returns {import('dev/dictionary-validate').Schema} 33 */ 34function readSchema(relativeFileName) { 35 const fileName = path.join(dirname, relativeFileName); 36 const source = fs.readFileSync(fileName, {encoding: 'utf8'}); 37 return parseJson(source); 38} 39 40/** 41 * @param {import('dev/schema-validate').ValidateMode} mode 42 * @param {import('@zip.js/zip.js').Entry[]} entries 43 * @param {import('dev/dictionary-validate').SchemasDetails} schemasDetails 44 */ 45async function validateDictionaryBanks(mode, entries, schemasDetails) { 46 for (const entry of entries) { 47 const {filename} = entry; 48 for (const [fileNameFormat, schema] of schemasDetails) { 49 if (!fileNameFormat.test(filename)) { continue; } 50 51 let jsonSchema; 52 try { 53 jsonSchema = createJsonSchema(mode, schema); 54 } catch (e) { 55 const e2 = toError(e); 56 e2.message += `\n(in file ${filename})`; 57 throw e2; 58 } 59 60 const data = await readArchiveEntryDataJson(entry); 61 62 try { 63 jsonSchema.validate(data); 64 } catch (e) { 65 const e2 = toError(e); 66 e2.message += `\n(in file ${filename})`; 67 throw e2; 68 } 69 break; 70 } 71 } 72} 73 74/** 75 * Validates a dictionary from its zip archive. 76 * @param {import('dev/schema-validate').ValidateMode} mode 77 * @param {ArrayBuffer} archiveData 78 * @param {import('dev/dictionary-validate').Schemas} schemas 79 */ 80export async function validateDictionary(mode, archiveData, schemas) { 81 const entries = await getDictionaryArchiveEntries(archiveData); 82 const indexFileName = getIndexFileName(); 83 /** @type {import('dictionary-data').Index} */ 84 const index = await getDictionaryArchiveJson(entries, indexFileName); 85 const version = index.format || index.version; 86 87 try { 88 const jsonSchema = createJsonSchema(mode, schemas.index); 89 jsonSchema.validate(index); 90 } catch (e) { 91 const e2 = toError(e); 92 e2.message += `\n(in file ${indexFileName})`; 93 throw e2; 94 } 95 96 /** @type {import('dev/dictionary-validate').SchemasDetails} */ 97 const schemasDetails = [ 98 [/^term_bank_(\d+)\.json$/, version === 1 ? schemas.termBankV1 : schemas.termBankV3], 99 [/^term_meta_bank_(\d+)\.json$/, schemas.termMetaBankV3], 100 [/^kanji_bank_(\d+)\.json$/, version === 1 ? schemas.kanjiBankV1 : schemas.kanjiBankV3], 101 [/^kanji_meta_bank_(\d+)\.json$/, schemas.kanjiMetaBankV3], 102 [/^tag_bank_(\d+)\.json$/, schemas.tagBankV3], 103 ]; 104 105 await validateDictionaryBanks(mode, entries, schemasDetails); 106} 107 108/** 109 * Returns a Schemas object from ext/data/schemas/*. 110 * @returns {import('dev/dictionary-validate').Schemas} 111 */ 112export function getSchemas() { 113 return { 114 index: readSchema('../ext/data/schemas/dictionary-index-schema.json'), 115 kanjiBankV1: readSchema('../ext/data/schemas/dictionary-kanji-bank-v1-schema.json'), 116 kanjiBankV3: readSchema('../ext/data/schemas/dictionary-kanji-bank-v3-schema.json'), 117 kanjiMetaBankV3: readSchema('../ext/data/schemas/dictionary-kanji-meta-bank-v3-schema.json'), 118 tagBankV3: readSchema('../ext/data/schemas/dictionary-tag-bank-v3-schema.json'), 119 termBankV1: readSchema('../ext/data/schemas/dictionary-term-bank-v1-schema.json'), 120 termBankV3: readSchema('../ext/data/schemas/dictionary-term-bank-v3-schema.json'), 121 termMetaBankV3: readSchema('../ext/data/schemas/dictionary-term-meta-bank-v3-schema.json'), 122 }; 123} 124 125/** 126 * Validates dictionary files and logs the results to the console. 127 * @param {import('dev/schema-validate').ValidateMode} mode 128 * @param {string[]} dictionaryFileNames 129 */ 130export async function testDictionaryFiles(mode, dictionaryFileNames) { 131 const schemas = getSchemas(); 132 133 for (const dictionaryFileName of dictionaryFileNames) { 134 // eslint-disable-next-line no-restricted-syntax 135 const start = performance.now(); 136 try { 137 console.log(`Validating ${dictionaryFileName}...`); 138 const source = fs.readFileSync(dictionaryFileName); 139 await validateDictionary(mode, source.buffer, schemas); 140 // eslint-disable-next-line no-restricted-syntax 141 const end = performance.now(); 142 console.log(`No issues detected (${((end - start) / 1000).toFixed(2)}s)`); 143 } catch (e) { 144 // eslint-disable-next-line no-restricted-syntax 145 const end = performance.now(); 146 console.log(`Encountered an error (${((end - start) / 1000).toFixed(2)}s)`); 147 console.warn(e); 148 } 149 } 150}