@generaltranslation/python-extractor 0.2.20 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.js +22 -17
- package/dist/constants.js.map +1 -0
- package/dist/extractCalls.js +188 -271
- package/dist/extractCalls.js.map +1 -0
- package/dist/extractImports.js +61 -76
- package/dist/extractImports.js.map +1 -0
- package/dist/index.js +50 -52
- package/dist/index.js.map +1 -0
- package/dist/parseStringExpression.js +722 -983
- package/dist/parseStringExpression.js.map +1 -0
- package/dist/parser.js +30 -37
- package/dist/parser.js.map +1 -0
- package/dist/resolveFunctionVariants.js +154 -181
- package/dist/resolveFunctionVariants.js.map +1 -0
- package/dist/resolveImport.js +48 -60
- package/dist/resolveImport.js.map +1 -0
- package/dist/stringNode.js +32 -46
- package/dist/stringNode.js.map +1 -0
- package/package.json +7 -4
package/dist/extractImports.js
CHANGED
|
@@ -1,86 +1,71 @@
|
|
|
1
|
-
import { PYTHON_GT_PACKAGES, PYTHON_TRANSLATION_FUNCTIONS
|
|
1
|
+
import { PYTHON_GT_PACKAGES, PYTHON_TRANSLATION_FUNCTIONS } from "./constants.js";
|
|
2
|
+
//#region src/extractImports.ts
|
|
2
3
|
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
localName: text,
|
|
43
|
-
originalName: text,
|
|
44
|
-
packageName: moduleName,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return aliases;
|
|
4
|
+
* Extracts GT-related imports from a Python AST.
|
|
5
|
+
*
|
|
6
|
+
* Handles:
|
|
7
|
+
* - `from gt_flask import t`
|
|
8
|
+
* - `from gt_flask import t as translate`
|
|
9
|
+
* - `from gt_flask import t, msg`
|
|
10
|
+
*/
|
|
11
|
+
function extractImports(rootNode) {
|
|
12
|
+
const aliases = [];
|
|
13
|
+
for (let i = 0; i < rootNode.childCount; i++) {
|
|
14
|
+
const node = rootNode.child(i);
|
|
15
|
+
if (!node || node.type !== "import_from_statement") continue;
|
|
16
|
+
const moduleName = getModuleName(node);
|
|
17
|
+
if (!moduleName || !isGtPackage(moduleName)) continue;
|
|
18
|
+
for (let j = 0; j < node.childCount; j++) {
|
|
19
|
+
const child = node.child(j);
|
|
20
|
+
if (!child) continue;
|
|
21
|
+
if (child.type === "aliased_import") {
|
|
22
|
+
const nameNode = child.childForFieldName("name");
|
|
23
|
+
const aliasNode = child.childForFieldName("alias");
|
|
24
|
+
const originalName = nameNode ? getIdentifierText(nameNode) : void 0;
|
|
25
|
+
const localName = aliasNode ? aliasNode.text : originalName;
|
|
26
|
+
if (originalName && localName && isTranslationFunction(originalName)) aliases.push({
|
|
27
|
+
localName,
|
|
28
|
+
originalName,
|
|
29
|
+
packageName: moduleName
|
|
30
|
+
});
|
|
31
|
+
} else if (child.type === "dotted_name") {
|
|
32
|
+
const text = child.text;
|
|
33
|
+
if (text === moduleName) continue;
|
|
34
|
+
if (isTranslationFunction(text)) aliases.push({
|
|
35
|
+
localName: text,
|
|
36
|
+
originalName: text,
|
|
37
|
+
packageName: moduleName
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return aliases;
|
|
51
43
|
}
|
|
52
44
|
function getModuleName(importNode) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (child.type === 'import')
|
|
62
|
-
break; // reached the 'import' keyword
|
|
63
|
-
if (child.type === 'dotted_name')
|
|
64
|
-
return child.text;
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
45
|
+
const moduleNode = importNode.childForFieldName("module_name");
|
|
46
|
+
if (moduleNode) return moduleNode.text;
|
|
47
|
+
for (let i = 0; i < importNode.childCount; i++) {
|
|
48
|
+
const child = importNode.child(i);
|
|
49
|
+
if (!child) continue;
|
|
50
|
+
if (child.type === "import") break;
|
|
51
|
+
if (child.type === "dotted_name") return child.text;
|
|
52
|
+
}
|
|
67
53
|
}
|
|
68
54
|
function getIdentifierText(node) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (child && child.type === 'identifier')
|
|
76
|
-
return child.text;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return node.text;
|
|
55
|
+
if (node.type === "identifier") return node.text;
|
|
56
|
+
if (node.type === "dotted_name") for (let i = node.childCount - 1; i >= 0; i--) {
|
|
57
|
+
const child = node.child(i);
|
|
58
|
+
if (child && child.type === "identifier") return child.text;
|
|
59
|
+
}
|
|
60
|
+
return node.text;
|
|
80
61
|
}
|
|
81
62
|
function isGtPackage(name) {
|
|
82
|
-
|
|
63
|
+
return PYTHON_GT_PACKAGES.includes(name);
|
|
83
64
|
}
|
|
84
65
|
function isTranslationFunction(name) {
|
|
85
|
-
|
|
66
|
+
return PYTHON_TRANSLATION_FUNCTIONS.includes(name);
|
|
86
67
|
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { extractImports };
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=extractImports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractImports.js","names":[],"sources":["../src/extractImports.ts"],"sourcesContent":["import type { SyntaxNode } from './parser.js';\nimport {\n PYTHON_GT_PACKAGES,\n PYTHON_TRANSLATION_FUNCTIONS,\n} from './constants.js';\n\nexport type ImportAlias = {\n /** The local name used in the source file (e.g. \"translate\" for `import t as translate`) */\n localName: string;\n /** The original imported name (e.g. \"t\") */\n originalName: string;\n /** The package it was imported from (e.g. \"gt_flask\") */\n packageName: string;\n};\n\n/**\n * Extracts GT-related imports from a Python AST.\n *\n * Handles:\n * - `from gt_flask import t`\n * - `from gt_flask import t as translate`\n * - `from gt_flask import t, msg`\n */\nexport function extractImports(rootNode: SyntaxNode): ImportAlias[] {\n const aliases: ImportAlias[] = [];\n\n for (let i = 0; i < rootNode.childCount; i++) {\n const node = rootNode.child(i);\n if (!node || node.type !== 'import_from_statement') continue;\n\n const moduleName = getModuleName(node);\n if (!moduleName || !isGtPackage(moduleName)) continue;\n\n // Collect all imported names from this statement\n for (let j = 0; j < node.childCount; j++) {\n const child = node.child(j);\n if (!child) continue;\n\n if (child.type === 'aliased_import') {\n // `from gt_flask import t as translate`\n const nameNode = child.childForFieldName('name');\n const aliasNode = child.childForFieldName('alias');\n const originalName = nameNode ? getIdentifierText(nameNode) : undefined;\n const localName = aliasNode ? aliasNode.text : originalName;\n if (originalName && localName && isTranslationFunction(originalName)) {\n aliases.push({ localName, originalName, packageName: moduleName });\n }\n } else if (child.type === 'dotted_name') {\n // Skip the module name itself (first dotted_name is the module)\n const text = child.text;\n if (text === moduleName) continue;\n // `from gt_flask import t` — only track translation functions\n if (isTranslationFunction(text)) {\n aliases.push({\n localName: text,\n originalName: text,\n packageName: moduleName,\n });\n }\n }\n }\n }\n\n return aliases;\n}\n\nfunction getModuleName(importNode: SyntaxNode): string | undefined {\n const moduleNode = importNode.childForFieldName('module_name');\n if (moduleNode) return moduleNode.text;\n\n // Fallback: find the first dotted_name child (before 'import' keyword)\n for (let i = 0; i < importNode.childCount; i++) {\n const child = importNode.child(i);\n if (!child) continue;\n if (child.type === 'import') break; // reached the 'import' keyword\n if (child.type === 'dotted_name') return child.text;\n }\n return undefined;\n}\n\nfunction getIdentifierText(node: SyntaxNode): string | undefined {\n if (node.type === 'identifier') return node.text;\n if (node.type === 'dotted_name') {\n // Get the last identifier in a dotted name\n for (let i = node.childCount - 1; i >= 0; i--) {\n const child = node.child(i);\n if (child && child.type === 'identifier') return child.text;\n }\n }\n return node.text;\n}\n\nfunction isGtPackage(name: string): boolean {\n return (PYTHON_GT_PACKAGES as readonly string[]).includes(name);\n}\n\nfunction isTranslationFunction(name: string): boolean {\n return (PYTHON_TRANSLATION_FUNCTIONS as readonly string[]).includes(name);\n}\n"],"mappings":";;;;;;;;;;AAuBA,SAAgB,eAAe,UAAqC;CAClE,MAAM,UAAyB,EAAE;AAEjC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,YAAY,KAAK;EAC5C,MAAM,OAAO,SAAS,MAAM,EAAE;AAC9B,MAAI,CAAC,QAAQ,KAAK,SAAS,wBAAyB;EAEpD,MAAM,aAAa,cAAc,KAAK;AACtC,MAAI,CAAC,cAAc,CAAC,YAAY,WAAW,CAAE;AAG7C,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,YAAY,KAAK;GACxC,MAAM,QAAQ,KAAK,MAAM,EAAE;AAC3B,OAAI,CAAC,MAAO;AAEZ,OAAI,MAAM,SAAS,kBAAkB;IAEnC,MAAM,WAAW,MAAM,kBAAkB,OAAO;IAChD,MAAM,YAAY,MAAM,kBAAkB,QAAQ;IAClD,MAAM,eAAe,WAAW,kBAAkB,SAAS,GAAG,KAAA;IAC9D,MAAM,YAAY,YAAY,UAAU,OAAO;AAC/C,QAAI,gBAAgB,aAAa,sBAAsB,aAAa,CAClE,SAAQ,KAAK;KAAE;KAAW;KAAc,aAAa;KAAY,CAAC;cAE3D,MAAM,SAAS,eAAe;IAEvC,MAAM,OAAO,MAAM;AACnB,QAAI,SAAS,WAAY;AAEzB,QAAI,sBAAsB,KAAK,CAC7B,SAAQ,KAAK;KACX,WAAW;KACX,cAAc;KACd,aAAa;KACd,CAAC;;;;AAMV,QAAO;;AAGT,SAAS,cAAc,YAA4C;CACjE,MAAM,aAAa,WAAW,kBAAkB,cAAc;AAC9D,KAAI,WAAY,QAAO,WAAW;AAGlC,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,YAAY,KAAK;EAC9C,MAAM,QAAQ,WAAW,MAAM,EAAE;AACjC,MAAI,CAAC,MAAO;AACZ,MAAI,MAAM,SAAS,SAAU;AAC7B,MAAI,MAAM,SAAS,cAAe,QAAO,MAAM;;;AAKnD,SAAS,kBAAkB,MAAsC;AAC/D,KAAI,KAAK,SAAS,aAAc,QAAO,KAAK;AAC5C,KAAI,KAAK,SAAS,cAEhB,MAAK,IAAI,IAAI,KAAK,aAAa,GAAG,KAAK,GAAG,KAAK;EAC7C,MAAM,QAAQ,KAAK,MAAM,EAAE;AAC3B,MAAI,SAAS,MAAM,SAAS,aAAc,QAAO,MAAM;;AAG3D,QAAO,KAAK;;AAGd,SAAS,YAAY,MAAuB;AAC1C,QAAQ,mBAAyC,SAAS,KAAK;;AAGjE,SAAS,sBAAsB,MAAuB;AACpD,QAAQ,6BAAmD,SAAS,KAAK"}
|
package/dist/index.js
CHANGED
|
@@ -1,57 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}));
|
|
46
|
-
return {
|
|
47
|
-
results,
|
|
48
|
-
errors: prefixErrors(errors, filePath),
|
|
49
|
-
warnings: prefixErrors(warnings, filePath),
|
|
50
|
-
};
|
|
1
|
+
import { PYTHON_DECLARE_STATIC, PYTHON_DECLARE_VAR, PYTHON_DERIVE, PYTHON_GT_DEPENDENCIES, PYTHON_GT_PACKAGES, PYTHON_METADATA_KWARGS, PYTHON_MSG_FUNCTION, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_T_FUNCTION } from "./constants.js";
|
|
2
|
+
import { getParser } from "./parser.js";
|
|
3
|
+
import { extractImports } from "./extractImports.js";
|
|
4
|
+
import { extractCalls } from "./extractCalls.js";
|
|
5
|
+
//#region src/index.ts
|
|
6
|
+
async function extractFromPythonSource(sourceCode, filePath) {
|
|
7
|
+
let parser;
|
|
8
|
+
try {
|
|
9
|
+
parser = await getParser();
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return {
|
|
12
|
+
results: [],
|
|
13
|
+
errors: [],
|
|
14
|
+
warnings: [`${filePath}: Failed to initialize Python parser; skipping Python extraction. ${formatError(error)}`]
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const tree = parser.parse(sourceCode);
|
|
18
|
+
if (!tree) return {
|
|
19
|
+
results: [],
|
|
20
|
+
errors: [`Failed to parse ${filePath}`],
|
|
21
|
+
warnings: []
|
|
22
|
+
};
|
|
23
|
+
const imports = extractImports(tree.rootNode);
|
|
24
|
+
if (imports.length === 0) return {
|
|
25
|
+
results: [],
|
|
26
|
+
errors: [],
|
|
27
|
+
warnings: []
|
|
28
|
+
};
|
|
29
|
+
const { calls, errors, warnings } = await extractCalls(tree.rootNode, imports, filePath);
|
|
30
|
+
return {
|
|
31
|
+
results: calls.map((call) => ({
|
|
32
|
+
dataFormat: "ICU",
|
|
33
|
+
source: call.source,
|
|
34
|
+
metadata: {
|
|
35
|
+
...call.id && { id: call.id },
|
|
36
|
+
...call.context && { context: call.context },
|
|
37
|
+
...call.maxChars != null && { maxChars: call.maxChars },
|
|
38
|
+
...call.staticId && { staticId: call.staticId },
|
|
39
|
+
filePaths: [filePath]
|
|
40
|
+
}
|
|
41
|
+
})),
|
|
42
|
+
errors: prefixErrors(errors, filePath),
|
|
43
|
+
warnings: prefixErrors(warnings, filePath)
|
|
44
|
+
};
|
|
51
45
|
}
|
|
52
46
|
function prefixErrors(messages, filePath) {
|
|
53
|
-
|
|
47
|
+
return messages.map((msg) => `${filePath}: ${msg}`);
|
|
54
48
|
}
|
|
55
49
|
function formatError(error) {
|
|
56
|
-
|
|
50
|
+
return error instanceof Error ? error.message : String(error);
|
|
57
51
|
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { PYTHON_DECLARE_STATIC, PYTHON_DECLARE_VAR, PYTHON_DERIVE, PYTHON_GT_DEPENDENCIES, PYTHON_GT_PACKAGES, PYTHON_METADATA_KWARGS, PYTHON_MSG_FUNCTION, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_T_FUNCTION, extractFromPythonSource };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { ExtractionResult } from './types.js';\nimport { getParser } from './parser.js';\nimport { extractImports } from './extractImports.js';\nimport { extractCalls } from './extractCalls.js';\n\nexport type { ExtractionResult, ExtractionMetadata } from './types.js';\nexport type { ImportAlias } from './extractImports.js';\nexport {\n PYTHON_GT_PACKAGES,\n PYTHON_GT_DEPENDENCIES,\n PYTHON_T_FUNCTION,\n PYTHON_MSG_FUNCTION,\n PYTHON_DERIVE,\n PYTHON_DECLARE_STATIC,\n PYTHON_DECLARE_VAR,\n PYTHON_TRANSLATION_FUNCTIONS,\n PYTHON_METADATA_KWARGS,\n} from './constants.js';\n\nexport async function extractFromPythonSource(\n sourceCode: string,\n filePath: string\n): Promise<{\n results: ExtractionResult[];\n errors: string[];\n warnings: string[];\n}> {\n let parser: Awaited<ReturnType<typeof getParser>>;\n try {\n parser = await getParser();\n } catch (error) {\n return {\n results: [],\n errors: [],\n warnings: [\n `${filePath}: Failed to initialize Python parser; skipping Python extraction. ${formatError(error)}`,\n ],\n };\n }\n\n const tree = parser.parse(sourceCode);\n if (!tree) {\n return {\n results: [],\n errors: [`Failed to parse ${filePath}`],\n warnings: [],\n };\n }\n\n // Step 1: Extract GT imports\n const imports = extractImports(tree.rootNode);\n if (imports.length === 0) {\n return { results: [], errors: [], warnings: [] };\n }\n\n // Step 2: Extract translation calls\n const { calls, errors, warnings } = await extractCalls(\n tree.rootNode,\n imports,\n filePath\n );\n\n // Step 3: Map to ExtractionResult\n const results: ExtractionResult[] = calls.map((call) => ({\n dataFormat: 'ICU' as const,\n source: call.source,\n metadata: {\n ...(call.id && { id: call.id }),\n ...(call.context && { context: call.context }),\n ...(call.maxChars != null && { maxChars: call.maxChars }),\n ...(call.staticId && { staticId: call.staticId }),\n filePaths: [filePath],\n },\n }));\n\n return {\n results,\n errors: prefixErrors(errors, filePath),\n warnings: prefixErrors(warnings, filePath),\n };\n}\n\nfunction prefixErrors(messages: string[], filePath: string): string[] {\n return messages.map((msg) => `${filePath}: ${msg}`);\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":";;;;;AAmBA,eAAsB,wBACpB,YACA,UAKC;CACD,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,WAAW;UACnB,OAAO;AACd,SAAO;GACL,SAAS,EAAE;GACX,QAAQ,EAAE;GACV,UAAU,CACR,GAAG,SAAS,oEAAoE,YAAY,MAAM,GACnG;GACF;;CAGH,MAAM,OAAO,OAAO,MAAM,WAAW;AACrC,KAAI,CAAC,KACH,QAAO;EACL,SAAS,EAAE;EACX,QAAQ,CAAC,mBAAmB,WAAW;EACvC,UAAU,EAAE;EACb;CAIH,MAAM,UAAU,eAAe,KAAK,SAAS;AAC7C,KAAI,QAAQ,WAAW,EACrB,QAAO;EAAE,SAAS,EAAE;EAAE,QAAQ,EAAE;EAAE,UAAU,EAAE;EAAE;CAIlD,MAAM,EAAE,OAAO,QAAQ,aAAa,MAAM,aACxC,KAAK,UACL,SACA,SACD;AAeD,QAAO;EACL,SAbkC,MAAM,KAAK,UAAU;GACvD,YAAY;GACZ,QAAQ,KAAK;GACb,UAAU;IACR,GAAI,KAAK,MAAM,EAAE,IAAI,KAAK,IAAI;IAC9B,GAAI,KAAK,WAAW,EAAE,SAAS,KAAK,SAAS;IAC7C,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,UAAU;IACxD,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,UAAU;IAChD,WAAW,CAAC,SAAS;IACtB;GACF,EAGQ;EACP,QAAQ,aAAa,QAAQ,SAAS;EACtC,UAAU,aAAa,UAAU,SAAS;EAC3C;;AAGH,SAAS,aAAa,UAAoB,UAA4B;AACpE,QAAO,SAAS,KAAK,QAAQ,GAAG,SAAS,IAAI,MAAM;;AAGrD,SAAS,YAAY,OAAwB;AAC3C,QAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM"}
|