1/*
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { readFileSync, writeFileSync } from 'fs';
17import { join } from 'path';
18
19const COPYRIGHT_HEADER =
20  '/* \n\
21 * Copyright (c) 2022-2024 Huawei Device Co., Ltd. \n\
22 * Licensed under the Apache License, Version 2.0 (the "License"); \n\
23 * you may not use this file except in compliance with the License. \n\
24 * You may obtain a copy of the License at \n\
25 * \n\
26 * http://www.apache.org/licenses/LICENSE-2.0 \n\
27 * \n\
28 * Unless required by applicable law or agreed to in writing, software \n\
29 * distributed under the License is distributed on an "AS IS" BASIS, \n\
30 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n\
31 * See the License for the specific language governing permissions and \n\
32 * limitations under the License. \n\
33 */ \n\
34';
35
36const CODE_PROLOGUE =
37  'export const cookBookMsg: string[] = [];\n\
38export const cookBookTag: string[] = [];\n\
39\n\
40for( let i = 0; i <= 150; i++) {\n\
41  cookBookMsg[ i ] = \'\';\n\
42}\n\
43';
44
45// HTML tags
46const T_BR = '<br>';
47const T_BOLD = '<b>';
48const T_END_BOLD = '</b>';
49const T_CODE = '<code>';
50const T_END_CODE = '</code>';
51const T_NBSP = '&nbsp;';
52const T_HR = '<hr style="height:3px;">';
53
54// RST substititions
55const CB_ = '|CB_';
56const CB_R = '|CB_R|';
57const CB_RULE = '|CB_RULE|';
58const CB_BAD = '|CB_BAD|';
59const CB_OK = '|CB_OK|';
60// replace:: **Severity: error**
61const CB_ERROR = '|CB_ERROR|';
62// replace:: **Severity: warning**
63const CB_WARNING = '|CB_WARNING|';
64const CB_SEE = '|CB_SEE|';
65const CB_REF = ':ref:';
66const CB_META = '.. meta';
67const CB_FIX = ':fix:';
68
69const NEW_REC_HEADER = /.. _R\d\d\d:/;
70// should be ".. code-block" but there is an error in some doc files
71const CODE_BLOCK = '.. code';
72
73let doc_lines: string[];
74let _line: number;
75let recNum: number;
76
77const tegs: string[] = [];
78const ruleNames: string[] = [];
79let mdText: string[] = [];
80const fixTitles: Map<number, string> = new Map();
81
82// continue line
83const CL = ' \\';
84const STR_DLMTR = '\'';
85
86function syncReadFile(filename: string): string[] {
87  const contents = readFileSync(filename, 'utf-8');
88
89  doc_lines = contents.split(/\r?\n/);
90
91  // make table of rule names
92  _line = 0;
93  let ruleNum = -1;
94  while (_line < doc_lines.length) {
95    const line = doc_lines[_line];
96    if (NEW_REC_HEADER.test(line)) {
97      ruleNum = Number(line.replace(/\D/g, ''));
98      console.log('>>>>>>> START RULE ' + ruleNum + ':');
99      console.log('                    NUMBER: ' + ruleNum);
100    }
101    if (doc_lines[_line].startsWith(CB_R)) {
102      let line = doc_lines[_line].split(CB_R)[1];
103
104      /*
105       * let tegNumStr = line.split(':')[0];
106       * let ruleNum = Number(tegNumStr.split('#')[1]);
107       */
108
109      // line.split(':')[1];
110      ruleNames[ruleNum] = line;
111      _line++;
112      needHeader();
113      if (doc_lines[_line].startsWith(CB_RULE)) {
114        line = doc_lines[_line].trim().replace(CB_RULE, '').
115          trim();
116        ruleNames[ruleNum] = ruleNames[ruleNum] + ' (' + line + ')';
117      }
118    }
119    _line++;
120  }
121
122  // scan text
123  _line = 0;
124  while (_line < doc_lines.length) {
125    skipEmptyLines();
126    const line = doc_lines[_line];
127    if (NEW_REC_HEADER.test(line)) {
128      makeRecipe();
129    } else {
130      _line++;
131    }
132  }
133
134  return doc_lines;
135}
136
137/*
138 *
139 * utility functions
140 *
141 */
142
143function replaceAll(s: string, from: string, to: string): string {
144  const ss = s.split(from);
145  let outStr = '';
146  ss.forEach((line) => {
147    outStr += to + line;
148  });
149
150  // remove 1st 'to' substring
151  return outStr.replace(to, '');
152}
153
154function translateLine(s: string): string {
155  let line = s;
156  line = line.replace(CB_BAD, 'TypeScript');
157  line = line.replace(CB_OK, 'ArkTS');
158
159  /*
160   * line = line.replace( "|CB_R|", "Recipe");
161   * .. |CB_RULE| replace:: Rule
162   */
163  line = line.replace(CB_ERROR, '**Severity: error**');
164  line = line.replace(CB_WARNING, '**Severity: warning**');
165  line = line.replace(CB_SEE, '## See also');
166
167  line = replaceAll(line, '|JS|', 'JavaScript');
168  // .. |LANG| replace:: {lang}
169  line = replaceAll(line, '|LANG|', 'ArkTS');
170  line = replaceAll(line, '|TS|', 'TypeScript');
171
172  return line;
173}
174
175function translateTeg(s: string): string {
176  return replaceAll(s, '``', '"').trim();
177}
178
179function highlightCode(s: string): string {
180  const ss = s.split('``');
181  let line = ss[0];
182  for (let i = 1; i < ss.length; i++) {
183    if (i % 2 === 0) {
184      line += T_END_CODE;
185    } else {
186      line += T_CODE;
187    }
188    line += ss[i];
189  }
190  return line;
191}
192
193function escapeSym(s: string): string {
194  const ss = replaceAll(s, '\'', '\\\'');
195  return replaceAll(ss, '"', '\\"');
196}
197
198function setNBSP(s: string): string {
199  let ss = '';
200  let flag = true;
201  for (const ch of s) {
202    if (ch !== ' ' && ch !== '\t') {
203      flag = false;
204    }
205    if (flag && ch === ' ') {
206      ss += T_NBSP;
207    } else if (flag && ch === '\t') {
208      ss += T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP;
209    } else {
210      ss += ch;
211    }
212  }
213  return ss;
214}
215
216function skipEmptyLines(): void {
217  while (_line < doc_lines.length) {
218    let s = doc_lines[_line];
219    s = s.trim();
220    if (s !== '') {
221      break;
222    }
223    _line++;
224  }
225}
226
227function isHeader(): boolean {
228  return doc_lines[_line].startsWith(CB_) || doc_lines[_line].startsWith('..');
229}
230
231function needHeader(): void {
232  while (_line < doc_lines.length && !isHeader()) {
233    _line++;
234  }
235}
236
237function isFixTitle(): boolean {
238  return doc_lines[_line].trimStart().startsWith(CB_FIX);
239}
240
241/*
242 *
243 * parsing functions
244 *
245 */
246
247function makeFixTitle(): void {
248  while (_line < doc_lines.length && !isHeader() && !isFixTitle()) {
249    _line++;
250  }
251
252  if (isFixTitle()) {
253    const title = doc_lines[_line].split(CB_FIX)[1].trim();
254    fixTitles.set(recNum, escapeSym(title));
255  }
256}
257
258function makeRecipe(): void {
259  const line = doc_lines[_line];
260  recNum = Number(line.replace(/\D/g, ''));
261  console.log('cookBookMsg[ ' + recNum + ' ] = ' + STR_DLMTR + CL);
262  _line++;
263  mdText = [];
264  makeTag();
265  makeBody();
266  makeBad();
267  makeOk();
268  makeSee();
269
270  // emit .md file
271  const mdFileName = join('./md', 'recipe' + recNum + '.md');
272  writeFileSync(mdFileName, '', { flag: 'w' });
273  mdText.forEach((mdLine) => {
274    console.error('MD> ' + mdLine);
275    writeFileSync(mdFileName, mdLine + '\n', { flag: 'a+' });
276  });
277
278  console.log(STR_DLMTR + ';');
279  console.log('');
280}
281
282function makeTag(): void {
283  needHeader();
284  console.error('>>>TEG>>>: ' + _line + ' -> ' + doc_lines[_line]);
285  if (!doc_lines[_line].startsWith(CB_R)) {
286    return;
287  }
288  let line = doc_lines[_line].split(CB_R)[1];
289
290  // .split(':')[1] );
291  mdText.push('# ' + translateLine(line));
292  mdText.push('');
293
294  line = escapeSym(translateLine(line));
295  const teg = translateTeg(line);
296  const hdr = highlightCode(line);
297  console.log(hdr + T_BR + CL);
298  // .split(':')[1];
299  tegs[recNum] = teg;
300  _line++;
301}
302
303function makeBody(): string {
304  let body = '';
305  needHeader();
306  console.error('>>>BODY HDR>>>: ' + +_line + ' -> ' + doc_lines[_line]);
307  if (!doc_lines[_line].startsWith(CB_RULE)) {
308    return '';
309  }
310
311  let line = doc_lines[_line].trim();
312  const md_line = line;
313  line = line.replace(CB_RULE, '');
314  line = escapeSym(translateLine(line));
315  tegs[recNum] = tegs[recNum].trim() + ' (' + replaceAll(translateTeg(line), '"', '') + ')';
316  _line++;
317
318  // skip underline
319  _line++;
320  console.log(T_HR + T_BOLD + 'Rule' + T_END_BOLD + T_BR + CL);
321
322  // ("## Rule");
323  mdText.push(md_line.replace(CB_RULE, 'Rule'));
324  mdText.push('');
325  needHeader();
326  console.error('>>>BODY 2 HDR>>>: ' + +_line + ' -> ' + doc_lines[_line]);
327
328  if (doc_lines[_line].startsWith(CB_META)) {
329    _line++;
330    makeFixTitle();
331    needHeader();
332    console.error('>>>BODY 3 HDR>>>: ' + +_line + ' -> ' + doc_lines[_line]);
333  }
334
335  // line + 1
336  while (!isHeader() || doc_lines[_line].startsWith(CB_ERROR) || doc_lines[_line].startsWith(CB_WARNING)) {
337    // skip empty lines
338    let s = translateLine(doc_lines[_line]);
339
340    mdText.push(s);
341    s = highlightCode(s);
342    s = escapeSym(s);
343    console.log(s + CL);
344    body += s;
345    _line++;
346  }
347
348  console.log(T_BR + CL);
349  mdText.push('');
350
351  return body;
352}
353
354function makeBad(): string {
355  let badCode = '';
356
357  needHeader();
358  console.error('>>>makeBAD HDR>>>: ' + doc_lines[_line]);
359  if (!doc_lines[_line].startsWith(CB_BAD)) {
360    return '';
361  }
362  _line++;
363  // skip underline
364  _line++;
365
366  console.log(T_HR + T_BOLD + 'TypeScript' + T_END_BOLD + T_BR + CL);
367
368  mdText.push('## TypeScript');
369  mdText.push('');
370
371  while (_line < doc_lines.length && !isHeader()) {
372    // skip empty lines
373    let s = translateLine(doc_lines[_line]);
374    mdText.push(s);
375
376    s = highlightCode(s);
377    console.log(s + CL);
378
379    badCode += s;
380    _line++;
381  }
382
383  skipEmptyLines();
384  if (doc_lines[_line++].startsWith(CODE_BLOCK)) {
385    mdText.push('```');
386    console.log(T_CODE + CL);
387    while (_line < doc_lines.length && !isHeader()) {
388      mdText.push(doc_lines[_line]);
389      console.log(setNBSP(escapeSym(doc_lines[_line])) + T_BR + CL);
390      _line++;
391    }
392    console.log(T_END_CODE + T_BR + CL);
393
394    mdText.push('```');
395  }
396  mdText.push('');
397
398  return badCode;
399}
400
401function makeOk(): string {
402  let goodCode = '';
403
404  needHeader();
405  console.error('>>>makeOK HDR>>>: ' + doc_lines[_line]);
406  if (_line >= doc_lines.length || !doc_lines[_line].startsWith(CB_OK)) {
407    return '';
408  }
409  _line++;
410  // skip underline
411  _line++;
412  console.log(T_HR + T_BOLD + 'ArkTS' + T_END_BOLD + T_BR + CL);
413
414  mdText.push('## ArkTS');
415  mdText.push('');
416
417  while (_line < doc_lines.length && !isHeader()) {
418    // skip empty lines
419    let s = translateLine(doc_lines[_line]);
420
421    mdText.push(s);
422
423    s = highlightCode(s);
424    console.log(s + CL);
425
426    goodCode += s;
427    _line++;
428  }
429
430  skipEmptyLines();
431  if (doc_lines[_line++].startsWith(CODE_BLOCK)) {
432    console.log(T_CODE + CL);
433
434    mdText.push('```');
435
436    while (_line < doc_lines.length && !isHeader()) {
437      mdText.push(doc_lines[_line]);
438      console.log(setNBSP(escapeSym(doc_lines[_line])) + T_BR + CL);
439      _line++;
440    }
441    console.log(T_END_CODE + T_BR + CL);
442
443    mdText.push('```');
444  }
445
446  mdText.push('');
447
448  return goodCode;
449}
450
451function makeSee(): string {
452
453  /*
454   * mdText.push("## See also");
455   * mdText.push("");
456   */
457  const RECIPE = 'Recipe ';
458  console.error('>>> #' + recNum + ' PASSED: ' + doc_lines[_line]);
459  while (_line < doc_lines.length && !doc_lines[_line].startsWith('..')) {
460    let s = translateLine(doc_lines[_line]);
461
462    if (s.split(CB_REF)[1]) {
463      s = s.replace('*', '-');
464      s = s.replace(CB_REF, RECIPE);
465      s = s.replace('`R', '');
466      const ruleNum = Number(s.replace('`', '').split(RECIPE)[1]);
467      console.error('>>>RULE in SEE ' + ruleNum + ' ' + s.replace('`', '') + ' -> ' + ruleNames[ruleNum]);
468      s = s.replace('`', ':');
469      s += ' ' + ruleNames[ruleNum];
470    }
471
472    mdText.push(s);
473
474    if (doc_lines[_line].startsWith(CB_SEE)) {
475      _line++;
476    }
477    _line++;
478  }
479
480  mdText.push('');
481
482  return '';
483}
484
485/*
486 *
487 * Main routine
488 *
489 */
490let commandLineArgs = process.argv.slice(2);
491if (commandLineArgs.length === 0) {
492  console.error('>>> Command line error: no arguments');
493  process.exit(-1);
494}
495if (commandLineArgs[0] === '-md') {
496  commandLineArgs = process.argv.slice(3);
497}
498const inFileName = commandLineArgs[0];
499
500console.log(COPYRIGHT_HEADER);
501
502/*
503 * console.log("export const cookBookMsg: string[] = []; \n");
504 * console.log("export const cookBookTag: string[] = []; \n");
505 */
506console.log(CODE_PROLOGUE);
507syncReadFile(inFileName);
508
509for (recNum = 1; recNum < tegs.length; recNum++) {
510  console.log('cookBookTag[ ' + recNum + ' ] = ' + STR_DLMTR + (tegs[recNum] ? tegs[recNum] : '') + STR_DLMTR + ';');
511}
512
513console.log('\nexport const cookBookRefToFixTitle: Map<number, string> = new Map([');
514for (const num of fixTitles.keys()) {
515  console.log(`  [${num}, '${fixTitles.get(num)}'],`);
516}
517console.log(']);');
518