/*
* Copyright (c) 2022-2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
const COPYRIGHT_HEADER =
'/* \n\
* Copyright (c) 2022-2024 Huawei Device Co., Ltd. \n\
* Licensed under the Apache License, Version 2.0 (the "License"); \n\
* you may not use this file except in compliance with the License. \n\
* You may obtain a copy of the License at \n\
* \n\
* http://www.apache.org/licenses/LICENSE-2.0 \n\
* \n\
* Unless required by applicable law or agreed to in writing, software \n\
* distributed under the License is distributed on an "AS IS" BASIS, \n\
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n\
* See the License for the specific language governing permissions and \n\
* limitations under the License. \n\
*/ \n\
';
const CODE_PROLOGUE =
'export const cookBookMsg: string[] = [];\n\
export const cookBookTag: string[] = [];\n\
\n\
for( let i = 0; i <= 150; i++) {\n\
cookBookMsg[ i ] = \'\';\n\
}\n\
';
// HTML tags
const T_BR = '
';
const T_BOLD = '';
const T_END_BOLD = '';
const T_CODE = '';
const T_END_CODE = '
';
const T_NBSP = ' ';
const T_HR = '
';
// RST substititions
const CB_ = '|CB_';
const CB_R = '|CB_R|';
const CB_RULE = '|CB_RULE|';
const CB_BAD = '|CB_BAD|';
const CB_OK = '|CB_OK|';
// replace:: **Severity: error**
const CB_ERROR = '|CB_ERROR|';
// replace:: **Severity: warning**
const CB_WARNING = '|CB_WARNING|';
const CB_SEE = '|CB_SEE|';
const CB_REF = ':ref:';
const CB_META = '.. meta';
const CB_FIX = ':fix:';
const NEW_REC_HEADER = /.. _R\d\d\d:/;
// should be ".. code-block" but there is an error in some doc files
const CODE_BLOCK = '.. code';
let doc_lines: string[];
let _line: number;
let recNum: number;
const tegs: string[] = [];
const ruleNames: string[] = [];
let mdText: string[] = [];
const fixTitles: Map = new Map();
// continue line
const CL = ' \\';
const STR_DLMTR = '\'';
function syncReadFile(filename: string): string[] {
const contents = readFileSync(filename, 'utf-8');
doc_lines = contents.split(/\r?\n/);
// make table of rule names
_line = 0;
let ruleNum = -1;
while (_line < doc_lines.length) {
const line = doc_lines[_line];
if (NEW_REC_HEADER.test(line)) {
ruleNum = Number(line.replace(/\D/g, ''));
console.log('>>>>>>> START RULE ' + ruleNum + ':');
console.log(' NUMBER: ' + ruleNum);
}
if (doc_lines[_line].startsWith(CB_R)) {
let line = doc_lines[_line].split(CB_R)[1];
/*
* let tegNumStr = line.split(':')[0];
* let ruleNum = Number(tegNumStr.split('#')[1]);
*/
// line.split(':')[1];
ruleNames[ruleNum] = line;
_line++;
needHeader();
if (doc_lines[_line].startsWith(CB_RULE)) {
line = doc_lines[_line].trim().replace(CB_RULE, '').
trim();
ruleNames[ruleNum] = ruleNames[ruleNum] + ' (' + line + ')';
}
}
_line++;
}
// scan text
_line = 0;
while (_line < doc_lines.length) {
skipEmptyLines();
const line = doc_lines[_line];
if (NEW_REC_HEADER.test(line)) {
makeRecipe();
} else {
_line++;
}
}
return doc_lines;
}
/*
*
* utility functions
*
*/
function replaceAll(s: string, from: string, to: string): string {
const ss = s.split(from);
let outStr = '';
ss.forEach((line) => {
outStr += to + line;
});
// remove 1st 'to' substring
return outStr.replace(to, '');
}
function translateLine(s: string): string {
let line = s;
line = line.replace(CB_BAD, 'TypeScript');
line = line.replace(CB_OK, 'ArkTS');
/*
* line = line.replace( "|CB_R|", "Recipe");
* .. |CB_RULE| replace:: Rule
*/
line = line.replace(CB_ERROR, '**Severity: error**');
line = line.replace(CB_WARNING, '**Severity: warning**');
line = line.replace(CB_SEE, '## See also');
line = replaceAll(line, '|JS|', 'JavaScript');
// .. |LANG| replace:: {lang}
line = replaceAll(line, '|LANG|', 'ArkTS');
line = replaceAll(line, '|TS|', 'TypeScript');
return line;
}
function translateTeg(s: string): string {
return replaceAll(s, '``', '"').trim();
}
function highlightCode(s: string): string {
const ss = s.split('``');
let line = ss[0];
for (let i = 1; i < ss.length; i++) {
if (i % 2 === 0) {
line += T_END_CODE;
} else {
line += T_CODE;
}
line += ss[i];
}
return line;
}
function escapeSym(s: string): string {
const ss = replaceAll(s, '\'', '\\\'');
return replaceAll(ss, '"', '\\"');
}
function setNBSP(s: string): string {
let ss = '';
let flag = true;
for (const ch of s) {
if (ch !== ' ' && ch !== '\t') {
flag = false;
}
if (flag && ch === ' ') {
ss += T_NBSP;
} else if (flag && ch === '\t') {
ss += T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP;
} else {
ss += ch;
}
}
return ss;
}
function skipEmptyLines(): void {
while (_line < doc_lines.length) {
let s = doc_lines[_line];
s = s.trim();
if (s !== '') {
break;
}
_line++;
}
}
function isHeader(): boolean {
return doc_lines[_line].startsWith(CB_) || doc_lines[_line].startsWith('..');
}
function needHeader(): void {
while (_line < doc_lines.length && !isHeader()) {
_line++;
}
}
function isFixTitle(): boolean {
return doc_lines[_line].trimStart().startsWith(CB_FIX);
}
/*
*
* parsing functions
*
*/
function makeFixTitle(): void {
while (_line < doc_lines.length && !isHeader() && !isFixTitle()) {
_line++;
}
if (isFixTitle()) {
const title = doc_lines[_line].split(CB_FIX)[1].trim();
fixTitles.set(recNum, escapeSym(title));
}
}
function makeRecipe(): void {
const line = doc_lines[_line];
recNum = Number(line.replace(/\D/g, ''));
console.log('cookBookMsg[ ' + recNum + ' ] = ' + STR_DLMTR + CL);
_line++;
mdText = [];
makeTag();
makeBody();
makeBad();
makeOk();
makeSee();
// emit .md file
const mdFileName = join('./md', 'recipe' + recNum + '.md');
writeFileSync(mdFileName, '', { flag: 'w' });
mdText.forEach((mdLine) => {
console.error('MD> ' + mdLine);
writeFileSync(mdFileName, mdLine + '\n', { flag: 'a+' });
});
console.log(STR_DLMTR + ';');
console.log('');
}
function makeTag(): void {
needHeader();
console.error('>>>TEG>>>: ' + _line + ' -> ' + doc_lines[_line]);
if (!doc_lines[_line].startsWith(CB_R)) {
return;
}
let line = doc_lines[_line].split(CB_R)[1];
// .split(':')[1] );
mdText.push('# ' + translateLine(line));
mdText.push('');
line = escapeSym(translateLine(line));
const teg = translateTeg(line);
const hdr = highlightCode(line);
console.log(hdr + T_BR + CL);
// .split(':')[1];
tegs[recNum] = teg;
_line++;
}
function makeBody(): string {
let body = '';
needHeader();
console.error('>>>BODY HDR>>>: ' + +_line + ' -> ' + doc_lines[_line]);
if (!doc_lines[_line].startsWith(CB_RULE)) {
return '';
}
let line = doc_lines[_line].trim();
const md_line = line;
line = line.replace(CB_RULE, '');
line = escapeSym(translateLine(line));
tegs[recNum] = tegs[recNum].trim() + ' (' + replaceAll(translateTeg(line), '"', '') + ')';
_line++;
// skip underline
_line++;
console.log(T_HR + T_BOLD + 'Rule' + T_END_BOLD + T_BR + CL);
// ("## Rule");
mdText.push(md_line.replace(CB_RULE, 'Rule'));
mdText.push('');
needHeader();
console.error('>>>BODY 2 HDR>>>: ' + +_line + ' -> ' + doc_lines[_line]);
if (doc_lines[_line].startsWith(CB_META)) {
_line++;
makeFixTitle();
needHeader();
console.error('>>>BODY 3 HDR>>>: ' + +_line + ' -> ' + doc_lines[_line]);
}
// line + 1
while (!isHeader() || doc_lines[_line].startsWith(CB_ERROR) || doc_lines[_line].startsWith(CB_WARNING)) {
// skip empty lines
let s = translateLine(doc_lines[_line]);
mdText.push(s);
s = highlightCode(s);
s = escapeSym(s);
console.log(s + CL);
body += s;
_line++;
}
console.log(T_BR + CL);
mdText.push('');
return body;
}
function makeBad(): string {
let badCode = '';
needHeader();
console.error('>>>makeBAD HDR>>>: ' + doc_lines[_line]);
if (!doc_lines[_line].startsWith(CB_BAD)) {
return '';
}
_line++;
// skip underline
_line++;
console.log(T_HR + T_BOLD + 'TypeScript' + T_END_BOLD + T_BR + CL);
mdText.push('## TypeScript');
mdText.push('');
while (_line < doc_lines.length && !isHeader()) {
// skip empty lines
let s = translateLine(doc_lines[_line]);
mdText.push(s);
s = highlightCode(s);
console.log(s + CL);
badCode += s;
_line++;
}
skipEmptyLines();
if (doc_lines[_line++].startsWith(CODE_BLOCK)) {
mdText.push('```');
console.log(T_CODE + CL);
while (_line < doc_lines.length && !isHeader()) {
mdText.push(doc_lines[_line]);
console.log(setNBSP(escapeSym(doc_lines[_line])) + T_BR + CL);
_line++;
}
console.log(T_END_CODE + T_BR + CL);
mdText.push('```');
}
mdText.push('');
return badCode;
}
function makeOk(): string {
let goodCode = '';
needHeader();
console.error('>>>makeOK HDR>>>: ' + doc_lines[_line]);
if (_line >= doc_lines.length || !doc_lines[_line].startsWith(CB_OK)) {
return '';
}
_line++;
// skip underline
_line++;
console.log(T_HR + T_BOLD + 'ArkTS' + T_END_BOLD + T_BR + CL);
mdText.push('## ArkTS');
mdText.push('');
while (_line < doc_lines.length && !isHeader()) {
// skip empty lines
let s = translateLine(doc_lines[_line]);
mdText.push(s);
s = highlightCode(s);
console.log(s + CL);
goodCode += s;
_line++;
}
skipEmptyLines();
if (doc_lines[_line++].startsWith(CODE_BLOCK)) {
console.log(T_CODE + CL);
mdText.push('```');
while (_line < doc_lines.length && !isHeader()) {
mdText.push(doc_lines[_line]);
console.log(setNBSP(escapeSym(doc_lines[_line])) + T_BR + CL);
_line++;
}
console.log(T_END_CODE + T_BR + CL);
mdText.push('```');
}
mdText.push('');
return goodCode;
}
function makeSee(): string {
/*
* mdText.push("## See also");
* mdText.push("");
*/
const RECIPE = 'Recipe ';
console.error('>>> #' + recNum + ' PASSED: ' + doc_lines[_line]);
while (_line < doc_lines.length && !doc_lines[_line].startsWith('..')) {
let s = translateLine(doc_lines[_line]);
if (s.split(CB_REF)[1]) {
s = s.replace('*', '-');
s = s.replace(CB_REF, RECIPE);
s = s.replace('`R', '');
const ruleNum = Number(s.replace('`', '').split(RECIPE)[1]);
console.error('>>>RULE in SEE ' + ruleNum + ' ' + s.replace('`', '') + ' -> ' + ruleNames[ruleNum]);
s = s.replace('`', ':');
s += ' ' + ruleNames[ruleNum];
}
mdText.push(s);
if (doc_lines[_line].startsWith(CB_SEE)) {
_line++;
}
_line++;
}
mdText.push('');
return '';
}
/*
*
* Main routine
*
*/
let commandLineArgs = process.argv.slice(2);
if (commandLineArgs.length === 0) {
console.error('>>> Command line error: no arguments');
process.exit(-1);
}
if (commandLineArgs[0] === '-md') {
commandLineArgs = process.argv.slice(3);
}
const inFileName = commandLineArgs[0];
console.log(COPYRIGHT_HEADER);
/*
* console.log("export const cookBookMsg: string[] = []; \n");
* console.log("export const cookBookTag: string[] = []; \n");
*/
console.log(CODE_PROLOGUE);
syncReadFile(inFileName);
for (recNum = 1; recNum < tegs.length; recNum++) {
console.log('cookBookTag[ ' + recNum + ' ] = ' + STR_DLMTR + (tegs[recNum] ? tegs[recNum] : '') + STR_DLMTR + ';');
}
console.log('\nexport const cookBookRefToFixTitle: Map = new Map([');
for (const num of fixTitles.keys()) {
console.log(` [${num}, '${fixTitles.get(num)}'],`);
}
console.log(']);');