pogen WIP

This commit is contained in:
Florian Dold 2021-03-27 13:55:15 +01:00
parent 35bbe6af2d
commit c26a41ce70
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
9 changed files with 186 additions and 72 deletions

View File

@ -0,0 +1,11 @@
{
"name": "proj1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,27 @@
{
"compileOnSave": true,
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": false,
"target": "ES6",
"module": "ESNext",
"moduleResolution": "node",
"sourceMap": true,
"lib": ["es6"],
"types": ["node"],
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strict": true,
"strictPropertyInitialization": false,
"outDir": "lib",
"noImplicitAny": true,
"noImplicitThis": true,
"incremental": true,
"esModuleInterop": true,
"importHelpers": true,
"rootDir": "./src",
"typeRoots": ["./node_modules/@types"]
},
"include": ["src/**/*"]
}

View File

@ -19,9 +19,10 @@ It has multiple lines, and a trailing empty line.
*/ */
console.log(/*lol*/i18n.foo`Hello7,${123} World${42}`); console.log(/*lol*/i18n.foo`Hello7,${123} World${42}`);
// @ts-expect-error
i18n.plural(i18n`one ${"foo"}`, i18`many ${"bar"}`); i18n.plural(i18n`one ${"foo"}`, i18`many ${"bar"}`);
// @ts-expect-error
i18n.plural(i18n.foo`one bla ${"foo"}`, i18.foo`many bla ${"bar"}`); i18n.plural(i18n.foo`one bla ${"foo"}`, i18.foo`many bla ${"bar"}`);
let x = 42; let x = 42;

View File

@ -1,7 +1,9 @@
{ {
"name": "@gnu-taler/pogen", "name": "@gnu-taler/pogen",
"version": "0.0.5", "version": "0.0.5",
"main": "bin/pogen.js", "bin": {
"pogen": "lib/pogen.js"
},
"author": "Florian Dold", "author": "Florian Dold",
"license": "GPL-2.0+", "license": "GPL-2.0+",
"scripts": { "scripts": {

32
packages/pogen/po2.js Normal file
View File

@ -0,0 +1,32 @@
const ts = require("typescript");
const configPath = ts.findConfigFile(
/*searchPath*/ "./",
ts.sys.fileExists,
"tsconfig.json"
);
if (!configPath) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}
console.log(configPath);
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,
})
console.log(cmdline);
const prog = ts.createProgram({
options: cmdline.options,
rootNames: cmdline.fileNames,
});
const allFiles = prog.getSourceFiles();
console.log(allFiles.map(x => x.path));

View File

@ -14,7 +14,6 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
/** /**
* Generate .po file from list of source files. * Generate .po file from list of source files.
* *
@ -24,15 +23,15 @@
* @author Florian Dold * @author Florian Dold
*/ */
"use strict"; /**
* Imports.
*/
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import * as ts from "typescript"; import * as ts from "typescript";
function wordwrap(str: string, width: number = 80): string[] { function wordwrap(str: string, width: number = 80): string[] {
var regex = '.{1,' + width + '}(\\s|$)|\\S+(\\s|$)'; var regex = ".{1," + width + "}(\\s|$)|\\S+(\\s|$)";
return str.match(RegExp(regex, 'g')); return str.match(RegExp(regex, "g"));
} }
export function processFile(sourceFile: ts.SourceFile) { export function processFile(sourceFile: ts.SourceFile) {
@ -51,7 +50,7 @@ export function processFile(sourceFile: ts.SourceFile) {
textFragments.push(`%${(textFragments.length - 1) / 2 + 1}$s`); textFragments.push(`%${(textFragments.length - 1) / 2 + 1}$s`);
textFragments.push(tsp.literal.text.replace(/%/g, "%%")); textFragments.push(tsp.literal.text.replace(/%/g, "%%"));
} }
return textFragments.join(''); return textFragments.join("");
default: default:
return "(pogen.ts: unable to parse)"; return "(pogen.ts: unable to parse)";
} }
@ -71,7 +70,10 @@ export function processFile(sourceFile: ts.SourceFile) {
return; return;
} }
let candidate = lastComments[lastComments.length - 1]; let candidate = lastComments[lastComments.length - 1];
let candidateEndLine = ts.getLineAndCharacterOfPosition(sourceFile, candidate.end).line; let candidateEndLine = ts.getLineAndCharacterOfPosition(
sourceFile,
candidate.end,
).line;
if (candidateEndLine != lc.line - 1) { if (candidateEndLine != lc.line - 1) {
return; return;
} }
@ -123,13 +125,15 @@ export function processFile(sourceFile: ts.SourceFile) {
line: number; line: number;
} }
function processTaggedTemplateExpression(tte: ts.TaggedTemplateExpression): TemplateResult { function processTaggedTemplateExpression(
tte: ts.TaggedTemplateExpression,
): TemplateResult {
let lc = ts.getLineAndCharacterOfPosition(sourceFile, tte.pos); let lc = ts.getLineAndCharacterOfPosition(sourceFile, tte.pos);
if (lc.line != lastTokLine) { if (lc.line != lastTokLine) {
preLastTokLine = lastTokLine; preLastTokLine = lastTokLine;
lastTokLine = lc.line; lastTokLine = lc.line;
} }
let path = getPath(tte.tag) let path = getPath(tte.tag);
let res: TemplateResult = { let res: TemplateResult = {
path, path,
line: lc.line, line: lc.line,
@ -141,7 +145,7 @@ export function processFile(sourceFile: ts.SourceFile) {
function formatMsgComment(line: number, comment?: string) { function formatMsgComment(line: number, comment?: string) {
if (comment) { if (comment) {
for (let cl of comment.split('\n')) { for (let cl of comment.split("\n")) {
console.log(`#. ${cl}`); console.log(`#. ${cl}`);
} }
} }
@ -153,7 +157,7 @@ export function processFile(sourceFile: ts.SourceFile) {
// Do escaping, wrap break at newlines // Do escaping, wrap break at newlines
let parts = msg let parts = msg
.match(/(.*\n|.+$)/g) .match(/(.*\n|.+$)/g)
.map((x) => x.replace(/\n/g, '\\n')) .map((x) => x.replace(/\n/g, "\\n"))
.map((p) => wordwrap(p)) .map((p) => wordwrap(p))
.reduce((a, b) => a.concat(b)); .reduce((a, b) => a.concat(b));
if (parts.length == 1) { if (parts.length == 1) {
@ -166,18 +170,13 @@ export function processFile(sourceFile: ts.SourceFile) {
} }
} }
interface JsxProcessingContext {
}
function getJsxElementPath(node: ts.Node) { function getJsxElementPath(node: ts.Node) {
let path; let path;
let process = (childNode) => { let process = (childNode) => {
switch (childNode.kind) { switch (childNode.kind) {
case ts.SyntaxKind.JsxOpeningElement: case ts.SyntaxKind.JsxOpeningElement: {
{
let e = childNode as ts.JsxOpeningElement; let e = childNode as ts.JsxOpeningElement;
return path = getPath(e.tagName); return (path = getPath(e.tagName));
} }
default: default:
break; break;
@ -189,8 +188,7 @@ export function processFile(sourceFile: ts.SourceFile) {
function translateJsxExpression(node: ts.Node, h) { function translateJsxExpression(node: ts.Node, h) {
switch (node.kind) { switch (node.kind) {
case ts.SyntaxKind.StringLiteral: case ts.SyntaxKind.StringLiteral: {
{
let e = node as ts.StringLiteral; let e = node as ts.StringLiteral;
return e.text; return e.text;
} }
@ -208,8 +206,7 @@ export function processFile(sourceFile: ts.SourceFile) {
let holeNum = [1]; let holeNum = [1];
let process = (childNode) => { let process = (childNode) => {
switch (childNode.kind) { switch (childNode.kind) {
case ts.SyntaxKind.JsxText: case ts.SyntaxKind.JsxText: {
{
let e = childNode as ts.JsxText; let e = childNode as ts.JsxText;
let s = e.getFullText(); let s = e.getFullText();
let t = s.split("\n").map(trim).join(" "); let t = s.split("\n").map(trim).join(" ");
@ -226,8 +223,7 @@ export function processFile(sourceFile: ts.SourceFile) {
case ts.SyntaxKind.JsxElement: case ts.SyntaxKind.JsxElement:
fragments.push(`%${holeNum[0]++}$s`); fragments.push(`%${holeNum[0]++}$s`);
break; break;
case ts.SyntaxKind.JsxExpression: case ts.SyntaxKind.JsxExpression: {
{
let e = childNode as ts.JsxExpression; let e = childNode as ts.JsxExpression;
fragments.push(translateJsxExpression(e.expression, holeNum)); fragments.push(translateJsxExpression(e.expression, holeNum));
break; break;
@ -235,8 +231,17 @@ export function processFile(sourceFile: ts.SourceFile) {
case ts.SyntaxKind.JsxClosingElement: case ts.SyntaxKind.JsxClosingElement:
break; break;
default: default:
let lc = ts.getLineAndCharacterOfPosition(childNode.getSourceFile(), childNode.getStart()); let lc = ts.getLineAndCharacterOfPosition(
console.error(`unrecognized syntax in JSX Element ${ts.SyntaxKind[childNode.kind]} (${childNode.getSourceFile().fileName}:${lc.line+1}:${lc.character+1}`); childNode.getSourceFile(),
childNode.getStart(),
);
console.error(
`unrecognized syntax in JSX Element ${
ts.SyntaxKind[childNode.kind]
} (${childNode.getSourceFile().fileName}:${lc.line + 1}:${
lc.character + 1
}`,
);
break; break;
} }
}; };
@ -248,8 +253,7 @@ export function processFile(sourceFile: ts.SourceFile) {
let res; let res;
let process = (childNode) => { let process = (childNode) => {
switch (childNode.kind) { switch (childNode.kind) {
case ts.SyntaxKind.JsxElement: case ts.SyntaxKind.JsxElement: {
{
let path = getJsxElementPath(childNode); let path = getJsxElementPath(childNode);
if (arrayEq(path, ["i18n", "TranslateSingular"])) { if (arrayEq(path, ["i18n", "TranslateSingular"])) {
res = getJsxContent(childNode); res = getJsxContent(childNode);
@ -267,8 +271,7 @@ export function processFile(sourceFile: ts.SourceFile) {
let res; let res;
let process = (childNode) => { let process = (childNode) => {
switch (childNode.kind) { switch (childNode.kind) {
case ts.SyntaxKind.JsxElement: case ts.SyntaxKind.JsxElement: {
{
let path = getJsxElementPath(childNode); let path = getJsxElementPath(childNode);
if (arrayEq(path, ["i18n", "TranslatePlural"])) { if (arrayEq(path, ["i18n", "TranslatePlural"])) {
res = getJsxContent(childNode); res = getJsxContent(childNode);
@ -282,7 +285,6 @@ export function processFile(sourceFile: ts.SourceFile) {
return res; return res;
} }
function processNode(node: ts.Node) { function processNode(node: ts.Node) {
switch (node.kind) { switch (node.kind) {
case ts.SyntaxKind.JsxElement: case ts.SyntaxKind.JsxElement:
@ -319,8 +321,7 @@ export function processFile(sourceFile: ts.SourceFile) {
return; return;
} }
break; break;
case ts.SyntaxKind.CallExpression: case ts.SyntaxKind.CallExpression: {
{
// might be i18n.plural(i18n[.X]`...`, i18n[.X]`...`) // might be i18n.plural(i18n[.X]`...`, i18n[.X]`...`)
let ce = <ts.CallExpression>node; let ce = <ts.CallExpression>node;
let path = getPath(ce.expression); let path = getPath(ce.expression);
@ -334,8 +335,12 @@ export function processFile(sourceFile: ts.SourceFile) {
break; break;
} }
let { line } = ts.getLineAndCharacterOfPosition(sourceFile, ce.pos); let { line } = ts.getLineAndCharacterOfPosition(sourceFile, ce.pos);
let t1 = processTaggedTemplateExpression(<ts.TaggedTemplateExpression>ce.arguments[0]); let t1 = processTaggedTemplateExpression(
let t2 = processTaggedTemplateExpression(<ts.TaggedTemplateExpression>ce.arguments[1]); <ts.TaggedTemplateExpression>ce.arguments[0],
);
let t2 = processTaggedTemplateExpression(
<ts.TaggedTemplateExpression>ce.arguments[1],
);
let comment = getComment(ce); let comment = getComment(ce);
formatMsgComment(line, comment); formatMsgComment(line, comment);
@ -348,10 +353,11 @@ export function processFile(sourceFile: ts.SourceFile) {
// Important: no processing for child i18n expressions here // Important: no processing for child i18n expressions here
return; return;
} }
case ts.SyntaxKind.TaggedTemplateExpression: case ts.SyntaxKind.TaggedTemplateExpression: {
{
let tte = <ts.TaggedTemplateExpression>node; let tte = <ts.TaggedTemplateExpression>node;
let {comment, template, line, path} = processTaggedTemplateExpression(tte); let { comment, template, line, path } = processTaggedTemplateExpression(
tte,
);
if (path[0] != "i18n") { if (path[0] != "i18n") {
break; break;
} }
@ -367,10 +373,36 @@ export function processFile(sourceFile: ts.SourceFile) {
} }
} }
const fileNames = process.argv.slice(2); function main() {
const configPath = ts.findConfigFile(
/*searchPath*/ "./",
ts.sys.fileExists,
"tsconfig.json",
);
if (!configPath) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}
console.log( const cmdline = ts.getParsedCommandLineOfConfigFile(
`# SOME DESCRIPTIVE TITLE. 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.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
@ -388,11 +420,19 @@ msgstr ""
"MIME-Version: 1.0\\n" "MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n" "Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"`); "Content-Transfer-Encoding: 8bit\\n"`);
console.log()
fileNames.sort(); fileNames.forEach((fileName) => {
let sourceFile = ts.createSourceFile(
fileNames.forEach(fileName => { fileName,
let sourceFile = ts.createSourceFile(fileName, readFileSync(fileName).toString(), ts.ScriptTarget.ES2016, /*setParentNodes */ true); readFileSync(fileName).toString(),
ts.ScriptTarget.ES2016,
/*setParentNodes */ true,
);
processFile(sourceFile); processFile(sourceFile);
}); });
const out = outChunks.join("");
console.log(out);
}
main();

View File

@ -4,8 +4,8 @@
"target": "es5", "target": "es5",
"noImplicitAny": false, "noImplicitAny": false,
"sourceMap": false, "sourceMap": false,
"outDir": "bin", "outDir": "lib",
"incremental": true, "incremental": true
}, },
"files": [ "files": [
"pogen.ts" "pogen.ts"

View File

@ -5,5 +5,6 @@ This package implements various utility functionality for GNU Taler.
## When should something be moved to this package? ## When should something be moved to this package?
The ``@gnu-taler/taler-util`` package should have minimal dependencies The ``@gnu-taler/taler-util`` package should have minimal dependencies and
and as few platform-specific functionality as possible. should not be platform specific.