2016-01-27 01:07:11 +01:00
|
|
|
/*
|
|
|
|
This file is part of TALER
|
|
|
|
(C) 2016 GNUnet e.V.
|
|
|
|
|
|
|
|
TALER is free software; you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
|
|
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
2016-07-07 17:59:29 +02:00
|
|
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
2016-01-27 01:07:11 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate .po file from list of source files.
|
2016-01-27 01:57:34 +01:00
|
|
|
*
|
|
|
|
* Note that duplicate message IDs are NOT merged, to get the same output as
|
|
|
|
* you would from xgettext, just run msguniq.
|
2016-03-01 19:46:20 +01:00
|
|
|
*
|
|
|
|
* @author Florian Dold
|
2016-01-27 01:07:11 +01:00
|
|
|
*/
|
|
|
|
|
2021-03-27 13:55:15 +01:00
|
|
|
/**
|
|
|
|
* Imports.
|
|
|
|
*/
|
|
|
|
import { readFileSync } from "fs";
|
2016-01-27 01:07:11 +01:00
|
|
|
import * as ts from "typescript";
|
|
|
|
|
|
|
|
function wordwrap(str: string, width: number = 80): string[] {
|
2021-03-27 13:55:15 +01:00
|
|
|
var regex = ".{1," + width + "}(\\s|$)|\\S+(\\s|$)";
|
|
|
|
return str.match(RegExp(regex, "g"));
|
2016-01-27 01:07:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export function processFile(sourceFile: ts.SourceFile) {
|
|
|
|
processNode(sourceFile);
|
|
|
|
let lastTokLine = 0;
|
|
|
|
let preLastTokLine = 0;
|
|
|
|
|
|
|
|
function getTemplate(node: ts.Node): string {
|
|
|
|
switch (node.kind) {
|
|
|
|
case ts.SyntaxKind.FirstTemplateToken:
|
|
|
|
return (<any>node).text;
|
|
|
|
case ts.SyntaxKind.TemplateExpression:
|
|
|
|
let te = <ts.TemplateExpression>node;
|
|
|
|
let textFragments = [te.head.text];
|
|
|
|
for (let tsp of te.templateSpans) {
|
2021-03-27 13:55:15 +01:00
|
|
|
textFragments.push(`%${(textFragments.length - 1) / 2 + 1}$s`);
|
2016-11-16 04:02:02 +01:00
|
|
|
textFragments.push(tsp.literal.text.replace(/%/g, "%%"));
|
2016-01-27 01:07:11 +01:00
|
|
|
}
|
2021-03-27 13:55:15 +01:00
|
|
|
return textFragments.join("");
|
2016-01-27 01:07:11 +01:00
|
|
|
default:
|
|
|
|
return "(pogen.ts: unable to parse)";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-26 22:46:38 +02:00
|
|
|
function getComment(node: ts.Node): string {
|
|
|
|
let lc = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
|
2016-01-27 01:07:11 +01:00
|
|
|
let lastComments;
|
|
|
|
for (let l = preLastTokLine; l < lastTokLine; l++) {
|
|
|
|
let pos = ts.getPositionOfLineAndCharacter(sourceFile, l, 0);
|
|
|
|
let comments = ts.getTrailingCommentRanges(sourceFile.text, pos);
|
|
|
|
if (comments) {
|
|
|
|
lastComments = comments;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!lastComments) {
|
|
|
|
return;
|
|
|
|
}
|
2021-03-27 13:55:15 +01:00
|
|
|
let candidate = lastComments[lastComments.length - 1];
|
|
|
|
let candidateEndLine = ts.getLineAndCharacterOfPosition(
|
|
|
|
sourceFile,
|
|
|
|
candidate.end,
|
|
|
|
).line;
|
2016-01-27 01:57:34 +01:00
|
|
|
if (candidateEndLine != lc.line - 1) {
|
2016-01-27 01:07:11 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
let text = sourceFile.text.slice(candidate.pos, candidate.end);
|
|
|
|
switch (candidate.kind) {
|
|
|
|
case ts.SyntaxKind.SingleLineCommentTrivia:
|
2016-01-27 01:19:20 +01:00
|
|
|
// Remove comment leader
|
2016-01-27 01:07:11 +01:00
|
|
|
text = text.replace(/^[/][/]\s*/, "");
|
|
|
|
break;
|
|
|
|
case ts.SyntaxKind.MultiLineCommentTrivia:
|
2016-01-27 01:19:20 +01:00
|
|
|
// Remove comment leader and trailer,
|
|
|
|
// handling white space just like xgettext.
|
2016-01-27 01:07:11 +01:00
|
|
|
text = text
|
2021-03-27 13:55:15 +01:00
|
|
|
.replace(/^[/][*](\s*?\n|\s*)?/, "")
|
|
|
|
.replace(/(\n[ \t]*?)?[*][/]$/, "");
|
2016-01-27 01:07:11 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
2016-09-26 22:46:38 +02:00
|
|
|
function getPath(node: ts.Node): string[] {
|
|
|
|
switch (node.kind) {
|
|
|
|
case ts.SyntaxKind.PropertyAccessExpression:
|
|
|
|
let pae = <ts.PropertyAccessExpression>node;
|
|
|
|
return Array.prototype.concat(getPath(pae.expression), [pae.name.text]);
|
|
|
|
case ts.SyntaxKind.Identifier:
|
|
|
|
let id = <ts.Identifier>node;
|
|
|
|
return [id.text];
|
|
|
|
}
|
|
|
|
return ["(other)"];
|
|
|
|
}
|
|
|
|
|
|
|
|
function arrayEq<T>(a1: T[], a2: T[]) {
|
|
|
|
if (a1.length != a2.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (let i = 0; i < a1.length; i++) {
|
|
|
|
if (a1[i] != a2[i]) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TemplateResult {
|
|
|
|
comment: string;
|
|
|
|
path: string[];
|
|
|
|
template: string;
|
|
|
|
line: number;
|
|
|
|
}
|
|
|
|
|
2021-03-27 13:55:15 +01:00
|
|
|
function processTaggedTemplateExpression(
|
|
|
|
tte: ts.TaggedTemplateExpression,
|
|
|
|
): TemplateResult {
|
2016-09-26 22:46:38 +02:00
|
|
|
let lc = ts.getLineAndCharacterOfPosition(sourceFile, tte.pos);
|
|
|
|
if (lc.line != lastTokLine) {
|
|
|
|
preLastTokLine = lastTokLine;
|
|
|
|
lastTokLine = lc.line;
|
|
|
|
}
|
2021-03-27 13:55:15 +01:00
|
|
|
let path = getPath(tte.tag);
|
2016-09-26 22:46:38 +02:00
|
|
|
let res: TemplateResult = {
|
|
|
|
path,
|
|
|
|
line: lc.line,
|
|
|
|
comment: getComment(tte),
|
|
|
|
template: getTemplate(tte.template).replace(/"/g, '\\"'),
|
|
|
|
};
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatMsgComment(line: number, comment?: string) {
|
|
|
|
if (comment) {
|
2021-03-27 13:55:15 +01:00
|
|
|
for (let cl of comment.split("\n")) {
|
2016-09-26 22:46:38 +02:00
|
|
|
console.log(`#. ${cl}`);
|
|
|
|
}
|
|
|
|
}
|
2021-03-27 13:55:15 +01:00
|
|
|
console.log(`#: ${sourceFile.fileName}:${line + 1}`);
|
2016-09-26 22:46:38 +02:00
|
|
|
console.log(`#, c-format`);
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatMsgLine(head: string, msg: string) {
|
|
|
|
// Do escaping, wrap break at newlines
|
|
|
|
let parts = msg
|
2021-03-27 13:55:15 +01:00
|
|
|
.match(/(.*\n|.+$)/g)
|
|
|
|
.map((x) => x.replace(/\n/g, "\\n"))
|
|
|
|
.map((p) => wordwrap(p))
|
|
|
|
.reduce((a, b) => a.concat(b));
|
2016-09-26 22:46:38 +02:00
|
|
|
if (parts.length == 1) {
|
|
|
|
console.log(`${head} "${parts[0]}"`);
|
|
|
|
} else {
|
|
|
|
console.log(`${head} ""`);
|
|
|
|
for (let p of parts) {
|
|
|
|
console.log(`"${p}"`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-16 04:02:02 +01:00
|
|
|
|
|
|
|
function getJsxElementPath(node: ts.Node) {
|
|
|
|
let path;
|
|
|
|
let process = (childNode) => {
|
|
|
|
switch (childNode.kind) {
|
2021-03-27 13:55:15 +01:00
|
|
|
case ts.SyntaxKind.JsxOpeningElement: {
|
2016-11-16 04:02:02 +01:00
|
|
|
let e = childNode as ts.JsxOpeningElement;
|
2021-03-27 13:55:15 +01:00
|
|
|
return (path = getPath(e.tagName));
|
2016-11-16 04:02:02 +01:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
ts.forEachChild(node, process);
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
function translateJsxExpression(node: ts.Node, h) {
|
2021-03-27 13:55:15 +01:00
|
|
|
switch (node.kind) {
|
|
|
|
case ts.SyntaxKind.StringLiteral: {
|
|
|
|
let e = node as ts.StringLiteral;
|
|
|
|
return e.text;
|
2016-11-16 04:02:02 +01:00
|
|
|
}
|
2021-03-27 13:55:15 +01:00
|
|
|
default:
|
|
|
|
return `%${h[0]++}$s`;
|
|
|
|
}
|
2016-11-16 04:02:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function trim(s) {
|
|
|
|
return s.replace(/^[ \n\t]*/, "").replace(/[ \n\t]*$/, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
function getJsxContent(node: ts.Node) {
|
|
|
|
let fragments = [];
|
|
|
|
let holeNum = [1];
|
|
|
|
let process = (childNode) => {
|
|
|
|
switch (childNode.kind) {
|
2021-03-27 13:55:15 +01:00
|
|
|
case ts.SyntaxKind.JsxText: {
|
2016-11-16 04:02:02 +01:00
|
|
|
let e = childNode as ts.JsxText;
|
2018-02-22 10:25:11 +01:00
|
|
|
let s = e.getFullText();
|
|
|
|
let t = s.split("\n").map(trim).join(" ");
|
2018-02-22 11:49:13 +01:00
|
|
|
if (s[0] === " ") {
|
|
|
|
t = " " + t;
|
|
|
|
}
|
|
|
|
if (s[s.length - 1] === " ") {
|
|
|
|
t = t + " ";
|
|
|
|
}
|
2016-11-16 04:02:02 +01:00
|
|
|
fragments.push(t);
|
|
|
|
}
|
|
|
|
case ts.SyntaxKind.JsxOpeningElement:
|
|
|
|
break;
|
2016-11-23 01:00:28 +01:00
|
|
|
case ts.SyntaxKind.JsxElement:
|
|
|
|
fragments.push(`%${holeNum[0]++}$s`);
|
|
|
|
break;
|
2021-03-27 13:55:15 +01:00
|
|
|
case ts.SyntaxKind.JsxExpression: {
|
2016-11-16 04:02:02 +01:00
|
|
|
let e = childNode as ts.JsxExpression;
|
|
|
|
fragments.push(translateJsxExpression(e.expression, holeNum));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ts.SyntaxKind.JsxClosingElement:
|
|
|
|
break;
|
|
|
|
default:
|
2021-03-27 13:55:15 +01:00
|
|
|
let lc = ts.getLineAndCharacterOfPosition(
|
|
|
|
childNode.getSourceFile(),
|
|
|
|
childNode.getStart(),
|
|
|
|
);
|
|
|
|
console.error(
|
|
|
|
`unrecognized syntax in JSX Element ${
|
|
|
|
ts.SyntaxKind[childNode.kind]
|
|
|
|
} (${childNode.getSourceFile().fileName}:${lc.line + 1}:${
|
|
|
|
lc.character + 1
|
|
|
|
}`,
|
|
|
|
);
|
2016-11-16 04:02:02 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
ts.forEachChild(node, process);
|
2018-02-22 10:25:11 +01:00
|
|
|
return fragments.join("").trim().replace(/ +/g, " ");
|
2016-11-16 04:02:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function getJsxSingular(node: ts.Node) {
|
|
|
|
let res;
|
|
|
|
let process = (childNode) => {
|
|
|
|
switch (childNode.kind) {
|
2021-03-27 13:55:15 +01:00
|
|
|
case ts.SyntaxKind.JsxElement: {
|
2016-11-16 04:02:02 +01:00
|
|
|
let path = getJsxElementPath(childNode);
|
|
|
|
if (arrayEq(path, ["i18n", "TranslateSingular"])) {
|
|
|
|
res = getJsxContent(childNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
ts.forEachChild(node, process);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getJsxPlural(node: ts.Node) {
|
|
|
|
let res;
|
|
|
|
let process = (childNode) => {
|
|
|
|
switch (childNode.kind) {
|
2021-03-27 13:55:15 +01:00
|
|
|
case ts.SyntaxKind.JsxElement: {
|
2016-11-16 04:02:02 +01:00
|
|
|
let path = getJsxElementPath(childNode);
|
|
|
|
if (arrayEq(path, ["i18n", "TranslatePlural"])) {
|
|
|
|
res = getJsxContent(childNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
ts.forEachChild(node, process);
|
|
|
|
return res;
|
|
|
|
}
|
2016-09-26 22:46:38 +02:00
|
|
|
|
2016-01-27 01:07:11 +01:00
|
|
|
function processNode(node: ts.Node) {
|
|
|
|
switch (node.kind) {
|
2016-11-16 04:02:02 +01:00
|
|
|
case ts.SyntaxKind.JsxElement:
|
|
|
|
let path = getJsxElementPath(node);
|
|
|
|
if (arrayEq(path, ["i18n", "Translate"])) {
|
|
|
|
let content = getJsxContent(node);
|
2021-03-27 13:55:15 +01:00
|
|
|
let { line } = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
|
2016-11-16 04:02:02 +01:00
|
|
|
let comment = getComment(node);
|
|
|
|
formatMsgComment(line, comment);
|
|
|
|
formatMsgLine("msgid", content);
|
2016-11-19 00:52:50 +01:00
|
|
|
console.log(`msgstr ""`);
|
2016-11-16 04:02:02 +01:00
|
|
|
console.log();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (arrayEq(path, ["i18n", "TranslateSwitch"])) {
|
2021-03-27 13:55:15 +01:00
|
|
|
let { line } = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
|
2016-11-16 04:02:02 +01:00
|
|
|
let comment = getComment(node);
|
|
|
|
formatMsgComment(line, comment);
|
|
|
|
let singularForm = getJsxSingular(node);
|
|
|
|
if (!singularForm) {
|
|
|
|
console.error("singular form missing");
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
let pluralForm = getJsxPlural(node);
|
|
|
|
if (!pluralForm) {
|
|
|
|
console.error("plural form missing");
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
formatMsgLine("msgid", singularForm);
|
|
|
|
formatMsgLine("msgid_plural", pluralForm);
|
|
|
|
console.log(`msgstr[0] ""`);
|
|
|
|
console.log(`msgstr[1] ""`);
|
|
|
|
console.log();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
2021-03-27 13:55:15 +01:00
|
|
|
case ts.SyntaxKind.CallExpression: {
|
2016-09-26 22:46:38 +02:00
|
|
|
// might be i18n.plural(i18n[.X]`...`, i18n[.X]`...`)
|
|
|
|
let ce = <ts.CallExpression>node;
|
|
|
|
let path = getPath(ce.expression);
|
|
|
|
if (!arrayEq(path, ["i18n", "plural"])) {
|
|
|
|
break;
|
2016-01-27 01:07:11 +01:00
|
|
|
}
|
2016-09-26 22:46:38 +02:00
|
|
|
if (ce.arguments[0].kind != ts.SyntaxKind.TaggedTemplateExpression) {
|
|
|
|
break;
|
2016-01-27 01:07:11 +01:00
|
|
|
}
|
2016-09-26 22:46:38 +02:00
|
|
|
if (ce.arguments[1].kind != ts.SyntaxKind.TaggedTemplateExpression) {
|
|
|
|
break;
|
2016-01-27 01:07:11 +01:00
|
|
|
}
|
2021-03-27 13:55:15 +01:00
|
|
|
let { line } = ts.getLineAndCharacterOfPosition(sourceFile, ce.pos);
|
|
|
|
let t1 = processTaggedTemplateExpression(
|
|
|
|
<ts.TaggedTemplateExpression>ce.arguments[0],
|
|
|
|
);
|
|
|
|
let t2 = processTaggedTemplateExpression(
|
|
|
|
<ts.TaggedTemplateExpression>ce.arguments[1],
|
|
|
|
);
|
2016-09-26 22:53:41 +02:00
|
|
|
let comment = getComment(ce);
|
2016-09-26 22:46:38 +02:00
|
|
|
|
|
|
|
formatMsgComment(line, comment);
|
|
|
|
formatMsgLine("msgid", t1.template);
|
|
|
|
formatMsgLine("msgid_plural", t2.template);
|
|
|
|
console.log(`msgstr[0] ""`);
|
|
|
|
console.log(`msgstr[1] ""`);
|
|
|
|
console.log();
|
|
|
|
|
|
|
|
// Important: no processing for child i18n expressions here
|
|
|
|
return;
|
|
|
|
}
|
2021-03-27 13:55:15 +01:00
|
|
|
case ts.SyntaxKind.TaggedTemplateExpression: {
|
2016-09-26 22:46:38 +02:00
|
|
|
let tte = <ts.TaggedTemplateExpression>node;
|
2021-03-27 13:55:15 +01:00
|
|
|
let { comment, template, line, path } = processTaggedTemplateExpression(
|
|
|
|
tte,
|
|
|
|
);
|
2016-09-26 22:46:38 +02:00
|
|
|
if (path[0] != "i18n") {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
formatMsgComment(line, comment);
|
|
|
|
formatMsgLine("msgid", template);
|
2016-01-27 01:07:11 +01:00
|
|
|
console.log(`msgstr ""`);
|
|
|
|
console.log();
|
|
|
|
break;
|
2016-09-26 22:46:38 +02:00
|
|
|
}
|
2016-01-27 01:07:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ts.forEachChild(node, processNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-27 14:02:29 +01:00
|
|
|
export function main() {
|
2021-03-27 13:55:15 +01:00
|
|
|
const configPath = ts.findConfigFile(
|
|
|
|
/*searchPath*/ "./",
|
|
|
|
ts.sys.fileExists,
|
|
|
|
"tsconfig.json",
|
|
|
|
);
|
|
|
|
if (!configPath) {
|
|
|
|
throw new Error("Could not find a valid 'tsconfig.json'.");
|
|
|
|
}
|
2016-01-27 01:07:11 +01:00
|
|
|
|
2021-03-27 13:55:15 +01:00
|
|
|
const cmdline = ts.getParsedCommandLineOfConfigFile(
|
|
|
|
configPath,
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
fileExists: ts.sys.fileExists,
|
|
|
|
getCurrentDirectory: ts.sys.getCurrentDirectory,
|
|
|
|
onUnRecoverableConfigFileDiagnostic: (e) => console.log(e),
|
|
|
|
readDirectory: ts.sys.readDirectory,
|
|
|
|
readFile: ts.sys.readFile,
|
|
|
|
useCaseSensitiveFileNames: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const fileNames = cmdline.fileNames;
|
|
|
|
|
|
|
|
fileNames.sort();
|
|
|
|
|
|
|
|
const outChunks: string[] = [];
|
|
|
|
|
|
|
|
outChunks.push(`# SOME DESCRIPTIVE TITLE.
|
2016-01-27 01:07:11 +01:00
|
|
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
|
|
# This file is distributed under the same license as the PACKAGE package.
|
|
|
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
|
|
#
|
|
|
|
#, fuzzy
|
|
|
|
msgid ""
|
|
|
|
msgstr ""
|
|
|
|
"Project-Id-Version: PACKAGE VERSION\\n"
|
|
|
|
"Report-Msgid-Bugs-To: \\n"
|
2016-11-23 00:13:35 +01:00
|
|
|
"POT-Creation-Date: 2016-11-23 00:00+0100\\n"
|
2016-01-27 01:07:11 +01:00
|
|
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
|
|
|
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
|
|
|
|
"Language-Team: LANGUAGE <LL@li.org>\\n"
|
|
|
|
"Language: \\n"
|
|
|
|
"MIME-Version: 1.0\\n"
|
2016-02-22 19:08:16 +01:00
|
|
|
"Content-Type: text/plain; charset=UTF-8\\n"
|
2016-01-27 01:07:11 +01:00
|
|
|
"Content-Transfer-Encoding: 8bit\\n"`);
|
|
|
|
|
2021-03-27 13:55:15 +01:00
|
|
|
fileNames.forEach((fileName) => {
|
|
|
|
let sourceFile = ts.createSourceFile(
|
|
|
|
fileName,
|
|
|
|
readFileSync(fileName).toString(),
|
|
|
|
ts.ScriptTarget.ES2016,
|
|
|
|
/*setParentNodes */ true,
|
|
|
|
);
|
|
|
|
processFile(sourceFile);
|
|
|
|
});
|
|
|
|
|
|
|
|
const out = outChunks.join("");
|
|
|
|
console.log(out);
|
|
|
|
}
|