lint-wiki-dumps 0.1.0 → 0.3.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 CHANGED
@@ -28,7 +28,7 @@ npx lint-wiki-dumps <language> <path to download> [path to HTML output]
28
28
  npx lint-wiki-dumps zh-yue ~/Downloads/dumps
29
29
  ```
30
30
 
31
- or execute the Bash script `scan.sh` (single thread) or `scan-parallel.sh` (multi-core cluster) directly:
31
+ or execute the Bash script `scan.sh` directly:
32
32
 
33
33
  ```sh
34
34
  bash scan.sh <language> <path to download> [path to HTML output]
package/download.sh ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/local/bin/bash
2
+ path="https://dumps.wikimedia.org/$1/latest/"
3
+ files=$( \
4
+ curl -s "$path" \
5
+ | grep -o "href=\"$1-latest-pages-articles[0-9].*\.bz2\">" \
6
+ | gsed "s|href=\"|$path|;s|\">||" \
7
+ )
8
+ filtered=$(node filter.js $files)
9
+ if (( ${#filtered} < 2 ))
10
+ then
11
+ file="$path/$1-latest-pages-articles.xml.bz2"
12
+ curl --output-dir "$2" -O "$file"
13
+ exit 1
14
+ else
15
+ curl --output-dir "$2" --remote-name-all $filtered
16
+ fi
package/filter.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const files = process.argv.slice(2)
4
+ .map(file => [
5
+ file,
6
+ .../\.xml-p(\d+)p(\d+)\.bz2$/u.exec(file).slice(1).map(Number),
7
+ ])
8
+ .sort(([, a1, a2], [, b1, b2]) => a1 - b1 || a2 - b2)
9
+ .filter(([, a], i, arr) => a !== arr[i + 1]?.[1])
10
+ .map(([file]) => file)
11
+ .join(' ');
12
+ console.log(files);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-wiki-dumps",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Lint Wikipedia dumps",
5
5
  "keywords": [
6
6
  "lint",
@@ -13,29 +13,31 @@
13
13
  "license": "GPL-3.0",
14
14
  "author": "Bhsd",
15
15
  "files": [
16
- "scan.sh",
16
+ "*.sh",
17
+ "!bump.sh",
17
18
  "*.js",
18
19
  "reports/*.html",
19
20
  "reports/dist/",
20
21
  "reports/*.css"
21
22
  ],
22
23
  "bin": {
23
- "lint-wiki-dumps": "scan-parallel.sh"
24
+ "lint-wiki-dumps": "scan.sh"
24
25
  },
25
26
  "repository": {
26
27
  "type": "git",
27
28
  "url": "git+https://github.com/bhsd-harry/lint-wiki-dumps.git"
28
29
  },
29
30
  "scripts": {
31
+ "start": "node server.js",
30
32
  "prepublishOnly": "npm run build",
31
- "build": "tsc && mv dist/* . && tsc --project reports/tsconfig.json",
33
+ "build": "tsc && mv dist/* . && esbuild reports/src/*.ts --charset=utf8 --bundle --minify --target=es2019 --sourcemap --outdir=reports/dist && rm reports/dist/common.js*",
32
34
  "lint": "tsc --noEmit && tsc --project reports/tsconfig.json --noEmit && eslint --cache ."
33
35
  },
34
36
  "dependencies": {
35
- "@bhsd/common": "^0.9.1",
37
+ "@bhsd/common": "^0.9.3",
36
38
  "chalk": "^4.1.2",
37
39
  "unbzip2-stream": "^1.4.3",
38
- "wikilint": "^2.18.4",
40
+ "wikilint": "^2.20.0",
39
41
  "xml-stream": "^0.4.5"
40
42
  },
41
43
  "optionalDependencies": {
@@ -47,9 +49,9 @@
47
49
  "@types/mocha": "^10.0.10",
48
50
  "@types/node": "^22.13.1",
49
51
  "@types/unbzip2-stream": "^1.4.3",
50
- "@typescript-eslint/eslint-plugin": "^8.23.0",
51
- "@typescript-eslint/parser": "^8.23.0",
52
- "esbuild": "^0.25.0",
52
+ "@typescript-eslint/eslint-plugin": "^8.29.0",
53
+ "@typescript-eslint/parser": "^8.29.0",
54
+ "esbuild": "^0.25.2",
53
55
  "eslint": "^8.57.1",
54
56
  "eslint-plugin-es-x": "^8.4.1",
55
57
  "eslint-plugin-eslint-comments": "^3.2.0",
@@ -63,7 +65,7 @@
63
65
  "http-server": "^14.1.1",
64
66
  "mocha": "^11.1.0",
65
67
  "stylelint": "^16.14.1",
66
- "typescript": "^5.7.3"
68
+ "typescript": "^5.8.2"
67
69
  },
68
70
  "engines": {
69
71
  "node": ">=18.17.0"
@@ -9,11 +9,21 @@ const path_1 = __importDefault(require("path"));
9
9
  const os_1 = __importDefault(require("os"));
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
11
  const common_1 = require("@bhsd/common");
12
+ const util_1 = require("./util");
12
13
  const processor_1 = require("./processor");
13
- const [, , site, dir] = process.argv, target = `${site}wiki`;
14
+ const [, , site, dir, refresh] = process.argv, target = (0, util_1.normalize)(site);
14
15
  if (cluster_1.default.isPrimary) {
15
- (0, processor_1.init)();
16
- const dumpDir = dir.replace(/^~/u, os_1.default.homedir()), prefix = target.replaceAll('-', '_'), files = fs_1.default.readdirSync(dumpDir).filter(file => file.endsWith('.bz2') && file.startsWith(prefix))
16
+ (0, util_1.init)();
17
+ const tempFiles = [];
18
+ for (const file of fs_1.default.readdirSync(util_1.resultDir)) {
19
+ if (file.startsWith(`${target}-p`) && file.endsWith('.json')) {
20
+ const oldName = path_1.default.join(util_1.resultDir, file), newName = path_1.default.join(util_1.resultDir, `temp${file.slice(target.length)}`);
21
+ (0, util_1.reading)(oldName);
22
+ tempFiles.push(newName);
23
+ fs_1.default.renameSync(oldName, newName);
24
+ }
25
+ }
26
+ const dumpDir = (0, util_1.replaceTilde)(dir), prefix = `${target}wiki`, files = fs_1.default.readdirSync(dumpDir).filter(file => file.endsWith('.bz2') && file.startsWith(prefix))
17
27
  .map(file => {
18
28
  const filePath = path_1.default.join(dumpDir, file);
19
29
  return [filePath, fs_1.default.statSync(filePath).size];
@@ -22,40 +32,51 @@ if (cluster_1.default.isPrimary) {
22
32
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
23
33
  workers = new Array(Math.min(os_1.default.availableParallelism(), files.length)).fill(undefined)
24
34
  .map(() => cluster_1.default.fork());
25
- let i = 0, n = 0;
35
+ let i = 0, n = 0, m = 0;
26
36
  console.time('parse');
27
37
  for (; i < workers.length; i++) {
28
38
  const worker = workers[i];
29
- worker.on('message', count => {
39
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
40
+ worker.on('message', ([count, total]) => {
30
41
  n += count;
42
+ m += total;
31
43
  if (i < files.length) {
32
- worker.send([files[i], i]);
44
+ worker.send(files[i][0]);
33
45
  i++;
34
46
  }
35
47
  else {
36
48
  worker.disconnect();
37
49
  }
38
- }).send([files[i], i]);
50
+ }).send(files[i][0]);
39
51
  }
40
52
  process.on('exit', () => {
41
53
  console.timeEnd('parse');
42
- console.log(chalk_1.default.green(`Parsed ${n} pages in total`));
54
+ console.log(chalk_1.default.green(`Parsed ${n} / ${m} pages in total`));
55
+ for (const file of tempFiles) {
56
+ fs_1.default.unlinkSync(file);
57
+ }
43
58
  });
44
59
  }
45
60
  else {
46
- process.on('message', ([[file], j]) => {
47
- const results = fs_1.default.createWriteStream(path_1.default.join(processor_1.resultDir, `${site}-${j}.json`)), processor = new processor_1.Processor(site, results);
61
+ const getStartEnd = (f) => {
62
+ const p2 = f.lastIndexOf('p');
63
+ return [Number(f.slice(6, p2)), Number(f.slice(p2 + 1, -5))];
64
+ };
65
+ const tempFiles = fs_1.default.readdirSync(util_1.resultDir)
66
+ .filter(file => file.startsWith('temp-p') && file.endsWith('.json')), ranges = tempFiles.map(getStartEnd), max = Math.max(...ranges.map(([, end]) => end));
67
+ let start, end, last, data;
68
+ process.on('message', (file) => {
69
+ const filename = `${target}${file.slice(file.lastIndexOf('-'), -4)}.json`, tempPath = (0, util_1.getTempPath)(filename), results = (0, util_1.getWriteStream)(tempPath, () => {
70
+ fs_1.default.renameSync(tempPath, path_1.default.join(util_1.resultDir, filename));
71
+ process.send([processor.parsed, i]);
72
+ }), processor = new processor_1.Processor(site, results, refresh);
48
73
  let i = 0;
49
- results.write('{');
50
- results.on('close', () => {
51
- process.send(i);
52
- });
53
74
  const stop = () => {
54
- processor.stop(`parse ${file}`, `Parsed ${i} pages from ${file}`);
75
+ processor.stop(`parse ${file}`, `${i} pages from ${file}`);
55
76
  };
56
77
  const lint = ($text, ns, title, date, retry = 0) => {
57
78
  try {
58
- processor.lint($text, ns, title, date);
79
+ processor.lint($text, ns, title, date, last, data);
59
80
  return true;
60
81
  }
61
82
  catch (e) {
@@ -78,10 +99,24 @@ else {
78
99
  }
79
100
  };
80
101
  console.time(`parse ${file}`);
81
- const stream = (0, processor_1.getXmlStream)(file);
82
- stream.on('endElement: page', ({ title, ns, revision: { model, timestamp, text: { $text } } }) => {
83
- if (model === 'wikitext' && $text && ns === '0') {
102
+ const stream = (0, util_1.getXmlStream)(file);
103
+ stream.on('endElement: page', ({ title, ns, id, revision: { model, timestamp, text: { $text } } }) => {
104
+ if ((0, util_1.isArticle)($text, ns, model)) {
84
105
  (0, common_1.refreshStdout)(`${i++} ${title}`);
106
+ const pageid = Number(id);
107
+ if (start === undefined || end === undefined || pageid < start || pageid > end) {
108
+ const cur = pageid <= max && ranges.findIndex(([a, b]) => a <= pageid && b >= pageid);
109
+ if (cur === false || cur === -1) {
110
+ start = undefined;
111
+ end = undefined;
112
+ last = undefined;
113
+ }
114
+ else {
115
+ [start, end] = ranges[cur];
116
+ data = fs_1.default.readFileSync(path_1.default.join(util_1.resultDir, tempFiles[cur]), 'utf8');
117
+ last = (0, util_1.getTimestamp)(data);
118
+ }
119
+ }
85
120
  lint($text, ns, title, new Date(timestamp));
86
121
  }
87
122
  });
package/parser.js CHANGED
@@ -5,64 +5,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const fs_1 = __importDefault(require("fs"));
7
7
  const path_1 = __importDefault(require("path"));
8
- const os_1 = __importDefault(require("os"));
9
8
  const common_1 = require("@bhsd/common");
9
+ const util_1 = require("./util");
10
10
  const processor_1 = require("./processor");
11
- const n = Number(process.argv[4]) || Infinity, [, , site, file, , restart] = process.argv, filePath = path_1.default.join(processor_1.resultDir, `${site}.json`), data = fs_1.default.existsSync(filePath) && fs_1.default.readFileSync(filePath, 'utf8');
12
- const getTimestamp = () => {
13
- if (!data) {
14
- return undefined;
15
- }
16
- const i = data.indexOf('"#timestamp": "') + 15;
17
- return data.slice(i, data.indexOf('"', i));
18
- };
19
- const getErrors = (page) => {
20
- if (!data) {
21
- return undefined;
22
- }
23
- const str = JSON.stringify(page), i = data.indexOf(`${str}: [`);
24
- if (i === -1) {
25
- return undefined;
26
- }
27
- const j = i + str.length + 2;
28
- return JSON.parse(data.slice(j, data.indexOf('\n]', j) + 2));
29
- };
30
- (0, processor_1.init)();
31
- const time = getTimestamp(), last = time && new Date(time), results = fs_1.default.createWriteStream(path_1.default.join(processor_1.resultDir, `${site}.json`), { flags: restart ? 'a' : 'w' }), processor = new processor_1.Processor(site, results, last);
32
- let i = 0, stopping = false, restarted = !restart;
33
- if (!restart) {
34
- results.write('{');
11
+ const [, , site, file, refresh] = process.argv, filename = `${(0, util_1.normalize)(site)}.json`, filePath = path_1.default.join(util_1.resultDir, filename), tempPath = (0, util_1.getTempPath)(filename), data = fs_1.default.existsSync(filePath) && fs_1.default.readFileSync(filePath, 'utf8');
12
+ if (data) {
13
+ (0, util_1.reading)(filePath);
35
14
  }
36
- results.on('close', () => {
15
+ (0, util_1.init)();
16
+ const last = (0, util_1.getTimestamp)(data), results = (0, util_1.getWriteStream)(tempPath, () => {
17
+ fs_1.default.renameSync(tempPath, filePath);
37
18
  process.exit(); // eslint-disable-line n/no-process-exit
38
- });
39
- const stop = () => {
40
- stopping = true;
41
- processor.stop('parse', `Parsed ${i} pages`);
42
- };
19
+ }), processor = new processor_1.Processor(site, results, refresh, last);
20
+ let i = 0;
43
21
  console.time('parse');
44
- const stream = (0, processor_1.getXmlStream)(file.replace(/^~/u, os_1.default.homedir()));
22
+ const stream = (0, util_1.getXmlStream)((0, util_1.replaceTilde)(file));
45
23
  stream.on('endElement: page', ({ title, ns, revision: { model, timestamp, text: { $text } } }) => {
46
- if (i === n) {
47
- if (!stopping) {
48
- stop();
49
- }
50
- }
51
- else if (restarted && model === 'wikitext' && $text && ns === '0') {
24
+ if ((0, util_1.isArticle)($text, ns, model)) {
52
25
  (0, common_1.refreshStdout)(`${i++} ${title}`);
53
- const date = new Date(timestamp);
54
- if (last && date <= last) {
55
- const previous = getErrors(title);
56
- if (previous) {
57
- processor.newEntry(title, previous);
58
- }
59
- }
60
- else {
61
- processor.lint($text, ns, title, date);
62
- }
63
- }
64
- else if (title === restart) {
65
- restarted = true;
26
+ processor.lint($text, ns, title, new Date(timestamp), last, data);
66
27
  }
67
28
  });
68
- stream.on('end', stop);
29
+ stream.on('end', () => {
30
+ processor.stop('parse', `${i} pages`);
31
+ });
package/processor.js CHANGED
@@ -3,56 +3,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Processor = exports.getXmlStream = exports.init = exports.resultDir = exports.MAX = void 0;
7
- const fs_1 = __importDefault(require("fs"));
8
- const path_1 = __importDefault(require("path"));
9
- const perf_hooks_1 = require("perf_hooks");
6
+ exports.Processor = void 0;
10
7
  const cluster_1 = __importDefault(require("cluster"));
11
8
  const chalk_1 = __importDefault(require("chalk"));
12
- const unbzip2_stream_1 = __importDefault(require("unbzip2-stream"));
13
- const xml_stream_1 = __importDefault(require("xml-stream"));
14
9
  const wikilint_1 = __importDefault(require("wikilint"));
15
- exports.MAX = 100, exports.resultDir = path_1.default.join(__dirname, 'results');
16
- const ignore = new Set(['no-arg', 'url-encoding', 'h1', 'var-anchor']);
17
- const init = () => {
18
- if (!fs_1.default.existsSync(exports.resultDir)) {
19
- fs_1.default.mkdirSync(exports.resultDir);
20
- }
21
- };
22
- exports.init = init;
23
- const getXmlStream = (file) => {
24
- const stream = new xml_stream_1.default(fs_1.default.createReadStream(file).pipe((0, unbzip2_stream_1.default)()));
25
- stream.preserve('text', true);
26
- return stream;
27
- };
28
- exports.getXmlStream = getXmlStream;
10
+ const util_1 = require("./util");
29
11
  class Processor {
12
+ parsed = 0;
30
13
  #failed = 0;
31
14
  #comma = '';
32
- #worst;
33
15
  #results;
16
+ #refresh;
34
17
  #latest;
35
18
  /** @param site site nickname */
36
- constructor(site, results, latest) {
19
+ constructor(site, results, refresh, latest) {
37
20
  wikilint_1.default.config = `${site}wiki`;
38
21
  this.#results = results;
22
+ this.#refresh = Boolean(refresh);
39
23
  this.#latest = latest;
40
24
  }
41
25
  /**
42
26
  * Stop the processing and log the results.
43
27
  * @param timer timer name
44
- * @param msg message to log
28
+ * @param msg additional message to log
45
29
  */
46
- stop(timer, msg) {
30
+ stop(timer, msg = '') {
47
31
  console.log();
48
32
  console.timeEnd(timer);
49
- console.log(chalk_1.default.green(msg));
33
+ console.log(chalk_1.default.green(`Parsed ${this.parsed} / ${msg}`));
50
34
  if (this.#failed) {
51
35
  console.error(chalk_1.default.red(`${this.#failed} pages failed to parse`));
52
36
  }
53
- if (this.#worst) {
54
- console.info(chalk_1.default.yellow(`Worst page: ${this.#worst.title} (${this.#worst.duration.toFixed(3)} ms)`));
55
- }
56
37
  this.#results.write(`${this.#comma}\n"#timestamp": ${JSON.stringify(this.#latest)}\n}`);
57
38
  this.#results.end();
58
39
  }
@@ -62,7 +43,7 @@ class Processor {
62
43
  * @param errors lint errors
63
44
  */
64
45
  newEntry(title, errors) {
65
- this.#results.write(`${this.#comma}\n${JSON.stringify(title)}: ${JSON.stringify(errors, null, '\t')}`);
46
+ this.#results.write(`${this.#comma}\n${JSON.stringify(title)}: ${typeof errors === 'string' ? errors : JSON.stringify(errors, null, '\t')}`);
66
47
  this.#comma ||= ',';
67
48
  }
68
49
  /**
@@ -71,30 +52,29 @@ class Processor {
71
52
  * @param ns page namespace
72
53
  * @param title page title
73
54
  * @param date page revision date
55
+ * @param last last revision date
56
+ * @param data previous results
74
57
  * @throws `RangeError` maximum heap size exceeded
75
58
  */
76
- lint($text, ns, title, date) {
59
+ lint($text, ns, title, date, last, data) {
77
60
  if (!this.#latest || date > this.#latest) {
78
61
  this.#latest = date;
79
62
  }
63
+ if (last && date <= last) {
64
+ const previous = (0, util_1.getErrors)(data, title);
65
+ if (!previous) {
66
+ return;
67
+ }
68
+ else if (!this.#refresh) {
69
+ this.newEntry(title, previous);
70
+ return;
71
+ }
72
+ }
80
73
  try {
81
- const start = perf_hooks_1.performance.now(), errors = wikilint_1.default.parse($text, ns === '828').lint()
82
- .filter(({ severity, rule }) => severity === 'error' && !ignore.has(rule)), duration = perf_hooks_1.performance.now() - start;
74
+ const errors = (0, util_1.lint)($text, ns);
75
+ this.parsed++;
83
76
  if (errors.length > 0) {
84
- this.newEntry(title, errors.map(({ severity, suggestions, fix, ...e }) => ({
85
- ...e,
86
- ...suggestions && {
87
- suggestions: suggestions.map(action => ({
88
- ...action,
89
- original: $text.slice(...action.range),
90
- })),
91
- },
92
- ...fix && { fix: { ...fix, original: $text.slice(...fix.range) } },
93
- excerpt: $text.slice(e.startIndex, e.endIndex).slice(0, exports.MAX),
94
- })));
95
- }
96
- if (!this.#worst || duration > this.#worst.duration) {
97
- this.#worst = { title, duration };
77
+ this.newEntry(title, errors);
98
78
  }
99
79
  }
100
80
  catch (e) {
package/report.js CHANGED
@@ -5,9 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const fs_1 = __importDefault(require("fs"));
7
7
  const path_1 = __importDefault(require("path"));
8
- const crypto_1 = require("crypto");
9
8
  const chalk_1 = __importDefault(require("chalk"));
10
- const processor_1 = require("./processor");
9
+ const util_1 = require("./util");
11
10
  const { argv } = process, [, , lang] = argv, defaultOurDir = path_1.default.join(__dirname, 'reports');
12
11
  let [, , , outDir] = argv;
13
12
  const mkdir = (dir, empty) => {
@@ -30,15 +29,15 @@ else {
30
29
  const dataDir = path_1.default.join(outDir, 'data');
31
30
  mkdir(dataDir);
32
31
  const writeJS = (data, file) => {
33
- fs_1.default.writeFileSync(path_1.default.join(dataDir, `${file}.js`), `window.data=${JSON.stringify(data)}`);
32
+ (0, util_1.write)(path_1.default.join(dataDir, `${file}.js`), data);
34
33
  };
35
34
  const initJS = (file) => {
36
35
  const stream = fs_1.default.createWriteStream(`${file}.js`);
37
- stream.write('window.data={"articles":[');
36
+ stream.write('globalThis.data={"articles":[');
38
37
  return stream;
39
38
  };
40
39
  const compare = (a, b) => a.localeCompare(b);
41
- const resultDir = path_1.default.join(__dirname, 'results'), dir = fs_1.default.readdirSync(resultDir), summary = new Set(), ruleRecords = new Map(), wiki = {}, siteDir = path_1.default.join(dataDir, lang), articlesDir = path_1.default.join(siteDir, 'pages');
40
+ const dir = fs_1.default.readdirSync(util_1.resultDir), summary = new Set(), ruleRecords = new Map(), wiki = {}, siteDir = path_1.default.join(dataDir, lang), articlesDir = path_1.default.join(siteDir, 'pages');
42
41
  let latest;
43
42
  mkdir(siteDir, true);
44
43
  mkdir(articlesDir);
@@ -46,12 +45,13 @@ for (const file of dir) {
46
45
  if (!file.endsWith('.json')) {
47
46
  continue;
48
47
  }
49
- const fileDir = path_1.default.join(resultDir, file);
48
+ const fileDir = path_1.default.join(util_1.resultDir, file);
50
49
  if (!fs_1.default.existsSync(fileDir)) {
51
50
  console.error(chalk_1.default.red(`Failed to read ${file}`));
52
51
  continue;
53
52
  }
54
- const k = file.search(/-\d+\.json$/u), site = k === -1 ? file.slice(0, -5) : file.slice(0, k);
53
+ const k = file.search(/-(?:p\d+){2}\.json$/u), site = (k === -1 ? file.slice(0, -5) : file.slice(0, k))
54
+ .replaceAll('_', '-');
55
55
  summary.add(site);
56
56
  if (lang !== site) {
57
57
  continue;
@@ -59,8 +59,7 @@ for (const file of dir) {
59
59
  const data = fs_1.default.readFileSync(fileDir, 'utf8'), date = new Date(data.substr(data.indexOf('\n"#timestamp": "') + 16, 10));
60
60
  latest = !latest || date > latest ? date : latest;
61
61
  for (const mt of data.matchAll(/^(".+"): \[$/gmu)) {
62
- const page = JSON.parse(mt[1]), hash = (0, crypto_1.createHash)('sha256').update(page).digest('hex')
63
- .slice(0, 8), errors = JSON.parse(data.slice(mt.index + mt[0].length - 1, data.indexOf('\n]', mt.index) + 2)), rules = new Set(), info = [];
62
+ const page = JSON.parse(mt[1]), errors = JSON.parse(data.slice(mt.index + mt[0].length - 1, data.indexOf('\n]', mt.index) + 2)), rules = new Set(), info = [];
64
63
  for (const { rule, startLine, startCol, message, excerpt } of errors) {
65
64
  // article
66
65
  const line = startLine + 1, col = startCol + 1;
@@ -82,10 +81,10 @@ for (const file of dir) {
82
81
  ruleRecords.set(rule, ruleRecord);
83
82
  }
84
83
  ruleRecord[1].push(page);
85
- ruleRecord[0] += `${JSON.stringify([page, line, col, message, excerpt.slice(0, processor_1.MAX * 0.8)], null, '\t')},`;
84
+ ruleRecord[0] += `${JSON.stringify([page, line, col, message, excerpt.slice(0, util_1.MAX * 0.8)], null, '\t')},`;
86
85
  }
87
86
  }
88
- writeJS(info, path_1.default.join(site, 'pages', hash));
87
+ writeJS(info, (0, util_1.getHash)(site, page));
89
88
  }
90
89
  }
91
90
  const timestamp = latest.toISOString().slice(0, 10);
@@ -4,6 +4,7 @@
4
4
  <title>Diagnostic Report for Wikipedia</title>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0, width=device-width">
7
+ <link rel="icon" href="data:image/png;base64,iVBORw0KGgo=">
7
8
  <link rel="stylesheet" href="reports.css">
8
9
  <style>td:nth-child(-n+3){text-align:center}</style>
9
10
  <script defer="" src="dist/article.js"></script>
@@ -18,12 +19,16 @@
18
19
  <div>
19
20
  <a id="wiki" href="./wiki.html">Wikipedia</a>
20
21
  </div>
22
+ <div>
23
+ <a id="purge" href="#">Refresh</a>
24
+ </div>
21
25
  </div>
22
26
  <h2>
23
27
  Diagnostic Report for
24
28
  <a id="article">Article</a>
29
+ [<a id="edit">Edit</a>]
25
30
  </h2>
26
- <table style="table-layout:fixed;width:100%">
31
+ <table style="table-layout:fixed;width:100%;min-width:900px">
27
32
  <colgroup>
28
33
  <col span="1">
29
34
  <col span="1" style="width:calc(1.6em + 7ch)">
@@ -1,30 +1,2 @@
1
- "use strict";
2
- (async () => {
3
- const search = new URLSearchParams(location.search), page = search.get('page'), lang = search.get('lang'), buffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(page)), hash = [...new Uint8Array(buffer)].slice(0, 4)
4
- .map(b => b.toString(16).padStart(2, '0'))
5
- .join(''), title = document.querySelector('title'), h2 = document.getElementById('article'), wiki = document.getElementById('wiki'), tbody = document.querySelector('tbody'), script = document.createElement('script');
6
- title.textContent = title.textContent.replace('Wikipedia', page);
7
- h2.textContent = page;
8
- h2.href = `https://${lang}.wikipedia.org/wiki/${encodeURIComponent(page)}?redirect=no`;
9
- wiki.textContent = `${lang}wiki`;
10
- wiki.href += `?lang=${lang}`;
11
- script.src = `./data/${lang}/pages/${hash}.js`;
12
- script.addEventListener('load', () => {
13
- for (const entry of data) {
14
- const [rule, startLine, startCol, message, excerpt] = entry, tr = document.createElement('tr'), description = document.createElement('td'), line = document.createElement('td'), column = document.createElement('td'), detail = document.createElement('td'), notice = document.createElement('td'), descriptionLink = document.createElement('a');
15
- descriptionLink.textContent = rule;
16
- descriptionLink.href = `./rule.html?lang=${lang}&rule=${rule}`;
17
- description.className = 'excerpt';
18
- description.append(descriptionLink);
19
- line.textContent = String(startLine);
20
- column.textContent = String(startCol);
21
- detail.textContent = message;
22
- detail.className = 'excerpt';
23
- notice.textContent = excerpt;
24
- notice.className = 'excerpt mono';
25
- tr.append(description, line, column, detail, notice);
26
- tbody.append(tr);
27
- }
28
- });
29
- document.head.append(script);
30
- })();
1
+ "use strict";(()=>{var p=(n,e)=>{n.href=e,e.startsWith("https://")&&(n.target="_blank",n.rel="noopener")},g=(n,e)=>{let t=document.createElement("script");t.src=n,t.addEventListener("load",e),document.head.append(t)},u=(n,e)=>{let t=document.querySelector(n);return t.textContent=t.textContent.replace("Wikipedia",e),t},l=(n,e,t)=>{let r=document.getElementById(n);typeof e=="function"?r.href=e(r.href):e?p(r,e):r.removeAttribute("href"),t!==void 0&&(r.textContent=t)},E=(n,e,t,r)=>{let i=document.createElement(n),a=document.createElement("a");return a.textContent=e,p(a,t),i.append(a),r&&(i.className=r),i},c=(n,e)=>{let t=document.createElement("td");return t.textContent=n,e&&(t.className=e),t},f=(...n)=>{let e=document.createElement("tr");e.append(...n),document.querySelector("tbody").append(e)},L=(n,e,t,r)=>[c(String(n)),c(String(e)),c(t,"excerpt"),c(r,"excerpt mono")];(async()=>{let n=new URLSearchParams(location.search),e=n.get("page"),t=n.get("lang"),r=n.get("timestamp"),i=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e)),a=[...new Uint8Array(i)].slice(0,4).map(o=>o.toString(16).padStart(2,"0")).join(""),m=`https://${t==="mediawiki"?"www.mediawiki.org":`${t}.wikipedia.org`}/wiki/${encodeURIComponent(e)}`,s=document.getElementById("purge");u("title",e),l("article",`${m}?redirect=no`,e),l("edit",`${m}?action=edit`),l("wiki",o=>`${o}?lang=${t}`,`${t}wiki`),s.addEventListener("click",()=>{s.style.pointerEvents="none",(async()=>{let o=await fetch(`./purge/${t}/${encodeURIComponent(e.replace(/ /gu,"_"))}`);if(o.ok){let{timestamp:d}=await o.json();n.set("timestamp",d),location.href=`./article.html?${n}`}else s.style.pointerEvents=""})()}),g(`./data/${t}/pages/${a}.js${r?`?timestamp=${r}`:""}`,()=>{if(data.length===0)s.style.display="none";else for(let[o,d,w,$,h]of data)f(E("td",o,`./rule.html?lang=${t}&rule=${o}`,"excerpt"),...L(d,w,$,h))})})();})();
2
+ //# sourceMappingURL=article.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/common.ts", "../src/article.ts"],
4
+ "sourcesContent": ["const armorLink = (a: HTMLAnchorElement, href: string): void => {\n\ta.href = href;\n\tif (href.startsWith('https://')) {\n\t\ta.target = '_blank';\n\t\ta.rel = 'noopener';\n\t}\n};\n\nexport const load = (src: string, callback: () => void): void => {\n\tconst script = document.createElement('script');\n\tscript.src = src;\n\tscript.addEventListener('load', callback);\n\tdocument.head.append(script);\n};\n\nexport const update = (tag: string, replace: string): Element => {\n\tconst ele = document.querySelector(tag)!;\n\tele.textContent = ele.textContent!.replace('Wikipedia', replace);\n\treturn ele;\n};\n\nexport const updateLink = (id: string, href: string | ((s: string) => string) | false, text?: string): void => {\n\tconst a = document.getElementById(id) as HTMLAnchorElement;\n\tif (typeof href === 'function') {\n\t\ta.href = href(a.href);\n\t} else if (href) {\n\t\tarmorLink(a, href);\n\t} else {\n\t\ta.removeAttribute('href');\n\t}\n\tif (text !== undefined) {\n\t\ta.textContent = text;\n\t}\n};\n\nexport const addLink = (tag: string, text: string, href: string, className?: string): HTMLElement => {\n\tconst container = document.createElement(tag),\n\t\ta = document.createElement('a');\n\ta.textContent = text;\n\tarmorLink(a, href);\n\tcontainer.append(a);\n\tif (className) {\n\t\tcontainer.className = className;\n\t}\n\treturn container;\n};\n\nexport const createTd = (text: string, className?: string): HTMLTableCellElement => {\n\tconst td = document.createElement('td');\n\ttd.textContent = text;\n\tif (className) {\n\t\ttd.className = className;\n\t}\n\treturn td;\n};\n\nexport const insertRow = (...tds: HTMLElement[]): void => {\n\tconst tr = document.createElement('tr');\n\ttr.append(...tds);\n\tdocument.querySelector('tbody')!.append(tr);\n};\n\nexport const getErrorInfo = (\n\tstartLine: number,\n\tstartCol: number,\n\tmessage: string,\n\texcerpt: string,\n): [HTMLTableCellElement, HTMLTableCellElement, HTMLTableCellElement, HTMLTableCellElement] => [\n\tcreateTd(String(startLine)),\n\tcreateTd(String(startCol)),\n\tcreateTd(message, 'excerpt'),\n\tcreateTd(excerpt, 'excerpt mono'),\n];\n", "import {load, update, updateLink, addLink, insertRow, getErrorInfo} from './common';\n\ndeclare const data: [string, number, number, string, string][];\n\n(async () => {\n\tconst search = new URLSearchParams(location.search),\n\t\tpage = search.get('page')!,\n\t\tlang = search.get('lang')!,\n\t\ttime = search.get('timestamp'),\n\t\tbuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(page)),\n\t\thash = [...new Uint8Array(buffer)].slice(0, 4)\n\t\t\t.map(b => b.toString(16).padStart(2, '0'))\n\t\t\t.join(''),\n\t\tbase = `https://${\n\t\t\tlang === 'mediawiki' ? 'www.mediawiki.org' : `${lang}.wikipedia.org`\n\t\t}/wiki/${encodeURIComponent(page)}`,\n\t\tpurge = document.getElementById('purge')!;\n\tupdate('title', page);\n\tupdateLink('article', `${base}?redirect=no`, page);\n\tupdateLink('edit', `${base}?action=edit`);\n\tupdateLink('wiki', s => `${s}?lang=${lang}`, `${lang}wiki`);\n\tpurge.addEventListener('click', () => {\n\t\tpurge.style.pointerEvents = 'none';\n\t\t(async () => {\n\t\t\tconst response = await fetch(`./purge/${lang}/${\n\t\t\t\tencodeURIComponent(page.replace(/ /gu, '_'))\n\t\t\t}`);\n\t\t\tif (response.ok) {\n\t\t\t\tconst {timestamp} = await response.json();\n\t\t\t\tsearch.set('timestamp', timestamp as string);\n\t\t\t\tlocation.href = `./article.html?${search}`;\n\t\t\t} else {\n\t\t\t\tpurge.style.pointerEvents = '';\n\t\t\t}\n\t\t})();\n\t});\n\tload(`./data/${lang}/pages/${hash}.js${time ? `?timestamp=${time}` : ''}`, () => {\n\t\tif (data.length === 0) {\n\t\t\tpurge.style.display = 'none';\n\t\t} else {\n\t\t\tfor (const [rule, startLine, startCol, message, excerpt] of data) {\n\t\t\t\tinsertRow(\n\t\t\t\t\taddLink('td', rule, `./rule.html?lang=${lang}&rule=${rule}`, 'excerpt'),\n\t\t\t\t\t...getErrorInfo(startLine, startCol, message, excerpt),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t});\n})();\n"],
5
+ "mappings": "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",
6
+ "names": ["armorLink", "a", "href", "load", "src", "callback", "script", "update", "tag", "replace", "ele", "updateLink", "id", "text", "addLink", "className", "container", "createTd", "td", "insertRow", "tds", "tr", "getErrorInfo", "startLine", "startCol", "message", "excerpt", "search", "page", "lang", "time", "buffer", "hash", "b", "base", "purge", "update", "updateLink", "s", "response", "timestamp", "load", "rule", "startLine", "startCol", "message", "excerpt", "insertRow", "addLink", "getErrorInfo"]
7
+ }
@@ -1,15 +1,2 @@
1
- "use strict";
2
- (() => {
3
- const container = document.getElementById('container'), script = document.createElement('script');
4
- script.src = './data/index.js';
5
- script.addEventListener('load', () => {
6
- container.append(...window.data.map(lang => {
7
- const div = document.createElement('div'), a = document.createElement('a');
8
- a.href = `./wiki.html?lang=${lang}`;
9
- a.innerText = `${lang}.wikipedia.org`;
10
- div.append(a);
11
- return div;
12
- }));
13
- });
14
- document.head.append(script);
15
- })();
1
+ "use strict";(()=>{var l=(e,t)=>{e.href=t,t.startsWith("https://")&&(e.target="_blank",e.rel="noopener")},d=(e,t)=>{let n=document.createElement("script");n.src=e,n.addEventListener("load",t),document.head.append(n)};var s=(e,t,n,i)=>{let r=document.createElement(e),o=document.createElement("a");return o.textContent=t,l(o,n),r.append(o),i&&(r.className=i),r};d("./data/index.js",()=>{document.getElementById("container").append(...data.map(e=>s("div",`${e}wiki`,`./wiki.html?lang=${e}`)))});})();
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/common.ts", "../src/index.ts"],
4
+ "sourcesContent": ["const armorLink = (a: HTMLAnchorElement, href: string): void => {\n\ta.href = href;\n\tif (href.startsWith('https://')) {\n\t\ta.target = '_blank';\n\t\ta.rel = 'noopener';\n\t}\n};\n\nexport const load = (src: string, callback: () => void): void => {\n\tconst script = document.createElement('script');\n\tscript.src = src;\n\tscript.addEventListener('load', callback);\n\tdocument.head.append(script);\n};\n\nexport const update = (tag: string, replace: string): Element => {\n\tconst ele = document.querySelector(tag)!;\n\tele.textContent = ele.textContent!.replace('Wikipedia', replace);\n\treturn ele;\n};\n\nexport const updateLink = (id: string, href: string | ((s: string) => string) | false, text?: string): void => {\n\tconst a = document.getElementById(id) as HTMLAnchorElement;\n\tif (typeof href === 'function') {\n\t\ta.href = href(a.href);\n\t} else if (href) {\n\t\tarmorLink(a, href);\n\t} else {\n\t\ta.removeAttribute('href');\n\t}\n\tif (text !== undefined) {\n\t\ta.textContent = text;\n\t}\n};\n\nexport const addLink = (tag: string, text: string, href: string, className?: string): HTMLElement => {\n\tconst container = document.createElement(tag),\n\t\ta = document.createElement('a');\n\ta.textContent = text;\n\tarmorLink(a, href);\n\tcontainer.append(a);\n\tif (className) {\n\t\tcontainer.className = className;\n\t}\n\treturn container;\n};\n\nexport const createTd = (text: string, className?: string): HTMLTableCellElement => {\n\tconst td = document.createElement('td');\n\ttd.textContent = text;\n\tif (className) {\n\t\ttd.className = className;\n\t}\n\treturn td;\n};\n\nexport const insertRow = (...tds: HTMLElement[]): void => {\n\tconst tr = document.createElement('tr');\n\ttr.append(...tds);\n\tdocument.querySelector('tbody')!.append(tr);\n};\n\nexport const getErrorInfo = (\n\tstartLine: number,\n\tstartCol: number,\n\tmessage: string,\n\texcerpt: string,\n): [HTMLTableCellElement, HTMLTableCellElement, HTMLTableCellElement, HTMLTableCellElement] => [\n\tcreateTd(String(startLine)),\n\tcreateTd(String(startCol)),\n\tcreateTd(message, 'excerpt'),\n\tcreateTd(excerpt, 'excerpt mono'),\n];\n", "import {load, addLink} from './common';\n\ndeclare const data: string[];\n\nload('./data/index.js', () => {\n\tdocument.getElementById('container')!.append(...data.map(\n\t\tlang => addLink('div', `${lang}wiki`, `./wiki.html?lang=${lang}`),\n\t));\n});\n"],
5
+ "mappings": "mBAAA,IAAMA,EAAY,CAACC,EAAsBC,IAAuB,CAC/DD,EAAE,KAAOC,EACLA,EAAK,WAAW,UAAU,IAC7BD,EAAE,OAAS,SACXA,EAAE,IAAM,WAEV,EAEaE,EAAO,CAACC,EAAaC,IAA+B,CAChE,IAAMC,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,IAAMF,EACbE,EAAO,iBAAiB,OAAQD,CAAQ,EACxC,SAAS,KAAK,OAAOC,CAAM,CAC5B,EAsBO,IAAMC,EAAU,CAACC,EAAaC,EAAcC,EAAcC,IAAoC,CACpG,IAAMC,EAAY,SAAS,cAAcJ,CAAG,EAC3CK,EAAI,SAAS,cAAc,GAAG,EAC/B,OAAAA,EAAE,YAAcJ,EAChBK,EAAUD,EAAGH,CAAI,EACjBE,EAAU,OAAOC,CAAC,EACdF,IACHC,EAAU,UAAYD,GAEhBC,CACR,ECzCAG,EAAK,kBAAmB,IAAM,CAC7B,SAAS,eAAe,WAAW,EAAG,OAAO,GAAG,KAAK,IACpDC,GAAQC,EAAQ,MAAO,GAAGD,CAAI,OAAQ,oBAAoBA,CAAI,EAAE,CACjE,CAAC,CACF,CAAC",
6
+ "names": ["armorLink", "a", "href", "load", "src", "callback", "script", "addLink", "tag", "text", "href", "className", "container", "a", "armorLink", "load", "lang", "addLink"]
7
+ }
@@ -1,49 +1,2 @@
1
- "use strict";
2
- (() => {
3
- const search = new URLSearchParams(location.search), lang = search.get('lang'), rule = search.get('rule'), batch = Math.floor(Number(search.get('start') || 0) / 200), endStr = String((batch + 1) * 200), nav = document.getElementById('nav'), prev = document.getElementById('prev'), next = document.getElementById('next'), start = document.getElementById('start'), end = document.getElementById('end'), title = document.querySelector('title'), h2 = document.querySelector('h2'), wiki = document.getElementById('wiki'), table = document.querySelector('table'), tbody = document.querySelector('tbody'), script = document.createElement('script');
4
- title.textContent = title.textContent.replace('Wikipedia', `${lang}.wikipedia.org`);
5
- wiki.textContent = `${lang}wiki`;
6
- wiki.href += `?lang=${lang}`;
7
- if (batch === 0) {
8
- prev.removeAttribute('href');
9
- }
10
- else {
11
- start.textContent = String(batch * 200 + 1);
12
- end.textContent = endStr;
13
- search.set('start', String((batch - 1) * 200));
14
- prev.href = `${location.pathname}?${search}`;
15
- }
16
- search.set('start', endStr);
17
- next.href = `${location.pathname}?${search}`;
18
- script.src = `./data/${lang}/${rule}-${batch}.js`;
19
- script.addEventListener('load', () => {
20
- h2.textContent = `${h2.textContent.replace('Wikipedia', `${lang}.wikipedia.org: ${rule}`)} (${data.timestamp})`;
21
- if (data.batches === batch + 1) {
22
- next.removeAttribute('href');
23
- end.textContent = String(batch * 200 + data.articles.length);
24
- }
25
- for (const [page, startLine, startCol, message, excerpt] of data.articles) {
26
- const tr = document.createElement('tr'), article = document.createElement('td'), edit = document.createElement('td'), line = document.createElement('td'), column = document.createElement('td'), detail = document.createElement('td'), notice = document.createElement('td'), more = document.createElement('td'), articleLink = document.createElement('a'), editLink = document.createElement('a'), moreLink = document.createElement('a');
27
- articleLink.textContent = page;
28
- articleLink.href = `https://${lang}.wikipedia.org/wiki/${encodeURIComponent(page)}?redirect=no`;
29
- article.className = 'excerpt';
30
- article.append(articleLink);
31
- editLink.textContent = 'edit';
32
- editLink.href = `https://${lang}.wikipedia.org/wiki/${encodeURIComponent(page)}?action=edit`;
33
- edit.append(editLink);
34
- line.textContent = String(startLine);
35
- column.textContent = String(startCol);
36
- detail.textContent = message;
37
- detail.className = 'excerpt';
38
- notice.textContent = excerpt;
39
- notice.className = 'excerpt mono';
40
- moreLink.textContent = 'more';
41
- moreLink.href = `./article.html?lang=${lang}&page=${encodeURIComponent(page)}`;
42
- more.append(moreLink);
43
- tr.append(article, edit, line, column, detail, notice, more);
44
- tbody.append(tr);
45
- }
46
- table.after(nav.cloneNode(true));
47
- });
48
- document.head.append(script);
49
- })();
1
+ "use strict";(()=>{var g=(e,t)=>{e.href=t,t.startsWith("https://")&&(e.target="_blank",e.rel="noopener")},p=(e,t)=>{let n=document.createElement("script");n.src=e,n.addEventListener("load",t),document.head.append(n)},m=(e,t)=>{let n=document.querySelector(e);return n.textContent=n.textContent.replace("Wikipedia",t),n},d=(e,t,n)=>{let r=document.getElementById(e);typeof t=="function"?r.href=t(r.href):t?g(r,t):r.removeAttribute("href"),n!==void 0&&(r.textContent=n)},u=(e,t,n,r)=>{let s=document.createElement(e),c=document.createElement("a");return c.textContent=t,g(c,n),s.append(c),r&&(s.className=r),s},l=(e,t)=>{let n=document.createElement("td");return n.textContent=e,t&&(n.className=t),n},E=(...e)=>{let t=document.createElement("tr");t.append(...e),document.querySelector("tbody").append(t)},x=(e,t,n,r)=>[l(String(e)),l(String(t)),l(n,"excerpt"),l(r,"excerpt mono")];var o=new URLSearchParams(location.search),i=o.get("lang"),b=o.get("rule"),a=Math.floor(Number(o.get("start")||0)/200),f=m("h2",`${i}wiki: ${b}`);m("title",`${i}wiki`);d("wiki",e=>`${e}?lang=${i}`,`${i}wiki`);o.set("start",String((a-1)*200));d("prev",a!==0&&`${location.pathname}?${o}`);document.getElementById("start").textContent=String(a*200+1);p(`./data/${i}/${b}-${a}.js`,()=>{let e=String(a*200+data.articles.length);f.textContent+=` (${data.timestamp})`,document.getElementById("end").textContent=e,o.set("start",e),d("next",data.batches!==a+1&&`${location.pathname}?${o}`),document.querySelector("table").after(document.getElementById("nav").cloneNode(!0));for(let[t,n,r,s,c]of data.articles){let h=encodeURIComponent(t);E(u("td",t,`./article.html?lang=${i}&page=${h}`,"excerpt"),...x(n,r,s,c))}});})();
2
+ //# sourceMappingURL=rule.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/common.ts", "../src/rule.ts"],
4
+ "sourcesContent": ["const armorLink = (a: HTMLAnchorElement, href: string): void => {\n\ta.href = href;\n\tif (href.startsWith('https://')) {\n\t\ta.target = '_blank';\n\t\ta.rel = 'noopener';\n\t}\n};\n\nexport const load = (src: string, callback: () => void): void => {\n\tconst script = document.createElement('script');\n\tscript.src = src;\n\tscript.addEventListener('load', callback);\n\tdocument.head.append(script);\n};\n\nexport const update = (tag: string, replace: string): Element => {\n\tconst ele = document.querySelector(tag)!;\n\tele.textContent = ele.textContent!.replace('Wikipedia', replace);\n\treturn ele;\n};\n\nexport const updateLink = (id: string, href: string | ((s: string) => string) | false, text?: string): void => {\n\tconst a = document.getElementById(id) as HTMLAnchorElement;\n\tif (typeof href === 'function') {\n\t\ta.href = href(a.href);\n\t} else if (href) {\n\t\tarmorLink(a, href);\n\t} else {\n\t\ta.removeAttribute('href');\n\t}\n\tif (text !== undefined) {\n\t\ta.textContent = text;\n\t}\n};\n\nexport const addLink = (tag: string, text: string, href: string, className?: string): HTMLElement => {\n\tconst container = document.createElement(tag),\n\t\ta = document.createElement('a');\n\ta.textContent = text;\n\tarmorLink(a, href);\n\tcontainer.append(a);\n\tif (className) {\n\t\tcontainer.className = className;\n\t}\n\treturn container;\n};\n\nexport const createTd = (text: string, className?: string): HTMLTableCellElement => {\n\tconst td = document.createElement('td');\n\ttd.textContent = text;\n\tif (className) {\n\t\ttd.className = className;\n\t}\n\treturn td;\n};\n\nexport const insertRow = (...tds: HTMLElement[]): void => {\n\tconst tr = document.createElement('tr');\n\ttr.append(...tds);\n\tdocument.querySelector('tbody')!.append(tr);\n};\n\nexport const getErrorInfo = (\n\tstartLine: number,\n\tstartCol: number,\n\tmessage: string,\n\texcerpt: string,\n): [HTMLTableCellElement, HTMLTableCellElement, HTMLTableCellElement, HTMLTableCellElement] => [\n\tcreateTd(String(startLine)),\n\tcreateTd(String(startCol)),\n\tcreateTd(message, 'excerpt'),\n\tcreateTd(excerpt, 'excerpt mono'),\n];\n", "import {load, update, addLink, insertRow, getErrorInfo, updateLink} from './common';\n\ndeclare const data: {\n\tarticles: [string, number, number, string, string][];\n\tbatches: number;\n\ttimestamp: string;\n};\n\nconst search = new URLSearchParams(location.search),\n\tlang = search.get('lang'),\n\trule = search.get('rule'),\n\tbatch = Math.floor(Number(search.get('start') || 0) / 200),\n\th2 = update('h2', `${lang}wiki: ${rule}`);\nupdate('title', `${lang}wiki`);\nupdateLink('wiki', s => `${s}?lang=${lang}`, `${lang}wiki`);\nsearch.set('start', String((batch - 1) * 200));\nupdateLink('prev', batch !== 0 && `${location.pathname}?${search}`);\ndocument.getElementById('start')!.textContent = String(batch * 200 + 1);\nload(`./data/${lang}/${rule}-${batch}.js`, () => {\n\tconst endStr = String(batch * 200 + data.articles.length);\n\th2.textContent += ` (${data.timestamp})`;\n\tdocument.getElementById('end')!.textContent = endStr;\n\tsearch.set('start', endStr);\n\tupdateLink('next', data.batches !== batch + 1 && `${location.pathname}?${search}`);\n\tdocument.querySelector('table')!\n\t\t.after(document.getElementById('nav')!.cloneNode(true));\n\tfor (const [page, startLine, startCol, message, excerpt] of data.articles) {\n\t\tconst title = encodeURIComponent(page);\n\t\tinsertRow(\n\t\t\taddLink('td', page, `./article.html?lang=${lang}&page=${title}`, 'excerpt'),\n\t\t\t...getErrorInfo(startLine, startCol, message, excerpt),\n\t\t);\n\t}\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["armorLink", "a", "href", "load", "src", "callback", "script", "update", "tag", "replace", "ele", "updateLink", "id", "text", "addLink", "className", "container", "createTd", "td", "insertRow", "tds", "tr", "getErrorInfo", "startLine", "startCol", "message", "excerpt", "search", "lang", "rule", "batch", "h2", "update", "updateLink", "s", "load", "endStr", "page", "startLine", "startCol", "message", "excerpt", "title", "insertRow", "addLink", "getErrorInfo"]
7
+ }
@@ -1,19 +1,2 @@
1
- "use strict";
2
- (() => {
3
- const lang = new URLSearchParams(location.search).get('lang'), script = document.createElement('script'), title = document.querySelector('title'), h2 = document.querySelector('h2'), tbody = document.querySelector('tbody');
4
- title.textContent = title.textContent.replace('Wikipedia', `${lang}.wikipedia.org`);
5
- script.src = `./data/${lang}/index.js`;
6
- script.addEventListener('load', () => {
7
- h2.textContent = `${h2.textContent.replace('Wikipedia', `${lang}.wikipedia.org`)} (${data.slice(-1)[0]})`;
8
- for (const [rule, count] of data.slice(0, -1)) {
9
- const tr = document.createElement('tr'), description = document.createElement('td'), pages = document.createElement('td'), a = document.createElement('a');
10
- a.textContent = rule;
11
- a.href = `./rule.html?lang=${lang}&rule=${rule}`;
12
- description.append(a);
13
- pages.textContent = String(count);
14
- tr.append(description, pages);
15
- tbody.append(tr);
16
- }
17
- });
18
- document.head.append(script);
19
- })();
1
+ "use strict";(()=>{var g=(t,e)=>{t.href=e,e.startsWith("https://")&&(t.target="_blank",t.rel="noopener")},l=(t,e)=>{let n=document.createElement("script");n.src=t,n.addEventListener("load",e),document.head.append(n)},s=(t,e)=>{let n=document.querySelector(t);return n.textContent=n.textContent.replace("Wikipedia",e),n};var c=(t,e,n,a)=>{let o=document.createElement(t),i=document.createElement("a");return i.textContent=e,g(i,n),o.append(i),a&&(o.className=a),o},d=(t,e)=>{let n=document.createElement("td");return n.textContent=t,e&&(n.className=e),n},m=(...t)=>{let e=document.createElement("tr");e.append(...t),document.querySelector("tbody").append(e)};var r=new URLSearchParams(location.search).get("lang"),p=s("h2",`${r}wiki`);s("title",`${r}wiki`);l(`./data/${r}/index.js`,()=>{p.textContent+=` (${data[data.length-1]})`;for(let[t,e]of data.slice(0,-1))m(c("td",t,`./rule.html?lang=${r}&rule=${t}`),d(String(e)))});})();
2
+ //# sourceMappingURL=wiki.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/common.ts", "../src/wiki.ts"],
4
+ "sourcesContent": ["const armorLink = (a: HTMLAnchorElement, href: string): void => {\n\ta.href = href;\n\tif (href.startsWith('https://')) {\n\t\ta.target = '_blank';\n\t\ta.rel = 'noopener';\n\t}\n};\n\nexport const load = (src: string, callback: () => void): void => {\n\tconst script = document.createElement('script');\n\tscript.src = src;\n\tscript.addEventListener('load', callback);\n\tdocument.head.append(script);\n};\n\nexport const update = (tag: string, replace: string): Element => {\n\tconst ele = document.querySelector(tag)!;\n\tele.textContent = ele.textContent!.replace('Wikipedia', replace);\n\treturn ele;\n};\n\nexport const updateLink = (id: string, href: string | ((s: string) => string) | false, text?: string): void => {\n\tconst a = document.getElementById(id) as HTMLAnchorElement;\n\tif (typeof href === 'function') {\n\t\ta.href = href(a.href);\n\t} else if (href) {\n\t\tarmorLink(a, href);\n\t} else {\n\t\ta.removeAttribute('href');\n\t}\n\tif (text !== undefined) {\n\t\ta.textContent = text;\n\t}\n};\n\nexport const addLink = (tag: string, text: string, href: string, className?: string): HTMLElement => {\n\tconst container = document.createElement(tag),\n\t\ta = document.createElement('a');\n\ta.textContent = text;\n\tarmorLink(a, href);\n\tcontainer.append(a);\n\tif (className) {\n\t\tcontainer.className = className;\n\t}\n\treturn container;\n};\n\nexport const createTd = (text: string, className?: string): HTMLTableCellElement => {\n\tconst td = document.createElement('td');\n\ttd.textContent = text;\n\tif (className) {\n\t\ttd.className = className;\n\t}\n\treturn td;\n};\n\nexport const insertRow = (...tds: HTMLElement[]): void => {\n\tconst tr = document.createElement('tr');\n\ttr.append(...tds);\n\tdocument.querySelector('tbody')!.append(tr);\n};\n\nexport const getErrorInfo = (\n\tstartLine: number,\n\tstartCol: number,\n\tmessage: string,\n\texcerpt: string,\n): [HTMLTableCellElement, HTMLTableCellElement, HTMLTableCellElement, HTMLTableCellElement] => [\n\tcreateTd(String(startLine)),\n\tcreateTd(String(startCol)),\n\tcreateTd(message, 'excerpt'),\n\tcreateTd(excerpt, 'excerpt mono'),\n];\n", "import {load, update, addLink, createTd, insertRow} from './common';\n\ndeclare const data: [...[string, number][], string];\n\nconst lang = new URLSearchParams(location.search).get('lang'),\n\th2 = update('h2', `${lang}wiki`);\nupdate('title', `${lang}wiki`);\nload(`./data/${lang}/index.js`, () => {\n\th2.textContent += ` (${data[data.length - 1] as string})`;\n\tfor (const [rule, count] of data.slice(0, -1) as [string, number][]) {\n\t\tinsertRow(\n\t\t\taddLink('td', rule, `./rule.html?lang=${lang}&rule=${rule}`),\n\t\t\tcreateTd(String(count)),\n\t\t);\n\t}\n});\n"],
5
+ "mappings": "mBAAA,IAAMA,EAAY,CAACC,EAAsBC,IAAuB,CAC/DD,EAAE,KAAOC,EACLA,EAAK,WAAW,UAAU,IAC7BD,EAAE,OAAS,SACXA,EAAE,IAAM,WAEV,EAEaE,EAAO,CAACC,EAAaC,IAA+B,CAChE,IAAMC,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,IAAMF,EACbE,EAAO,iBAAiB,OAAQD,CAAQ,EACxC,SAAS,KAAK,OAAOC,CAAM,CAC5B,EAEaC,EAAS,CAACC,EAAaC,IAA6B,CAChE,IAAMC,EAAM,SAAS,cAAcF,CAAG,EACtC,OAAAE,EAAI,YAAcA,EAAI,YAAa,QAAQ,YAAaD,CAAO,EACxDC,CACR,EAgBO,IAAMC,EAAU,CAACC,EAAaC,EAAcC,EAAcC,IAAoC,CACpG,IAAMC,EAAY,SAAS,cAAcJ,CAAG,EAC3CK,EAAI,SAAS,cAAc,GAAG,EAC/B,OAAAA,EAAE,YAAcJ,EAChBK,EAAUD,EAAGH,CAAI,EACjBE,EAAU,OAAOC,CAAC,EACdF,IACHC,EAAU,UAAYD,GAEhBC,CACR,EAEaG,EAAW,CAACN,EAAcE,IAA6C,CACnF,IAAMK,EAAK,SAAS,cAAc,IAAI,EACtC,OAAAA,EAAG,YAAcP,EACbE,IACHK,EAAG,UAAYL,GAETK,CACR,EAEaC,EAAY,IAAIC,IAA6B,CACzD,IAAMC,EAAK,SAAS,cAAc,IAAI,EACtCA,EAAG,OAAO,GAAGD,CAAG,EAChB,SAAS,cAAc,OAAO,EAAG,OAAOC,CAAE,CAC3C,ECxDA,IAAMC,EAAO,IAAI,gBAAgB,SAAS,MAAM,EAAE,IAAI,MAAM,EAC3DC,EAAKC,EAAO,KAAM,GAAGF,CAAI,MAAM,EAChCE,EAAO,QAAS,GAAGF,CAAI,MAAM,EAC7BG,EAAK,UAAUH,CAAI,YAAa,IAAM,CACrCC,EAAG,aAAe,KAAK,KAAK,KAAK,OAAS,CAAC,CAAW,IACtD,OAAW,CAACG,EAAMC,CAAK,IAAK,KAAK,MAAM,EAAG,EAAE,EAC3CC,EACCC,EAAQ,KAAMH,EAAM,oBAAoBJ,CAAI,SAASI,CAAI,EAAE,EAC3DI,EAAS,OAAOH,CAAK,CAAC,CACvB,CAEF,CAAC",
6
+ "names": ["armorLink", "a", "href", "load", "src", "callback", "script", "update", "tag", "replace", "ele", "addLink", "tag", "text", "href", "className", "container", "a", "armorLink", "createTd", "td", "insertRow", "tds", "tr", "lang", "h2", "update", "load", "rule", "count", "insertRow", "addLink", "createTd"]
7
+ }
@@ -4,6 +4,7 @@
4
4
  <title>Diagnostic Report for Wikipedia Dumps</title>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0, width=device-width">
7
+ <link rel="icon" href="data:image/png;base64,iVBORw0KGgo=">
7
8
  <link rel="stylesheet" href="reports.css">
8
9
  <script defer="" src="dist/index.js"></script>
9
10
  </head>
@@ -29,6 +29,7 @@ article {
29
29
  display: block;
30
30
  width: 100%;
31
31
  padding: 1rem 2rem 2rem;
32
+ overflow-x: auto;
32
33
  }
33
34
  h2 {
34
35
  font-size: 1.3em;
@@ -65,6 +66,7 @@ th {
65
66
  }
66
67
  .mono {
67
68
  font-family: monospace;
69
+ white-space: pre;
68
70
  }
69
71
 
70
72
  @media screen and (max-width: 720px) {
package/reports/rule.html CHANGED
@@ -4,6 +4,7 @@
4
4
  <title>Diagnostic Report for Wikipedia</title>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0, width=device-width">
7
+ <link rel="icon" href="data:image/png;base64,iVBORw0KGgo=">
7
8
  <link rel="stylesheet" href="reports.css">
8
9
  <style>td:nth-child(-n+4):not(:first-child),td:last-child{text-align:center}</style>
9
10
  <script defer="" src="dist/rule.js"></script>
@@ -27,25 +28,21 @@
27
28
  <span id="end">200</span>
28
29
  <a href="#" id="next" title="Next">→</a>
29
30
  </div>
30
- <table style="table-layout:fixed;width:100%">
31
+ <table style="table-layout:fixed;width:100%;min-width:900px">
31
32
  <colgroup>
32
33
  <col span="1">
33
- <col span="1" style="width:calc(1.6em + 5ch)">
34
34
  <col span="1" style="width:calc(1.6em + 7ch)">
35
35
  <col span="1" style="width:calc(1.6em + 7ch)">
36
36
  <col span="1" style="width:20%">
37
37
  <col span="1" style="width:40%">
38
- <col span="1" style="width:calc(1.6em + 5ch)">
39
38
  </colgroup>
40
39
  <tbody>
41
40
  <tr>
42
41
  <th>Article</th>
43
- <th>Edit</th>
44
42
  <th>Line</th>
45
43
  <th>Column</th>
46
44
  <th>Detail</th>
47
45
  <th>Notice</th>
48
- <th>More</th>
49
46
  </tr>
50
47
  </tbody>
51
48
  </table>
package/reports/wiki.html CHANGED
@@ -4,6 +4,7 @@
4
4
  <title>Diagnostic Report for Wikipedia</title>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0, width=device-width">
7
+ <link rel="icon" href="data:image/png;base64,iVBORw0KGgo=">
7
8
  <link rel="stylesheet" href="reports.css">
8
9
  <script defer="" src="dist/wiki.js"></script>
9
10
  </head>
package/scan.sh CHANGED
@@ -5,12 +5,18 @@ then
5
5
  echo 'Example: npx lint-wiki-dumps zh-yue ~/Downloads/dumps'
6
6
  exit 1
7
7
  fi
8
- site="${1}wiki" # example: zh-yuewiki
9
8
  target="${1//-/_}wiki" # example: zh_yuewiki
10
- file="$target-latest-pages-articles.xml.bz2"
11
- if (( $# < 3 ))
9
+ npx getParserConfig "${1}wiki" "https://$1.wikipedia.org/w/"
10
+ bash download.sh "$target" "$2"
11
+ if (( $? == 1 ))
12
12
  then
13
- curl --output-dir "$2" -O "https://dumps.wikimedia.org/$target/latest/$file"
14
- npx getParserConfig "$site" "https://$1.wikipedia.org/w/"
13
+ echo 'Switching to single-threaded mode'
14
+ node parser.js "$1" "$2/$target-latest-pages-articles.xml.bz2"
15
+ else
16
+ node parser-parallel.js "$1" "$2" "$4"
17
+ fi
18
+ if (( $? == 0))
19
+ then
20
+ echo 'Starting report generation'
21
+ node report.js "$1" "$3"
15
22
  fi
16
- node parser.js "$1" "$2/$file" "$4" "$5" && node report.js "$1" "$3"
package/server.js ADDED
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const http_1 = require("http");
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const util_1 = require("./util");
10
+ const port = parseInt(process.env['PORT'] || '8000'), headers = {
11
+ 'content-type': 'application/json',
12
+ 'x-content-type-options': 'nosniff',
13
+ 'cache-control': 'max-age=5',
14
+ };
15
+ let busy = false;
16
+ (0, http_1.createServer)(({ url }, res) => {
17
+ if (!url || url === '/') {
18
+ url = 'index.html'; // eslint-disable-line no-param-reassign
19
+ }
20
+ const file = new URL(path_1.default.join('reports', url), 'http://localhost').pathname.slice(1);
21
+ if (file.startsWith('reports/purge/')) {
22
+ const [, , lang, page] = file.split('/');
23
+ (async () => {
24
+ const obj = { status: 'error' };
25
+ let code = 400;
26
+ if (busy) {
27
+ code = 503;
28
+ }
29
+ else if (lang && page) {
30
+ busy = true;
31
+ try {
32
+ const response = await fetch(`https://${lang === 'mediawiki' ? 'www.mediawiki.org' : `${lang}.wikipedia.org`}/w/rest.php/v1/page/${page}`, {
33
+ headers: {
34
+ 'Api-User-Agent': 'tools.lint-wiki-dumps (https://github.com/bhsd-harry/lint-wiki-dumps)',
35
+ },
36
+ }), title = decodeURIComponent(page).replaceAll('_', ' ');
37
+ code = response.status;
38
+ console.log(`Purging ${lang}wiki: ${title}; status: ${code}`);
39
+ if (code === 200) {
40
+ const { source, content_model: contentModel, latest: { timestamp } } = await response.json();
41
+ if (contentModel === 'wikitext') {
42
+ const errors = (0, util_1.lint)(source), hash = `${(0, util_1.getHash)(lang, title)}.js`, filepath = path_1.default.join('reports', 'data', hash);
43
+ console.log(`Remaining errors in ${hash}: ${errors.length}`);
44
+ (0, util_1.write)(filepath, errors);
45
+ obj.status = 'success';
46
+ obj.timestamp = timestamp;
47
+ }
48
+ else {
49
+ code = 400;
50
+ }
51
+ }
52
+ }
53
+ catch {
54
+ code = 500;
55
+ }
56
+ busy = false; // eslint-disable-line require-atomic-updates
57
+ }
58
+ res.writeHead(code, headers);
59
+ res.end(JSON.stringify(obj), 'utf8');
60
+ })();
61
+ }
62
+ else {
63
+ let contentType;
64
+ switch (path_1.default.extname(file)) {
65
+ case '.js':
66
+ contentType = 'text/javascript';
67
+ break;
68
+ case '.css':
69
+ contentType = 'text/css';
70
+ break;
71
+ default:
72
+ contentType = 'text/html';
73
+ }
74
+ if (fs_1.default.existsSync(file)) {
75
+ res.writeHead(200, {
76
+ 'content-type': contentType,
77
+ 'x-content-type-options': 'nosniff',
78
+ 'cache-control': `max-age=${60 * 60 * 24}, public`,
79
+ });
80
+ res.end(fs_1.default.readFileSync(file), 'utf8');
81
+ }
82
+ else {
83
+ res.writeHead(301, { Location: '/' });
84
+ res.end();
85
+ }
86
+ }
87
+ }).listen(port);
package/util.js ADDED
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.write = exports.getHash = exports.normalize = exports.reading = exports.replaceTilde = exports.isArticle = exports.getErrors = exports.getTimestamp = exports.getXmlStream = exports.getWriteStream = exports.init = exports.getTempPath = exports.lint = exports.resultDir = exports.MAX = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const crypto_1 = require("crypto");
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const unbzip2_stream_1 = __importDefault(require("unbzip2-stream"));
13
+ const xml_stream_1 = __importDefault(require("xml-stream"));
14
+ const wikilint_1 = __importDefault(require("wikilint"));
15
+ exports.MAX = 100, exports.resultDir = path_1.default.join(__dirname, 'results');
16
+ const tempDir = path_1.default.join(__dirname, 'temp'), ignore = new Set(['h1', 'no-arg', 'unclosed-table', 'unmatched-tag', 'url-encoding', 'var-anchor', 'void-ext']);
17
+ const lint = ($text, ns) => wikilint_1.default.parse($text, ns === '828').lint()
18
+ .filter(({ severity, rule }) => severity === 'error' && !ignore.has(rule))
19
+ .map(({ severity, suggestions, fix,
20
+ /* DISABLED */
21
+ code, startIndex, endLine, endCol, endIndex,
22
+ /* DISABLED END */
23
+ ...e }) => ({
24
+ ...e,
25
+ // eslint-disable-next-line @stylistic/multiline-comment-style
26
+ /* DISABLED
27
+
28
+ ...suggestions && {
29
+ suggestions: suggestions.map(action => ({
30
+ ...action,
31
+ original: $text.slice(...action.range),
32
+ })),
33
+ },
34
+ ...fix && {fix: {...fix, original: $text.slice(...fix.range)}},
35
+
36
+ */
37
+ excerpt: $text.slice(startIndex, endIndex).slice(0, exports.MAX),
38
+ }));
39
+ exports.lint = lint;
40
+ const getTempPath = (file) => path_1.default.join(tempDir, file);
41
+ exports.getTempPath = getTempPath;
42
+ const init = () => {
43
+ if (!fs_1.default.existsSync(exports.resultDir)) {
44
+ fs_1.default.mkdirSync(exports.resultDir);
45
+ }
46
+ if (!fs_1.default.existsSync(tempDir)) {
47
+ fs_1.default.mkdirSync(tempDir);
48
+ }
49
+ };
50
+ exports.init = init;
51
+ const getWriteStream = (file, callback) => {
52
+ const stream = fs_1.default.createWriteStream(file);
53
+ stream.write('{');
54
+ stream.on('close', callback);
55
+ return stream;
56
+ };
57
+ exports.getWriteStream = getWriteStream;
58
+ const getXmlStream = (file) => {
59
+ const readable = fs_1.default.createReadStream(file).pipe((0, unbzip2_stream_1.default)()), stream = new xml_stream_1.default(readable);
60
+ readable.on('error', e => {
61
+ console.error(chalk_1.default.red(`Error unzipping ${file}`));
62
+ throw e;
63
+ });
64
+ stream.preserve('text', true);
65
+ return stream;
66
+ };
67
+ exports.getXmlStream = getXmlStream;
68
+ const getTimestamp = (data) => {
69
+ if (!data) {
70
+ return undefined;
71
+ }
72
+ const i = data.indexOf('"#timestamp": "') + 15;
73
+ return new Date(data.slice(i, data.indexOf('"', i)));
74
+ };
75
+ exports.getTimestamp = getTimestamp;
76
+ const getErrors = (data, page) => {
77
+ const str = JSON.stringify(page), i = data.indexOf(`${str}: [`);
78
+ if (i === -1) {
79
+ return undefined;
80
+ }
81
+ const j = i + str.length + 2;
82
+ return data.slice(j, data.indexOf('\n]', j) + 2);
83
+ };
84
+ exports.getErrors = getErrors;
85
+ const isArticle = ($text, ns, model) => ns === '0' && model === 'wikitext' && Boolean($text);
86
+ exports.isArticle = isArticle;
87
+ const replaceTilde = (str) => str.replace(/^~/u, os_1.default.homedir());
88
+ exports.replaceTilde = replaceTilde;
89
+ const reading = (file) => {
90
+ console.log(chalk_1.default.green(`Reading ${file}`));
91
+ };
92
+ exports.reading = reading;
93
+ const normalize = (str) => str.replaceAll('-', '_');
94
+ exports.normalize = normalize;
95
+ const getHash = (lang, page) => {
96
+ const hash = (0, crypto_1.createHash)('sha256').update(page).digest('hex').slice(0, 8);
97
+ return path_1.default.join(lang, 'pages', hash);
98
+ };
99
+ exports.getHash = getHash;
100
+ const write = (file, data) => {
101
+ fs_1.default.writeFileSync(file, `globalThis.data=${JSON.stringify(data)}`);
102
+ };
103
+ exports.write = write;
package/scan-parallel.sh DELETED
@@ -1,23 +0,0 @@
1
- #!/usr/local/bin/bash
2
- if (( $# < 2 ))
3
- then
4
- echo 'Usage: npx lint-wiki-dumps <language> <path to download> [path to HTML output]'
5
- echo 'Example: npx lint-wiki-dumps zh-yue ~/Downloads/dumps'
6
- exit 1
7
- fi
8
- site="${1}wiki" # example: zh-yuewiki
9
- target="${1//-/_}wiki" # example: zh_yuewiki
10
- files=$( \
11
- curl -s "https://dumps.wikimedia.org/$target/latest/" \
12
- | grep -o "href=\"$target-latest-pages-articles[0-9].*\.bz2\">" \
13
- | gsed "s|href=\"|https://dumps.wikimedia.org/$target/latest/|;s|\">||" \
14
- )
15
- if (( ${#files} < 2 ))
16
- then
17
- echo 'Switching to single-threaded mode'
18
- bash scan.sh "$1" "$2" "$3"
19
- else
20
- curl --output-dir "$2" --remote-name-all $files
21
- npx getParserConfig "$site" "https://$1.wikipedia.org/w/"
22
- node parser-parallel.js "$1" "$2" && node report.js "$1" "$3"
23
- fi