1+ // PEP 508 environment marker evaluator.
2+ // Filters Python dependencies by platform/version markers so that e.g.
3+ // "pywin32 ; sys_platform == 'win32'" is excluded on Linux/macOS.
4+ // See https://peps.python.org/pep-0508/#environment-markers
5+
16import { execSync } from 'node:child_process'
27import os from 'node:os'
38
@@ -15,7 +20,11 @@ function getPythonVersion() {
1520 return cachedPythonVersion
1621}
1722
18- /** @returns {Record<string, string> } */
23+ /**
24+ * Maps Node.js/OS values to PEP 508 marker variables.
25+ * Example: on Linux, sys_platform='linux', platform_system='Linux', os_name='posix'
26+ * @returns {Record<string, string> }
27+ */
1928export function getEnvironmentMarkers ( ) {
2029 let platform = process . platform
2130 let systemMap = { win32 : 'Windows' , linux : 'Linux' , darwin : 'Darwin' }
@@ -47,6 +56,9 @@ function compareVersions(left, right) {
4756 return 0
4857}
4958
59+ // Evaluates a single comparison like sys_platform == 'win32' or python_version >= '3.8'.
60+ // Version-bearing variables (python_version, python_full_version) use numeric comparison;
61+ // all others use string equality. Returns false when the env value is missing.
5062function evaluateComparison ( variable , op , value , env ) {
5163 let envVal = env [ variable ]
5264 if ( envVal === undefined || envVal === '' ) {
@@ -82,17 +94,26 @@ function evaluateComparison(variable, op, value, env) {
8294 }
8395}
8496
97+ // Parses a single marker comparison into {variable, op, value}.
98+ // Handles both normal and reversed forms:
99+ // "sys_platform == 'linux'" → { variable: 'sys_platform', op: '==', value: 'linux' }
100+ // "'linux' == sys_platform" → { variable: 'sys_platform', op: '==', value: 'linux' }
85101function parseAtom ( expr ) {
102+ // Normal form: variable op 'value'
86103 let m = expr . match ( / ^ \s * ( [ \w . ] + ) \s * ( ~ = | ! = | = = | > = | < = | > | < | n o t \s + i n | i n ) \s * [ " ' ] ( [ ^ " ' ] * ) [ " ' ] \s * $ / )
87104 if ( m ) { return { variable : m [ 1 ] , op : m [ 2 ] . replace ( / \s + / g, ' ' ) , value : m [ 3 ] } }
88105
106+ // Reversed form: 'value' op variable
89107 let mReverse = expr . match ( / ^ \s * [ " ' ] ( [ ^ " ' ] * ) [ ' " ] \s * ( ~ = | ! = | = = | > = | < = | > | < | n o t \s + i n | i n ) \s * ( [ \w . ] + ) \s * $ / )
90108 if ( mReverse ) { return { variable : mReverse [ 3 ] , op : mReverse [ 2 ] . replace ( / \s + / g, ' ' ) , value : mReverse [ 1 ] } }
91109
92110 return null
93111}
94112
95113/**
114+ * Evaluates a full PEP 508 marker expression against the current platform.
115+ * Example: "sys_platform == 'win32' and python_version >= '3.8'" → false on Linux
116+ * Empty/missing markers return true (unconditional dependency).
96117 * @param {string } markerExpr
97118 * @returns {boolean }
98119 */
@@ -124,6 +145,10 @@ function evaluateExpr(expr, env) {
124145 return evaluateComparison ( atom . variable , atom . op , atom . value , env )
125146}
126147
148+ // Splits an expression by " and " or " or " at the top level, skipping
149+ // separators inside parentheses or quoted strings.
150+ // Example: splitLogical("a == 'x' and (b == 'y' or c == 'z')", " and ")
151+ // → ["a == 'x'", "(b == 'y' or c == 'z')"]
127152function splitLogical ( expr , sep ) {
128153 let parts = [ ]
129154 let depth = 0
0 commit comments