@generaltranslation/python-extractor 0.2.20 → 0.2.22

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.
@@ -1,86 +1,71 @@
1
- import { PYTHON_GT_PACKAGES, PYTHON_TRANSLATION_FUNCTIONS, } from './constants.js';
1
+ import { PYTHON_GT_PACKAGES, PYTHON_TRANSLATION_FUNCTIONS } from "./constants.js";
2
+ //#region src/extractImports.ts
2
3
  /**
3
- * Extracts GT-related imports from a Python AST.
4
- *
5
- * Handles:
6
- * - `from gt_flask import t`
7
- * - `from gt_flask import t as translate`
8
- * - `from gt_flask import t, msg`
9
- */
10
- export function extractImports(rootNode) {
11
- const aliases = [];
12
- for (let i = 0; i < rootNode.childCount; i++) {
13
- const node = rootNode.child(i);
14
- if (!node || node.type !== 'import_from_statement')
15
- continue;
16
- const moduleName = getModuleName(node);
17
- if (!moduleName || !isGtPackage(moduleName))
18
- continue;
19
- // Collect all imported names from this statement
20
- for (let j = 0; j < node.childCount; j++) {
21
- const child = node.child(j);
22
- if (!child)
23
- continue;
24
- if (child.type === 'aliased_import') {
25
- // `from gt_flask import t as translate`
26
- const nameNode = child.childForFieldName('name');
27
- const aliasNode = child.childForFieldName('alias');
28
- const originalName = nameNode ? getIdentifierText(nameNode) : undefined;
29
- const localName = aliasNode ? aliasNode.text : originalName;
30
- if (originalName && localName && isTranslationFunction(originalName)) {
31
- aliases.push({ localName, originalName, packageName: moduleName });
32
- }
33
- }
34
- else if (child.type === 'dotted_name') {
35
- // Skip the module name itself (first dotted_name is the module)
36
- const text = child.text;
37
- if (text === moduleName)
38
- continue;
39
- // `from gt_flask import t` — only track translation functions
40
- if (isTranslationFunction(text)) {
41
- aliases.push({
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
- const moduleNode = importNode.childForFieldName('module_name');
54
- if (moduleNode)
55
- return moduleNode.text;
56
- // Fallback: find the first dotted_name child (before 'import' keyword)
57
- for (let i = 0; i < importNode.childCount; i++) {
58
- const child = importNode.child(i);
59
- if (!child)
60
- continue;
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
- if (node.type === 'identifier')
70
- return node.text;
71
- if (node.type === 'dotted_name') {
72
- // Get the last identifier in a dotted name
73
- for (let i = node.childCount - 1; i >= 0; i--) {
74
- const child = node.child(i);
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
- return PYTHON_GT_PACKAGES.includes(name);
63
+ return PYTHON_GT_PACKAGES.includes(name);
83
64
  }
84
65
  function isTranslationFunction(name) {
85
- return PYTHON_TRANSLATION_FUNCTIONS.includes(name);
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 { getParser } from './parser.js';
2
- import { extractImports } from './extractImports.js';
3
- import { extractCalls } from './extractCalls.js';
4
- export { PYTHON_GT_PACKAGES, PYTHON_GT_DEPENDENCIES, PYTHON_T_FUNCTION, PYTHON_MSG_FUNCTION, PYTHON_DERIVE, PYTHON_DECLARE_STATIC, PYTHON_DECLARE_VAR, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_METADATA_KWARGS, } from './constants.js';
5
- export async function extractFromPythonSource(sourceCode, filePath) {
6
- let parser;
7
- try {
8
- parser = await getParser();
9
- }
10
- catch (error) {
11
- return {
12
- results: [],
13
- errors: [],
14
- warnings: [
15
- `${filePath}: Failed to initialize Python parser; skipping Python extraction. ${formatError(error)}`,
16
- ],
17
- };
18
- }
19
- const tree = parser.parse(sourceCode);
20
- if (!tree) {
21
- return {
22
- results: [],
23
- errors: [`Failed to parse ${filePath}`],
24
- warnings: [],
25
- };
26
- }
27
- // Step 1: Extract GT imports
28
- const imports = extractImports(tree.rootNode);
29
- if (imports.length === 0) {
30
- return { results: [], errors: [], warnings: [] };
31
- }
32
- // Step 2: Extract translation calls
33
- const { calls, errors, warnings } = await extractCalls(tree.rootNode, imports, filePath);
34
- // Step 3: Map to ExtractionResult
35
- const results = calls.map((call) => ({
36
- dataFormat: 'ICU',
37
- source: call.source,
38
- metadata: {
39
- ...(call.id && { id: call.id }),
40
- ...(call.context && { context: call.context }),
41
- ...(call.maxChars != null && { maxChars: call.maxChars }),
42
- ...(call.staticId && { staticId: call.staticId }),
43
- filePaths: [filePath],
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
- return messages.map((msg) => `${filePath}: ${msg}`);
47
+ return messages.map((msg) => `${filePath}: ${msg}`);
54
48
  }
55
49
  function formatError(error) {
56
- return error instanceof Error ? error.message : String(error);
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"}