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 = ' '; 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