Skip to content

Commit 220fcd6

Browse files
ruromeroclaude
andcommitted
docs(python): add comments to marker evaluator and provider integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 52a17d4 commit 220fcd6

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

src/providers/marker_evaluator.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
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+
16
import { execSync } from 'node:child_process'
27
import 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+
*/
1928
export 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.
5062
function 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' }
85101
function parseAtom(expr) {
102+
// Normal form: variable op 'value'
86103
let m = expr.match(/^\s*([\w.]+)\s*(~=|!=|==|>=|<=|>|<|not\s+in|in)\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*(~=|!=|==|>=|<=|>|<|not\s+in|in)\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')"]
127152
function splitLogical(expr, sep) {
128153
let parts = []
129154
let depth = 0

src/providers/python_poetry.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ export default class Python_poetry extends Base_pyproject {
113113
}
114114

115115
/**
116+
* Collects PEP 508 marker expressions for direct and transitive deps.
117+
* Direct markers come from pyproject.toml dependency strings, e.g.:
118+
* "pywin32>=311 ; sys_platform == 'win32'" → directMarkers['pywin32'] = "sys_platform == 'win32'"
119+
* Transitive markers come from poetry.lock [package.dependencies] entries, e.g.:
120+
* colorama = {version = "*", markers = "sys_platform == 'win32'"}
121+
* → transitiveMarkers['click']['colorama'] = "sys_platform == 'win32'"
116122
* @param {string|null} lockDir
117123
* @param {object} parsed - parsed pyproject.toml
118124
* @returns {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>}}
@@ -121,6 +127,7 @@ export default class Python_poetry extends Base_pyproject {
121127
let directMarkers = new Map()
122128
let transitiveMarkers = new Map()
123129

130+
// Extract markers from PEP 621 dependency strings: "name[extras]>=ver ; marker"
124131
let deps = parsed.project?.dependencies || []
125132
for (let dep of deps) {
126133
let m = dep.match(/^([A-Za-z0-9][A-Za-z0-9._-]*)\s*[^;]*;\s*(.+)$/)

src/providers/python_uv.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export default class Python_uv extends Base_pyproject {
9999
let nameNode = child.children.find(c => c.type === 'package')
100100
if (!nameNode) { continue }
101101

102+
// Skip packages with non-matching PEP 508 markers, e.g. "pywin32==311 ; sys_platform == 'win32'"
102103
let markerNode = child.children.find(c => c.type === 'marker_spec')
103104
if (markerNode) {
104105
let markerText = markerNode.text.replace(/^\s*;\s*/, '')

0 commit comments

Comments
 (0)